Adding Kusto Telemetry to ApplicationHealthLinux v2 (#63)

* Adding internal/manifest package from Cross-Platform AppHealth Feature Branch

* Running go mod tidy and go mod vendor

* - Add manifest.xml to Extension folder
- Chaged Github workflow go version to Go 1.18
- Small refactor in setup function for bats tests.

* Update Go version to 1.18 in Dockerfile

* Add logging package with NopLogger implementation

* Add telemetry package for logging events

* - Add telemetry event Logging to main.go

* - Add new String() methods to vmWatchSignalFilters and vmWatchSettings structs
- Add telemetry event Logging to handlersettings.go

* - Add telemetry event Logging to reportstatus.go

* Add telemetry event Logging to health.go

* Refactor install handler in main/cmds.go to use telemetry event logging

* Refactor uninstall handler in main/cmds.go to use telemetry event logging

* Refactor enable handler function in main/cmds.go to use telemetry event logging

* Refactor vmWatch.go to use telemetry event logging

* Fix requestPath in extension-settings.json and updated 2 integration tests,  one in 2_handler-commands.bats and another in 7_vmwatch.bats

* ran go mod tidy && go mod vendor

* Update ExtensionManifest version to 2.0.9 on UT

* Refactor telemetry event sender to use EventLevel constants in main/telemetry.go

* Refactor telemetry event sender to use EventTasks constants that match with existing Windows Telemetry

* Update logging messages in 7_vmwatch.bats

* Moved telemetry.go to its package in internal/telemetry

* Update Go version to 1.22 in Dockerfile, go.yml, go.mod, and go.sum

* Update ExtensionManifest version to 2.0.9 on UT

* Add NopLogger documentation to pkg/logging/logging.go

* Added Documentation to Telemetry Pkg

* -Added a Wrapper to HandlerEnviroment to add Additional functionality like the String() func
- Added String() func to handlersettings struct, publicSettings struct, vmWatchSettings struct and
vmWatchSignalFilters struct
- Added Telemetry Event for HandlerSettings, and for HandlerEnviroment

* - Updated HandlerEnviroment String to use MarshallIndent Function.
- Updated HandlerSettings struct String() func to use MarshallIndent
- Fixed Failing UTs due to nil pointer in Embedded Struct inside HandlerEnviroment.

* - Updated vmWatchSetting String Func to use MarshallIdent

* Update ExtensionManifest version to 2.0.10 on Failing UT

* removed duplicated UT

* Removed String() func from VMWatchSignalFilters, publicSettings and protectedSettings
This commit is contained in:
Kevin Lugo 2024-05-01 23:46:04 -07:00 коммит произвёл GitHub
Родитель e8e69f4a0b
Коммит b56f2ad074
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
79 изменённых файлов: 2381 добавлений и 1881 удалений

Просмотреть файл

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/go:1.21-bullseye
FROM mcr.microsoft.com/devcontainers/go:1.22-bullseye
RUN apt-get -qqy update && \
apt-get -qqy install jq openssl ca-certificates && \

Просмотреть файл

@ -4,7 +4,7 @@
"handlerSettings": {
"protectedSettingsCertThumbprint": "$cert_tp",
"publicSettings": {
"requestPath": "health",
"requestPath": "/health",
"port": 8080,
"numberOfProbes": 1,
"intervalInSeconds": 5,

20
.github/workflows/go.yml поставляемый
Просмотреть файл

@ -28,9 +28,9 @@ jobs:
run: sudo apt-get update
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: '1.18.10'
go-version: '1.22.2'
- name: Setup Go Environment
run: |
@ -53,6 +53,20 @@ jobs:
sudo apt install npm
sudo npm install -g bats
- name: Setup bats libs
uses: bats-core/bats-action@1.5.6
with:
assert-install: true
support-install: true
bats-install: false
detik-install: false
file-install: false
- name: Testing Bats Installation
run: |
bats --version
sudo bats --version
- name: Install Parallel
run: |
sudo apt install parallel
@ -79,7 +93,7 @@ jobs:
working-directory: ${{ env.repo_root }}
- name: Unit Tests
continue-on-error: true
continue-on-error: false
run: go list ./... | grep -v '/vendor/' | xargs go test -v -cover
working-directory: ${{ env.repo_root }}

6
.vscode/extensions.json поставляемый
Просмотреть файл

@ -1,7 +1,9 @@
{
"recommendations": [
"golang.go",
"ms-vscode-remote.remote-containers",
"ms-vscode-remote.remote-ssh"
"ms-vscode-remote.remote-ssh",
"github.copilot",
"ms-azuretools.vscode-docker",
"github.vscode-pull-request-github"
]
}

16
go.mod
Просмотреть файл

@ -1,32 +1,32 @@
module github.com/Azure/run-command-extension-linux
module github.com/Azure/applicationhealth-extension-linux
go 1.18
go 1.22
require (
github.com/Azure/azure-docker-extension v0.0.0-20160802215703-0dd2f199467d
github.com/Azure/azure-extension-platform v0.0.0-20240327184133-73b5b3b55955
github.com/containerd/cgroups/v3 v3.0.2
github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2
github.com/opencontainers/runtime-spec v1.0.2
github.com/pkg/errors v0.7.1-0.20160627222352-a2d6902c6d2a
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.0
github.com/xeipuuv/gojsonschema v0.0.0-20160623135812-c539bca196be
)
require github.com/go-kit/log v0.2.0
require (
github.com/cilium/ebpf v0.9.1 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/go-logfmt/logfmt v0.2.1-0.20160601130801-d4327190ff83 // indirect
github.com/go-stack/stack v1.5.2 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20151027082146-e0fe6f683076 // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

28
go.sum
Просмотреть файл

@ -1,5 +1,7 @@
github.com/Azure/azure-docker-extension v0.0.0-20160802215703-0dd2f199467d h1:IZq7wAvhHb/IObHOh8RHClV2zv4dHh5MrHdRWTTIwe0=
github.com/Azure/azure-docker-extension v0.0.0-20160802215703-0dd2f199467d/go.mod h1:tVA4DYQYxotjw+EkJhfywtM99w7nAOatBvgNAkpsBvk=
github.com/Azure/azure-extension-platform v0.0.0-20240327184133-73b5b3b55955 h1:klRZMtNE2mFdG8mO3+Ep7Armk1oewdw/7Z4nANDiGgk=
github.com/Azure/azure-extension-platform v0.0.0-20240327184133-73b5b3b55955/go.mod h1:nEQQIC3RKmMnpdc+RakYHIdu556jdcHv67ML8PdsQeQ=
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=
@ -12,28 +14,29 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2 h1:awXynDTA1TiAp1SA/o/xoU6oRHE3xKCokck9l4/poMc=
github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.2.1-0.20160601130801-d4327190ff83 h1:WEFlTYvIQSd2ofUwgM9nOR+KTzdG/pLZzdUlDp6mciM=
github.com/go-logfmt/logfmt v0.2.1-0.20160601130801-d4327190ff83/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.5.2 h1:5sTB/0oZM2O31k/N1IRwxxVXzLIt5NF2Aqx/2gWI9OY=
github.com/go-stack/stack v1.5.2/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pkg/errors v0.7.1-0.20160627222352-a2d6902c6d2a h1:dKpZ0nc8i7prliB4AIfJulQxsX7whlVwi6j5HqaYUl4=
github.com/pkg/errors v0.7.1-0.20160627222352-a2d6902c6d2a/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -49,12 +52,11 @@ github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c/go.mod h1:
github.com/xeipuuv/gojsonschema v0.0.0-20160623135812-c539bca196be h1:sRGd3e18izj1hQgF1hSvDOA8RPPnA2t4p8YeLZ/GdBU=
github.com/xeipuuv/gojsonschema v0.0.0-20160623135812-c539bca196be/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

Просмотреть файл

@ -27,4 +27,5 @@ err1=$?
bats integration-test/test/parallel --jobs 10 -T --trace $FILTER
err2=$?
delete_certificate
rm_image
exit $((err1 + err2))

Просмотреть файл

@ -1,8 +1,7 @@
#!/usr/bin/env bats
load ../test_helper
setup(){
load "../test_helper"
build_docker_image
container_name="handler-command_$BATS_TEST_NUMBER"
}
@ -19,7 +18,7 @@ teardown(){
run start_container
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" = *'event=installed'* ]]
[[ "$output" = *'event="Handler successfully installed"'* ]]
diff="$(container_diff)"
echo "$diff"

Просмотреть файл

@ -1,8 +1,7 @@
#!/usr/bin/env bats
load ../test_helper
setup(){
load "../test_helper"
build_docker_image
container_name="rich-states_$BATS_TEST_NUMBER"
}

Просмотреть файл

@ -1,8 +1,7 @@
#!/usr/bin/env bats
load ../test_helper
setup(){
load "../test_helper"
build_docker_image
container_name="grace-period_$BATS_TEST_NUMBER"
}

Просмотреть файл

@ -1,8 +1,7 @@
#!/usr/bin/env bats
load ../test_helper
setup(){
load "../test_helper"
build_docker_image
container_name="custom-metrics_$BATS_TEST_NUMBER"
}

Просмотреть файл

@ -1,8 +1,7 @@
#!/usr/bin/env bats
load ../test_helper
setup(){
load "../test_helper"
build_docker_image
container_name="tls-config_$BATS_TEST_NUMBER"
}

Просмотреть файл

@ -1,8 +1,8 @@
#!/usr/bin/env bats
load ../test_helper
setup(){
load "../test_helper"
_load_bats_libs
build_docker_image
container_name="vmwatch_$BATS_TEST_NUMBER"
extension_version=$(get_extension_version)
@ -11,6 +11,7 @@ setup(){
teardown(){
rm -rf "$certs_dir"
cleanup
}
@test "handler command: enable - vm watch disabled - vmwatch settings omitted" {
@ -95,7 +96,7 @@ teardown(){
echo "$output"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'--config /var/lib/waagent/Extension/bin/VMWatch/vmwatch.conf'* ]]
[[ "$output" == *"--apphealth-version $extension_version"* ]]
[[ "$output" == *'Env: [SIGNAL_FOLDER=/var/log/azure/Extension/events VERBOSE_LOG_FILE_FULL_PATH=/var/log/azure/Extension/VE.RS.ION/vmwatch.log]'* ]]
@ -130,7 +131,7 @@ teardown(){
echo "$output"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'--config /var/lib/waagent/Extension/bin/VMWatch/vmwatch.conf'* ]]
[[ "$output" == *'--disabled-signals clockskew:az_storage_blob:process:dns'* ]]
[[ "$output" == *"--apphealth-version $extension_version"* ]]
@ -176,7 +177,7 @@ teardown(){
verify_states "$enableLog" "${expectedStateLogs[@]}"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'--config /var/lib/waagent/Extension/bin/VMWatch/vmwatch.conf'* ]]
[[ "$output" == *'--disabled-signals clockskew:az_storage_blob:process:dns'* ]]
[[ "$output" == *"--apphealth-version $extension_version"* ]]
@ -229,7 +230,7 @@ teardown(){
verify_states "$enableLog" "${expectedStateLogs[@]}"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'--config /var/lib/waagent/Extension/bin/VMWatch/vmwatch.conf'* ]]
[[ "$output" == *'--disabled-signals outbound_connectivity:disk_io'* ]]
[[ "$output" == *'--enabled-tags Network'* ]]
@ -270,8 +271,8 @@ teardown(){
echo "$output"
echo "$status_file"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'Attempt 1: VMWatch process started'* ]]
[[ "$output" == *'Attempt 3: VMWatch process started'* ]]
[[ "$output" == *'Attempt 1: Started VMWatch'* ]]
[[ "$output" == *'Attempt 3: Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'Attempt 1: VMWatch process exited'* ]]
[[ "$output" == *'Attempt 3: VMWatch process exited'* ]]
@ -313,8 +314,8 @@ teardown(){
echo "$output"
echo "$status_file"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'Attempt 1: VMWatch process started'* ]]
[[ "$output" == *'Attempt 3: VMWatch process started'* ]]
[[ "$output" == *'Attempt 1: Started VMWatch'* ]]
[[ "$output" == *'Attempt 3: Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'Attempt 1: VMWatch process exited'* ]]
[[ "$output" == *'Attempt 3: VMWatch process exited'* ]]
@ -377,7 +378,7 @@ teardown(){
echo "$output"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'Invoking: /var/lib/waagent/Extension/bin/applicationhealth-shim disable'* ]]
@ -405,12 +406,13 @@ teardown(){
echo "$output"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'Invoking: /var/lib/waagent/Extension/bin/applicationhealth-shim uninstall'* ]]
[[ "$output" == *'applicationhealth-extension process terminated'* ]]
[[ "$output" == *'operation=uninstall seq=0 path=/var/lib/waagent/apphealth event=uninstalled'* ]]
any_regex_pattern="[[:digit:]|[:space:]|[:alpha:]|[:punct:]]"
assert_line --regexp "operation=uninstall seq=0 path=/var/lib/waagent/apphealth ${any_regex_pattern}* event=\"Handler successfully uninstalled\""
}
@test "handler command: enable - Graceful Shutdown - vm watch killed when Apphealth is killed gracefully with SIGTERM" {
@ -430,7 +432,7 @@ teardown(){
run start_container
echo "$output"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'event="Received shutdown request"'* ]]
@ -455,7 +457,7 @@ teardown(){
run start_container
echo "$output"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'event="Received shutdown request"'* ]]
@ -483,7 +485,7 @@ teardown(){
shutdown_log="$(container_read_file /var/log/azure/Extension/force-kill-extension.txt)"
echo "$shutdown_log"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$shutdown_log" == *'Successfully killed the apphealth extension'* ]]
@ -509,7 +511,7 @@ teardown(){
echo "$output"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'VMWatch process started'* ]]
[[ "$output" == *'Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'--memory-limit-bytes 40000000'* ]]
}
@ -546,8 +548,8 @@ teardown(){
echo "$output"
echo "$status_file"
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'Attempt 1: VMWatch process started'* ]]
[[ "$output" == *'Attempt 3: VMWatch process started'* ]]
[[ "$output" == *'Attempt 1: Started VMWatch'* ]]
[[ "$output" == *'Attempt 3: Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
[[ "$output" == *'Attempt 1: VMWatch process exited'* ]]
[[ "$output" == *'Attempt 3: VMWatch process exited'* ]]
@ -594,7 +596,7 @@ teardown(){
[[ "$avg_cpu" == *'PASS'* ]]
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'Attempt 1: VMWatch process started'* ]]
[[ "$output" == *'Attempt 1: Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
verify_substatus_item "$status_file" AppHealthStatus success "Application found to be healthy"
@ -637,7 +639,7 @@ teardown(){
[[ "$avg_cpu" == *'PASS'* ]]
[[ "$output" == *'Setup VMWatch command: /var/lib/waagent/Extension/bin/VMWatch/vmwatch_linux_amd64'* ]]
[[ "$output" == *'Attempt 1: VMWatch process started'* ]]
[[ "$output" == *'Attempt 1: Started VMWatch'* ]]
[[ "$output" == *'VMWatch is running'* ]]
verify_substatus_item "$status_file" AppHealthStatus success "Application found to be healthy"

Просмотреть файл

@ -1,7 +1,8 @@
#!/usr/bin/env bats
load ../test_helper
setup() {
load "../test_helper"
}
@test "meta: docker is installed" {
run docker version
echo "$output">&2

Просмотреть файл

@ -6,20 +6,15 @@ TEST_CONTAINER=test
certs_dir="$BATS_TEST_DIRNAME/certs"
# This function builds a Docker image for testing purposes.
# If the image already exists, a random number is appended to the name.
# a unique name is needed to avoid conflicts with other tests while running in parallel.
# This function builds a Docker image for testing purposes, if it already doesn't exist.
build_docker_image() {
# Generate a base name for the image
BASE_IMAGE_NAME=$IMAGE
# Loop until we find a unique image name
while [ -n "$(docker images -q $IMAGE)" ]; do
# Append the counter to the base image name
IMAGE="${BASE_IMAGE_NAME}_$RANDOM"
done
# Check if the image already exists
if [ -z "$(docker images -q $IMAGE)" ]; then
echo "Building test image $IMAGE..."
docker build -q -f $DOCKERFILE -t $IMAGE . 1>&2
else
echo "Test image $IMAGE already exists. Skipping build."
fi
}
in_tmp_container() {
@ -29,7 +24,6 @@ in_tmp_container() {
cleanup() {
echo "Cleaning up...">&2
rm_container
rm_image
}
rm_container() {
@ -185,25 +179,26 @@ copy_config() { # places specified settings file ($1) into container as 0.settin
}
# first argument is the string containing healthextension logs separated by newline
# it also expects the time={time in TZ format} version={version} to be in each log line
# it also expects the time={time in TZ format} level... to be in each log line
# second argument is an array of expected time difference (in seconds) between previous log
# for example: [5,10] means that the expected time difference between second log and first log is 5 seconds
# and time difference between third log and second log is 10 seconds
verify_state_change_timestamps() {
expectedTimeDifferences="$2"
regex='time=(.*) version=(.*)'
regex='time=([^[:space:]]*)' # regex to extract time from log line, will select everything until a space is found
prevDate=""
index=0
while IFS=$'\n' read -ra enableLogs; do
for i in "${!enableLogs[@]}"; do
[[ $enableLogs[index] =~ $regex ]]
currentDate=${BASH_REMATCH[1]}
if [[ ! -z "$prevDate" ]]; then
diff=$(( $(date -d "${BASH_REMATCH[1]}" "+%s") - $(date -d "$prevDate" "+%s") ))
diff=$(( $(date -d "$currentDate" "+%s") - $(date -d "$prevDate" "+%s") ))
echo "Actual time difference is: $diff and expected is: ${expectedTimeDifferences[$index-1]}"
[[ "$diff" -ge "${expectedTimeDifferences[$index-1]}" ]]
fi
index=$index+1
prevDate=${BASH_REMATCH[1]}
prevDate=$currentDate
done
done <<< "$1"
}
@ -289,3 +284,10 @@ kill_apphealth_extension_gracefully() {
# echo "Printing the process list after killing the applicationhealth extension"
ps -ef | grep -e "applicationhealth-extension" -e "vmwatch_linux_amd64" | grep -v grep
}
_load_bats_libs() {
export BATS_LIB_PATH=${CUSTOM_BATS_LIB_PATH:-"/usr/lib:/usr/local/lib/node_modules"}
echo "BATS_LIB_PATH: $BATS_LIB_PATH"
bats_load_library bats-support
bats_load_library bats-assert
}

Просмотреть файл

@ -0,0 +1,28 @@
package handlerenv
import (
"encoding/json"
"github.com/Azure/applicationhealth-extension-linux/internal/manifest"
"github.com/Azure/azure-extension-platform/pkg/handlerenv"
)
type HandlerEnvironment struct {
handlerenv.HandlerEnvironment
}
func (he *HandlerEnvironment) String() string {
env, _ := json.MarshalIndent(he, "", " ")
return string(env)
}
func GetHandlerEnviroment() (he *HandlerEnvironment, _ error) {
em, err := manifest.GetExtensionManifest()
if err != nil {
return nil, err
}
env, _ := handlerenv.GetHandlerEnvironment(em.Name(), em.Version)
return &HandlerEnvironment{
HandlerEnvironment: *env,
}, err
}

Просмотреть файл

@ -0,0 +1,96 @@
package manifest
import (
"encoding/xml"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/Azure/azure-extension-platform/pkg/utils"
)
// manifestFileName is the name of the manifest file.
const (
manifestFileName = "manifest.xml"
)
// GetDirFunc is a function type that returns a directory path and an error.
type GetDirFunc func() (string, error)
var (
// Set a package-level variable for the directory function
getDir GetDirFunc = utils.GetCurrentProcessWorkingDir
)
// ExtensionManifest represents the structure of an extension manifest.
type ExtensionManifest struct {
ProviderNameSpace string `xml:"ProviderNameSpace"`
Type string `xml:"Type"`
Version string `xml:"Version"`
Label string `xml:"Label"`
HostingResources string `xml:"HostingResources"`
MediaLink string `xml:"MediaLink"`
Description string `xml:"Description"`
IsInternalExtension bool `xml:"IsInternalExtension"`
IsJsonExtension bool `xml:"IsJsonExtension"`
SupportedOS string `xml:"SupportedOS"`
CompanyName string `xml:"CompanyName"`
}
// Name returns the formatted name of the extension manifest.
func (em *ExtensionManifest) Name() string {
return fmt.Sprintf("%s.%s", em.ProviderNameSpace, em.Type)
}
// GetExtensionManifest retrieves the extension manifest from the specified directory.
// If getDir is nil, it uses the current process working directory.
// It returns the extension manifest and an error, if any.
func GetExtensionManifest() (*ExtensionManifest, error) {
dir, err := getDir()
if err != nil {
return nil, err
}
fp, err := findManifestFilePath(dir)
if err != nil {
return nil, err
}
file, err := os.Open(fp)
if err != nil {
return nil, err
}
defer file.Close()
decoder := xml.NewDecoder(file)
var manifest ExtensionManifest
err = decoder.Decode(&manifest)
if err != nil {
return nil, err
}
return &manifest, nil
}
// findManifestFilePath finds the path of the manifest file in the specified directory.
// It returns the path and an error, if any.
func findManifestFilePath(dir string) (string, error) {
var (
paths = []string{
filepath.Join(dir, manifestFileName), // this level (i.e. executable is in [EXT_NAME]/.)
filepath.Join(dir, "..", manifestFileName), // one up (i.e. executable is in [EXT_NAME]/bin/.)
}
)
for _, p := range paths {
_, err := os.ReadFile(p)
if err != nil && !os.IsNotExist(err) {
return "", fmt.Errorf("cannot read file at path %s: %v", p, err)
} else if err == nil {
return p, nil
}
}
return "", fmt.Errorf("cannot find HandlerEnvironment at paths: %s", strings.Join(paths, ", "))
}

Просмотреть файл

@ -0,0 +1,80 @@
package manifest
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func Test_ExtensionManifestVersion(t *testing.T) {
// Save the original function and restore it after the test
originalGetDir := getDir
defer func() { getDir = originalGetDir }()
currVersion := "2.0.10"
expectedManifest := ExtensionManifest{
ProviderNameSpace: "Microsoft.ManagedServices",
Type: "ApplicationHealthLinux",
Version: currVersion,
Label: "Microsoft Azure Application Health Extension for Linux Virtual Machines",
HostingResources: "VmRole",
MediaLink: "",
Description: "Microsoft Azure Application Health Extension is an extension installed on a VM to periodically determine configured application health.",
IsInternalExtension: true,
IsJsonExtension: true,
SupportedOS: "Linux",
CompanyName: "Microsoft",
}
// Override the getDir function to return a mock directory
getDir = func() (string, error) {
return "../../misc", nil
}
currentManifest, err := GetExtensionManifest()
require.Nil(t, err)
require.Equal(t, expectedManifest.Type, currentManifest.Type)
require.Equal(t, expectedManifest.Label, currentManifest.Label)
require.Equal(t, expectedManifest.HostingResources, currentManifest.HostingResources)
require.Equal(t, expectedManifest.MediaLink, currentManifest.MediaLink)
require.Equal(t, expectedManifest.Description, currentManifest.Description)
require.Equal(t, expectedManifest.IsInternalExtension, currentManifest.IsInternalExtension)
require.Equal(t, expectedManifest.IsJsonExtension, currentManifest.IsJsonExtension)
require.Equal(t, expectedManifest.SupportedOS, currentManifest.SupportedOS)
require.Equal(t, expectedManifest.CompanyName, currentManifest.CompanyName)
}
func Test_FindManifestFilePath(t *testing.T) {
var (
manifestFileName = "manifest.xml"
src = "../../misc/" // Replace with the actual directory path
dst = "/tmp/lib/waagent/Microsoft.ManagedServices-ApplicationHealthLinux/" // Replace with the actual directory path
)
err := copyFileNewDirectory(src, dst, manifestFileName)
require.NoError(t, err, "failed to copy manifest file to a new directory")
defer os.RemoveAll(dst)
// Test case 1: Manifest file exists in the current directory
workingDir := filepath.Join(dst, "bin")
path, err := findManifestFilePath(workingDir)
require.NoError(t, err)
require.Equalf(t, filepath.Join(dst, manifestFileName), path, "failed to find manifest file from bin directory: %s", workingDir)
// Test case 2: Manifest file exists in the parent directory
workingDir = filepath.Join(workingDir, "..")
path, err = findManifestFilePath(workingDir)
require.NoError(t, err)
require.Equal(t, filepath.Join(dst, manifestFileName), path, "failed to find manifest file from parent directory: %s", workingDir)
os.Remove(filepath.Join(dst, manifestFileName))
// Test case 3: Manifest file does not exist
workingDir = filepath.Join(dst, "bin")
path, err = findManifestFilePath(workingDir)
require.Error(t, err)
require.EqualError(t, err, fmt.Sprintf("cannot find HandlerEnvironment at paths: %s", strings.Join([]string{filepath.Join(workingDir, manifestFileName), filepath.Join(workingDir, "..", manifestFileName)}, ", ")))
require.Equal(t, "", path)
}

Просмотреть файл

@ -0,0 +1,47 @@
package manifest
import (
"fmt"
"io"
"os"
"path/filepath"
)
func copyFileNewDirectory(src, dst, fileName string) error {
// This function is used to copy the manifest file to a new directory
// The function is not implemented as it is not relevant to the test case
if src == "" || dst == "" {
return fmt.Errorf("invalid source or destination path")
}
src, err := filepath.Abs(src)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(dst), 0755)
if err != nil {
return err
}
// Open the source file
srcFile, err := os.Open(filepath.Join(src, fileName))
if err != nil {
return err
}
defer srcFile.Close()
// Create the destination file
dstFile, err := os.Create(filepath.Join(dst, fileName))
if err != nil {
return err
}
defer dstFile.Close()
// Copy the contents of the source file to the destination file
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
}
return nil
}

Просмотреть файл

@ -0,0 +1,82 @@
package telemetry
import (
"runtime"
"github.com/Azure/azure-extension-platform/pkg/extensionevents"
"github.com/go-kit/log"
)
type EventLevel string
type EventTask string
const (
EventLevelCritical EventLevel = "Critical"
EventLevelError EventLevel = "Error"
EventLevelWarning EventLevel = "Warning"
EventLevelVerbose EventLevel = "Verbose"
EventLevelInfo EventLevel = "Informational"
)
const (
MainTask EventTask = "Main"
AppHealthTask EventTask = "AppHealth"
AppHealthProbeTask EventTask = "AppHealth-HealthProbe"
ReportStatusTask EventTask = "ReportStatus"
ReportHeatBeatTask EventTask = "CheckHealthAndReportHeartBeat"
StartVMWatchTask EventTask = "StartVMWatchIfApplicable"
StopVMWatchTask EventTask = "OnExited"
SetupVMWatchTask EventTask = "SetupVMWatchProcess"
KillVMWatchTask EventTask = "KillVMWatchIfApplicable"
)
type LogFunc func(logger log.Logger, keyvals ...interface{})
type LogEventFunc func(logger log.Logger, level EventLevel, taskName EventTask, message string, keyvals ...interface{})
type TelemetryEventSender struct {
eem *extensionevents.ExtensionEventManager
}
func NewTelemetryEventSender(eem *extensionevents.ExtensionEventManager) *TelemetryEventSender {
return &TelemetryEventSender{
eem: eem,
}
}
// sendEvent sends a telemetry event with the specified level, task name, and message.
func (t *TelemetryEventSender) sendEvent(level EventLevel, taskName EventTask, message string) {
switch level {
case EventLevelCritical:
t.eem.LogCriticalEvent(string(taskName), message)
case EventLevelError:
t.eem.LogErrorEvent(string(taskName), message)
case EventLevelWarning:
t.eem.LogWarningEvent(string(taskName), message)
case EventLevelVerbose:
t.eem.LogVerboseEvent(string(taskName), message)
case EventLevelInfo:
t.eem.LogInformationalEvent(string(taskName), message)
default:
return
}
}
// LogStdOutAndEventWithSender is a higher-order function that returns a LogEventFunc.
// It logs the event to the provided logger and sends the event to the specified sender.
// If the taskName is empty, it automatically determines the caller's function name as the taskName.
// The event level, task name, and message are appended to the keyvals slice.
// Finally, it calls the sender's sendEvent method to send the event.
func LogStdOutAndEventWithSender(sender *TelemetryEventSender) LogEventFunc {
return func(logger log.Logger, level EventLevel, taskName EventTask, message string, keyvals ...interface{}) {
if taskName == "" {
pc, _, _, _ := runtime.Caller(1)
callerName := runtime.FuncForPC(pc).Name()
taskName = EventTask(callerName)
}
keyvals = append(keyvals, "level", level, "task", taskName, "event", message)
logger.Log(keyvals...)
// logger.Log("eventLevel", level, "eventTask", taskName, "event", message)
(*sender).sendEvent(level, taskName, message)
}
}

Просмотреть файл

@ -6,12 +6,14 @@ import (
"strings"
"time"
"github.com/go-kit/kit/log"
"github.com/Azure/applicationhealth-extension-linux/internal/handlerenv"
"github.com/Azure/applicationhealth-extension-linux/internal/telemetry"
"github.com/go-kit/log"
"github.com/pkg/errors"
)
type cmdFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (msg string, err error)
type preFunc func(ctx *log.Context, seqNum int) error
type cmdFunc func(lg log.Logger, hEnv *handlerenv.HandlerEnvironment, seqNum int) (msg string, err error)
type preFunc func(lg log.Logger, seqNum int) error
type cmd struct {
f cmdFunc // associated function
@ -39,31 +41,31 @@ var (
}
)
func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) {
ctx.Log("event", "noop")
func noop(lg log.Logger, h *handlerenv.HandlerEnvironment, seqNum int) (string, error) {
lg.Log("event", "noop")
return "", nil
}
func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) {
func install(lg log.Logger, h *handlerenv.HandlerEnvironment, seqNum int) (string, error) {
if err := os.MkdirAll(dataDir, 0755); err != nil {
return "", errors.Wrap(err, "failed to create data dir")
}
ctx.Log("event", "created data dir", "path", dataDir)
ctx.Log("event", "installed")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Created data dir", "path", dataDir)
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Handler successfully installed")
return "", nil
}
func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) {
func uninstall(lg log.Logger, h *handlerenv.HandlerEnvironment, seqNum int) (string, error) {
{ // a new context scope with path
ctx = ctx.With("path", dataDir)
ctx.Log("event", "removing data dir", "path", dataDir)
lg = log.With(lg, "path", dataDir)
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Removing data dir")
if err := os.RemoveAll(dataDir); err != nil {
return "", errors.Wrap(err, "failed to delete data dir")
}
ctx.Log("event", "removed data dir")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Successfully removed data dir")
}
ctx.Log("event", "uninstalled")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Handler successfully uninstalled")
return "", nil
}
@ -75,14 +77,17 @@ var (
errTerminated = errors.New("Application health process terminated")
)
func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) {
func enable(lg log.Logger, h *handlerenv.HandlerEnvironment, seqNum int) (string, error) {
// parse the extension handler settings (not available prior to 'enable')
cfg, err := parseAndValidateSettings(ctx, h.HandlerEnvironment.ConfigFolder)
cfg, err := parseAndValidateSettings(lg, h.ConfigFolder)
if err != nil {
return "", errors.Wrap(err, "failed to get configuration")
}
probe := NewHealthProbe(ctx, &cfg)
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Successfully parsed and validated settings")
sendTelemetry(lg, telemetry.EventLevelVerbose, telemetry.AppHealthTask, fmt.Sprintf("HandlerSettings = %s", cfg))
probe := NewHealthProbe(lg, &cfg)
var (
intervalBetweenProbesInMs = time.Duration(cfg.intervalInSeconds()) * time.Millisecond * 1000
numberOfProbes = cfg.numberOfProbes()
@ -99,17 +104,17 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
)
if !honorGracePeriod {
ctx.Log("event", "Grace period not set")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Grace period not set")
} else {
ctx.Log("event", fmt.Sprintf("Grace period set to %v", gracePeriodInSeconds))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, fmt.Sprintf("Grace period set to %v", gracePeriodInSeconds))
}
ctx.Log("event", fmt.Sprintf("VMWatch settings: %#v", vmWatchSettings))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, fmt.Sprintf("VMWatch settings: %s", vmWatchSettings))
if vmWatchSettings == nil || vmWatchSettings.Enabled == false {
ctx.Log("event", fmt.Sprintf("VMWatch is disabled, not starting process."))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.StartVMWatchTask, "VMWatch is disabled, not starting process.")
} else {
vmWatchResult = VMWatchResult{Status: NotRunning, Error: nil}
go executeVMWatch(ctx, vmWatchSettings, h, vmWatchResultChannel)
go executeVMWatch(lg, vmWatchSettings, h, vmWatchResultChannel)
}
// The committed health status (the state written to the status file) initially does not have a state
@ -124,13 +129,15 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
// 2. A valid health state is observed numberOfProbes consecutive times
for {
startTime := time.Now()
probeResponse, err := probe.evaluate(ctx)
probeResponse, err := probe.evaluate(lg)
state := probeResponse.ApplicationHealthState
if err != nil {
ctx.Log("error", err)
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask,
fmt.Sprintf("Error evaluating health probe: %v", err), "error", err)
}
if shutdown {
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, "Shutting down AppHealth Extension Gracefully")
return "", errTerminated
}
@ -143,14 +150,16 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
if !ok {
vmWatchResult = VMWatchResult{Status: Failed, Error: errors.New("VMWatch channel has closed, unknown error")}
} else if result.Status == Running {
ctx.Log("event", "VMWatch is running")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.ReportHeatBeatTask, "VMWatch is running")
} else if result.Status == Failed {
ctx.Log("error", vmWatchResult.GetMessage())
sendTelemetry(lg, telemetry.EventLevelError, telemetry.ReportHeatBeatTask, vmWatchResult.GetMessage())
} else if result.Status == NotRunning {
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.ReportHeatBeatTask, "VMWatch is not running")
}
default:
if vmWatchResult.Status == Running && time.Since(timeOfLastVMWatchLog) >= 60*time.Second {
timeOfLastVMWatchLog = time.Now()
ctx.Log("event", "VMWatch is running")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.ReportHeatBeatTask, "VMWatch is running")
}
}
@ -159,7 +168,7 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
numConsecutiveProbes++
// Log stage changes and also reset consecutive count to 1 as a new state was observed
} else {
ctx.Log("event", "Health state changed to "+strings.ToLower(string(state)))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, fmt.Sprintf("Health state changed to %s", strings.ToLower(string(state))))
numConsecutiveProbes = 1
prevState = state
}
@ -168,7 +177,7 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
timeElapsed := time.Now().Sub(gracePeriodStartTime)
// If grace period expires, application didn't initialize on time
if timeElapsed >= gracePeriodInSeconds {
ctx.Log("event", fmt.Sprintf("No longer honoring grace period - expired. Time elapsed = %v", timeElapsed))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, fmt.Sprintf("No longer honoring grace period - expired. Time elapsed = %v", timeElapsed))
honorGracePeriod = false
state = probe.healthStatusAfterGracePeriodExpires()
prevState = probe.healthStatusAfterGracePeriodExpires()
@ -176,11 +185,11 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
committedState = Empty
// If grace period has not expired, check if we have consecutive valid probes
} else if (numConsecutiveProbes == numberOfProbes) && (state != probe.healthStatusAfterGracePeriodExpires()) {
ctx.Log("event", fmt.Sprintf("No longer honoring grace period - successful probes. Time elapsed = %v", timeElapsed))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, fmt.Sprintf("No longer honoring grace period - successful probes. Time elapsed = %v", timeElapsed))
honorGracePeriod = false
// Application will be in Initializing state since we have not received consecutive valid health states
} else {
ctx.Log("event", fmt.Sprintf("Honoring grace period. Time elapsed = %v", timeElapsed))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, fmt.Sprintf("Honoring grace period. Time elapsed = %v", timeElapsed))
state = Initializing
}
}
@ -188,7 +197,7 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
if (numConsecutiveProbes == numberOfProbes) || (committedState == Empty) {
if state != committedState {
committedState = state
ctx.Log("event", fmt.Sprintf("Committed health state is %s", strings.ToLower(string(committedState))))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthTask, fmt.Sprintf("Committed health state is %s", strings.ToLower(string(committedState))))
}
// Only reset if we've observed consecutive probes in order to preserve previous observations when handling grace period
if numConsecutiveProbes == numberOfProbes {
@ -210,16 +219,22 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error)
customMetricsStatusType = StatusSuccess
}
substatuses = append(substatuses, NewSubstatus(SubstatusKeyNameCustomMetrics, customMetricsStatusType, probeResponse.CustomMetrics))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.ReportStatusTask,
fmt.Sprintf("Reporting CustomMetric Substatus with status: %s , message: %s",
customMetricsStatusType, probeResponse.CustomMetrics))
}
// VMWatch substatus should only be displayed when settings are present
if vmWatchSettings != nil {
substatuses = append(substatuses, NewSubstatus(SubstatusKeyNameVMWatch, vmWatchResult.Status.GetStatusType(), vmWatchResult.GetMessage()))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.ReportStatusTask,
fmt.Sprintf("Reporting VMWatch Substatus with status: %s, message: %s",
vmWatchResult.Status.GetStatusType(), vmWatchResult.GetMessage()))
}
err = reportStatusWithSubstatuses(ctx, h, seqNum, StatusSuccess, "enable", statusMessage, substatuses)
err = reportStatusWithSubstatuses(lg, h, seqNum, StatusSuccess, "enable", statusMessage, substatuses)
if err != nil {
ctx.Log("error", err)
sendTelemetry(lg, telemetry.EventLevelError, telemetry.ReportStatusTask, fmt.Sprintf("Error while trying to report health status: %v", err), "error", err)
}
endTime := time.Now()

Просмотреть файл

@ -1,84 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// HandlerEnvFileName is the file name of the Handler Environment as placed by the
// Azure Linux Guest Agent.
const HandlerEnvFileName = "HandlerEnvironment.json"
// HandlerEnvironment describes the handler environment configuration presented
// to the extension handler by the Azure Linux Guest Agent.
type HandlerEnvironment struct {
Version float64 `json:"version"`
Name string `json:"name"`
HandlerEnvironment struct {
HeartbeatFile string `json:"heartbeatFile"`
StatusFolder string `json:"statusFolder"`
ConfigFolder string `json:"configFolder"`
LogFolder string `json:"logFolder"`
EventsFolder string `json:"eventsFolder"`
EventsFolderPreview string `json:"eventsFolder_preview"`
DeploymentID string `json:"deploymentid"`
RoleName string `json:"rolename"`
Instance string `json:"instance"`
HostResolverAddress string `json:"hostResolverAddress"`
}
}
// GetHandlerEnv locates the HandlerEnvironment.json file by assuming it lives
// next to or one level above the extension handler (read: this) executable,
// reads, parses and returns it.
func GetHandlerEnv() (he HandlerEnvironment, _ error) {
dir, err := scriptDir()
if err != nil {
return he, fmt.Errorf("vmextension: cannot find base directory of the running process: %v", err)
}
paths := []string{
filepath.Join(dir, HandlerEnvFileName), // this level (i.e. executable is in [EXT_NAME]/.)
filepath.Join(dir, "..", HandlerEnvFileName), // one up (i.e. executable is in [EXT_NAME]/bin/.)
}
var b []byte
for _, p := range paths {
o, err := ioutil.ReadFile(p)
if err != nil && !os.IsNotExist(err) {
return he, fmt.Errorf("vmextension: error examining HandlerEnvironment at '%s': %v", p, err)
} else if err == nil {
b = o
break
}
}
if b == nil {
return he, fmt.Errorf("vmextension: Cannot find HandlerEnvironment at paths: %s", strings.Join(paths, ", "))
}
return ParseHandlerEnv(b)
}
// scriptDir returns the absolute path of the running process.
func scriptDir() (string, error) {
p, err := filepath.Abs(os.Args[0])
if err != nil {
return "", err
}
return filepath.Dir(p), nil
}
// ParseHandlerEnv parses the
// /var/lib/waagent/[extension]/HandlerEnvironment.json format.
func ParseHandlerEnv(b []byte) (he HandlerEnvironment, _ error) {
var hf []HandlerEnvironment
if err := json.Unmarshal(b, &hf); err != nil {
return he, fmt.Errorf("vmextension: failed to parse handler env: %v", err)
}
if len(hf) != 1 {
return he, fmt.Errorf("vmextension: expected 1 config in parsed HandlerEnvironment, found: %v", len(hf))
}
return hf[0], nil
}

Просмотреть файл

@ -6,8 +6,9 @@ import (
"os"
"path/filepath"
"github.com/Azure/applicationhealth-extension-linux/internal/telemetry"
"github.com/Azure/azure-docker-extension/pkg/vmextension"
"github.com/go-kit/kit/log"
"github.com/go-kit/log"
"github.com/pkg/errors"
)
@ -26,6 +27,11 @@ type handlerSettings struct {
protectedSettings
}
func (s handlerSettings) String() string {
settings, _ := json.MarshalIndent(s, "", " ")
return string(settings)
}
func (s *handlerSettings) protocol() string {
return s.publicSettings.Protocol
}
@ -106,6 +112,11 @@ type vmWatchSettings struct {
DisableConfigReader bool `json:"disableConfigReader,boolean"`
}
func (v *vmWatchSettings) String() string {
setting, _ := json.MarshalIndent(v, "", " ")
return string(setting)
}
// publicSettings is the type deserialized from public configuration section of
// the extension handler. This should be in sync with publicSettingsSchema.
type publicSettings struct {
@ -125,31 +136,30 @@ type protectedSettings struct {
// parseAndValidateSettings reads configuration from configFolder, decrypts it,
// runs JSON-schema and logical validation on it and returns it back.
func parseAndValidateSettings(ctx *log.Context, configFolder string) (h handlerSettings, _ error) {
ctx.Log("event", "reading configuration")
func parseAndValidateSettings(lg log.Logger, configFolder string) (h handlerSettings, _ error) {
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.MainTask, "Reading configuration")
pubJSON, protJSON, err := readSettings(configFolder)
if err != nil {
return h, err
}
ctx.Log("event", "read configuration")
ctx.Log("event", "validating json schema")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.MainTask, "validating json schema")
if err := validateSettingsSchema(pubJSON, protJSON); err != nil {
return h, errors.Wrap(err, "json validation error")
}
ctx.Log("event", "json schema valid")
ctx.Log("event", "parsing configuration json")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.MainTask, "json schema valid")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.MainTask, "parsing configuration json")
if err := vmextension.UnmarshalHandlerSettings(pubJSON, protJSON, &h.publicSettings, &h.protectedSettings); err != nil {
return h, errors.Wrap(err, "json parsing error")
}
ctx.Log("event", "parsed configuration json")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.MainTask, "parsed configuration json")
ctx.Log("event", "validating configuration logically")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.MainTask, "validating configuration logically")
if err := h.validate(); err != nil {
return h, errors.Wrap(err, "invalid configuration")
}
ctx.Log("event", "validated configuration")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.MainTask, "validated configuration")
return h, nil
}

Просмотреть файл

@ -70,36 +70,3 @@ func Test_unMarshalPublicSetting(t *testing.T) {
require.Equal(t, true, h.publicSettings.VMWatchSettings.Enabled)
require.Equal(t, "https://testxyz.azurefd.net/config/disable-switch-config.json", h.publicSettings.VMWatchSettings.GlobalConfigUrl)
}
func Test_ExtensionManifestVersion(t *testing.T) {
currVersion := "2.0.8"
expectedManifest := ExtensionManifest{
ProviderNameSpace: "Microsoft.ManagedServices",
Type: "ApplicationHealthLinux",
Version: currVersion,
Label: "Microsoft Azure Application Health Extension for Linux Virtual Machines",
HostingResources: "VmRole",
MediaLink: "",
Description: "Microsoft Azure Application Health Extension is an extension installed on a VM to periodically determine configured application health.",
IsInternalExtension: true,
IsJsonExtension: true,
SupportedOS: "Linux",
CompanyName: "Microsoft",
}
v := GetExtensionVersion()
require.Empty(t, v)
currentManifest, err := GetExtensionManifest("../misc/manifest.xml")
require.Nil(t, err)
require.Equal(t, expectedManifest.Version, currentManifest.Version)
require.Equal(t, expectedManifest.Type, currentManifest.Type)
require.Equal(t, expectedManifest.Label, currentManifest.Label)
require.Equal(t, expectedManifest.HostingResources, currentManifest.HostingResources)
require.Equal(t, expectedManifest.MediaLink, currentManifest.MediaLink)
require.Equal(t, expectedManifest.Description, currentManifest.Description)
require.Equal(t, expectedManifest.IsInternalExtension, currentManifest.IsInternalExtension)
require.Equal(t, expectedManifest.IsJsonExtension, currentManifest.IsJsonExtension)
require.Equal(t, expectedManifest.SupportedOS, currentManifest.SupportedOS)
require.Equal(t, expectedManifest.CompanyName, currentManifest.CompanyName)
}

Просмотреть файл

@ -12,7 +12,8 @@ import (
"net/url"
"github.com/go-kit/kit/log"
"github.com/Azure/applicationhealth-extension-linux/internal/telemetry"
"github.com/go-kit/log"
"github.com/pkg/errors"
)
@ -55,7 +56,7 @@ func (p HealthStatus) GetMessageForAppHealthStatus() string {
}
type HealthProbe interface {
evaluate(ctx *log.Context) (ProbeResponse, error)
evaluate(lg log.Logger) (ProbeResponse, error)
address() string
healthStatusAfterGracePeriodExpires() HealthStatus
}
@ -69,7 +70,7 @@ type HttpHealthProbe struct {
Address string
}
func NewHealthProbe(ctx *log.Context, cfg *handlerSettings) HealthProbe {
func NewHealthProbe(lg log.Logger, cfg *handlerSettings) HealthProbe {
var p HealthProbe
p = new(DefaultHealthProbe)
switch cfg.protocol() {
@ -77,20 +78,20 @@ func NewHealthProbe(ctx *log.Context, cfg *handlerSettings) HealthProbe {
p = &TcpHealthProbe{
Address: "localhost:" + strconv.Itoa(cfg.port()),
}
ctx.Log("event", "creating tcp probe targeting "+p.address())
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthProbeTask, fmt.Sprintf("Creating %s probe targeting %s", cfg.protocol(), p.address()))
case "http":
fallthrough
case "https":
p = NewHttpHealthProbe(cfg.protocol(), cfg.requestPath(), cfg.port())
ctx.Log("event", "creating "+cfg.protocol()+" probe targeting "+p.address())
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthProbeTask, fmt.Sprintf("Creating %s probe targeting %s", cfg.protocol(), p.address()))
default:
ctx.Log("event", "default settings without probe")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.AppHealthProbeTask, "Configuration not provided. Using default reporting.")
}
return p
}
func (p *TcpHealthProbe) evaluate(ctx *log.Context) (ProbeResponse, error) {
func (p *TcpHealthProbe) evaluate(lg log.Logger) (ProbeResponse, error) {
conn, err := net.DialTimeout("tcp", p.address(), 30*time.Second)
var probeResponse ProbeResponse
if err != nil {
@ -173,7 +174,7 @@ func NewHttpHealthProbe(protocol string, requestPath string, port int) *HttpHeal
return p
}
func (p *HttpHealthProbe) evaluate(ctx *log.Context) (ProbeResponse, error) {
func (p *HttpHealthProbe) evaluate(lg log.Logger) (ProbeResponse, error) {
req, err := http.NewRequest("GET", p.address(), nil)
var probeResponse ProbeResponse
if err != nil {
@ -209,7 +210,7 @@ func (p *HttpHealthProbe) evaluate(ctx *log.Context) (ProbeResponse, error) {
}
if err := probeResponse.validateCustomMetrics(); err != nil {
ctx.Log("error", err)
sendTelemetry(lg, telemetry.EventLevelError, telemetry.AppHealthProbeTask, err.Error(), "error", err)
}
if err := probeResponse.validateApplicationHealthState(); err != nil {
@ -240,7 +241,7 @@ func noRedirect(req *http.Request, via []*http.Request) error {
type DefaultHealthProbe struct {
}
func (p DefaultHealthProbe) evaluate(ctx *log.Context) (ProbeResponse, error) {
func (p DefaultHealthProbe) evaluate(lg log.Logger) (ProbeResponse, error) {
var probeResponse ProbeResponse
probeResponse.ApplicationHealthState = Healthy
return probeResponse, nil

Просмотреть файл

@ -8,7 +8,11 @@ import (
"strings"
"syscall"
"github.com/go-kit/kit/log"
"github.com/Azure/applicationhealth-extension-linux/internal/handlerenv"
"github.com/Azure/applicationhealth-extension-linux/internal/telemetry"
"github.com/Azure/applicationhealth-extension-linux/pkg/logging"
"github.com/Azure/azure-extension-platform/pkg/extensionevents"
"github.com/go-kit/log"
)
var (
@ -20,60 +24,69 @@ var (
// We need a reference to the command here so that we can cleanly shutdown VMWatch process
// when a shutdown signal is received
vmWatchCommand *exec.Cmd
eem *extensionevents.ExtensionEventManager
sendTelemetry telemetry.LogEventFunc
)
func main() {
ctx := log.NewContext(log.NewSyncLogger(log.NewLogfmtLogger(
os.Stdout))).With("time", log.DefaultTimestamp).With("version", VersionString())
logger := log.NewSyncLogger(log.NewLogfmtLogger(
os.Stdout))
logger = log.With(logger, "time", log.DefaultTimestamp)
logger = log.With(logger, "version", VersionString())
// parse command line arguments
cmd := parseCmd(os.Args)
ctx = ctx.With("operation", strings.ToLower(cmd.name))
logger = log.With(logger, "operation", strings.ToLower(cmd.name))
// subscribe to cleanly shutdown
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
ctx.Log("event", "Received shutdown request")
sendTelemetry(logger, telemetry.EventLevelInfo, telemetry.KillVMWatchTask, "Received shutdown request")
shutdown = true
err := killVMWatch(ctx, vmWatchCommand)
err := killVMWatch(logger, vmWatchCommand)
if err != nil {
ctx.Log("error", "error when killing vmwatch", err.Error())
sendTelemetry(logger, telemetry.EventLevelError, telemetry.KillVMWatchTask, fmt.Sprintf("Error when killing vmwatch process, error: %s", err.Error()))
}
}()
// parse extension environment
hEnv, err := GetHandlerEnv()
hEnv, err := handlerenv.GetHandlerEnviroment()
if err != nil {
ctx.Log("message", "failed to parse handlerenv", "error", err)
logger.Log("message", "failed to parse handlerenv", "error", err)
os.Exit(cmd.failExitCode)
}
seqNum, err := FindSeqNum(hEnv.HandlerEnvironment.ConfigFolder)
seqNum, err := FindSeqNum(hEnv.ConfigFolder)
if err != nil {
ctx.Log("messsage", "failed to find sequence number", "error", err)
logger.Log("message", "failed to find sequence number", "error", err)
}
ctx = ctx.With("seq", seqNum)
logger = log.With(logger, "seq", seqNum)
eem = extensionevents.New(logging.NewNopLogger(), &hEnv.HandlerEnvironment)
sendTelemetry = telemetry.LogStdOutAndEventWithSender(telemetry.NewTelemetryEventSender(eem))
// check sub-command preconditions, if any, before executing
ctx.Log("event", "start")
sendTelemetry(logger, telemetry.EventLevelInfo, telemetry.MainTask, fmt.Sprintf("Starting AppHealth Extension %s", GetExtensionVersion()))
sendTelemetry(logger, telemetry.EventLevelInfo, telemetry.MainTask, fmt.Sprintf("HandlerEnviroment = %s", hEnv))
if cmd.pre != nil {
ctx.Log("event", "pre-check")
if err := cmd.pre(ctx, seqNum); err != nil {
ctx.Log("event", "pre-check failed", "error", err)
logger.Log("event", "pre-check")
if err := cmd.pre(logger, seqNum); err != nil {
logger.Log("event", "pre-check failed", "error", err)
os.Exit(cmd.failExitCode)
}
}
// execute the subcommand
reportStatus(ctx, hEnv, seqNum, StatusTransitioning, cmd, "")
msg, err := cmd.f(ctx, hEnv, seqNum)
reportStatus(logger, hEnv, seqNum, StatusTransitioning, cmd, "")
msg, err := cmd.f(logger, hEnv, seqNum)
if err != nil {
ctx.Log("event", "failed to handle", "error", err)
reportStatus(ctx, hEnv, seqNum, StatusError, cmd, err.Error()+msg)
logger.Log("event", "failed to handle", "error", err)
reportStatus(logger, hEnv, seqNum, StatusError, cmd, err.Error()+msg)
os.Exit(cmd.failExitCode)
}
reportStatus(ctx, hEnv, seqNum, StatusSuccess, cmd, msg)
ctx.Log("event", "end")
reportStatus(logger, hEnv, seqNum, StatusSuccess, cmd, msg)
sendTelemetry(logger, telemetry.EventLevelInfo, telemetry.MainTask, fmt.Sprintf("Finished execution of AppHealth Extension %s", GetExtensionVersion()))
}
// parseCmd looks at os.Args and parses the subcommand. If it is invalid,

Просмотреть файл

@ -1,7 +1,9 @@
package main
import (
"github.com/go-kit/kit/log"
"github.com/Azure/applicationhealth-extension-linux/internal/handlerenv"
"github.com/Azure/applicationhealth-extension-linux/internal/telemetry"
"github.com/go-kit/log"
"github.com/pkg/errors"
)
@ -10,26 +12,26 @@ import (
// status.
//
// If an error occurs reporting the status, it will be logged and returned.
func reportStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t StatusType, c cmd, msg string) error {
func reportStatus(lg log.Logger, hEnv *handlerenv.HandlerEnvironment, seqNum int, t StatusType, c cmd, msg string) error {
if !c.shouldReportStatus {
ctx.Log("status", "not reported for operation (by design)")
lg.Log("status", "not reported for operation (by design)")
return nil
}
s := NewStatus(t, c.name, statusMsg(c, t, msg))
if err := s.Save(hEnv.HandlerEnvironment.StatusFolder, seqNum); err != nil {
ctx.Log("event", "failed to save handler status", "error", err)
if err := s.Save(hEnv.StatusFolder, seqNum); err != nil {
lg.Log("event", "failed to save handler status", "error", err)
return errors.Wrap(err, "failed to save handler status")
}
return nil
}
func reportStatusWithSubstatuses(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t StatusType, op string, msg string, substatuses []SubstatusItem) error {
func reportStatusWithSubstatuses(lg log.Logger, hEnv *handlerenv.HandlerEnvironment, seqNum int, t StatusType, op string, msg string, substatuses []SubstatusItem) error {
s := NewStatus(t, op, msg)
for _, substatus := range substatuses {
s.AddSubstatusItem(substatus)
}
if err := s.Save(hEnv.HandlerEnvironment.StatusFolder, seqNum); err != nil {
ctx.Log("event", "failed to save handler status", "error", err)
if err := s.Save(hEnv.StatusFolder, seqNum); err != nil {
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.ReportStatusTask, "failed to save handler status", "error", err.Error())
return errors.Wrap(err, "failed to save handler status")
}
return nil

Просмотреть файл

@ -6,7 +6,9 @@ import (
"path/filepath"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/log"
"github.com/Azure/applicationhealth-extension-linux/internal/handlerenv"
"github.com/stretchr/testify/require"
)
@ -22,10 +24,10 @@ func Test_statusMsg(t *testing.T) {
}
func Test_reportStatus_fails(t *testing.T) {
fakeEnv := HandlerEnvironment{}
fakeEnv.HandlerEnvironment.StatusFolder = "/non-existing/dir/"
fakeEnv := &handlerenv.HandlerEnvironment{}
fakeEnv.StatusFolder = "/non-existing/dir/"
err := reportStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusSuccess, cmdEnable, "")
err := reportStatus(log.NewNopLogger(), fakeEnv, 1, StatusSuccess, cmdEnable, "")
require.NotNil(t, err)
require.Contains(t, err.Error(), "failed to save handler status")
}
@ -35,10 +37,10 @@ func Test_reportStatus_fileExists(t *testing.T) {
require.Nil(t, err)
defer os.RemoveAll(tmpDir)
fakeEnv := HandlerEnvironment{}
fakeEnv.HandlerEnvironment.StatusFolder = tmpDir
fakeEnv := &handlerenv.HandlerEnvironment{}
fakeEnv.StatusFolder = tmpDir
require.Nil(t, reportStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusError, cmdEnable, "FOO ERROR"))
require.Nil(t, reportStatus(log.NewNopLogger(), fakeEnv, 1, StatusError, cmdEnable, "FOO ERROR"))
path := filepath.Join(tmpDir, "1.status")
b, err := ioutil.ReadFile(path)
@ -52,9 +54,9 @@ func Test_reportStatus_checksIfShouldBeReported(t *testing.T) {
require.Nil(t, err)
defer os.RemoveAll(tmpDir)
fakeEnv := HandlerEnvironment{}
fakeEnv.HandlerEnvironment.StatusFolder = tmpDir
require.Nil(t, reportStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 2, StatusSuccess, c, ""))
fakeEnv := &handlerenv.HandlerEnvironment{}
fakeEnv.StatusFolder = tmpDir
require.Nil(t, reportStatus(log.NewNopLogger(), fakeEnv, 2, StatusSuccess, c, ""))
fp := filepath.Join(tmpDir, "2.status")
_, err = os.Stat(fp) // check if the .status file is there

Просмотреть файл

@ -13,10 +13,13 @@ import (
"syscall"
"time"
"github.com/go-kit/log"
"github.com/Azure/applicationhealth-extension-linux/internal/handlerenv"
"github.com/Azure/applicationhealth-extension-linux/internal/telemetry"
"github.com/containerd/cgroups/v3"
"github.com/containerd/cgroups/v3/cgroup1"
"github.com/containerd/cgroups/v3/cgroup2"
"github.com/go-kit/kit/log"
"github.com/opencontainers/runtime-spec/specs-go"
)
@ -75,12 +78,12 @@ func (r *VMWatchResult) GetMessage() string {
// We will setup and execute VMWatch as a separate process. Ideally VMWatch should run indefinitely,
// but as a best effort we will attempt at most 3 times to run the process
func executeVMWatch(ctx *log.Context, s *vmWatchSettings, hEnv HandlerEnvironment, vmWatchResultChannel chan VMWatchResult) {
func executeVMWatch(lg log.Logger, s *vmWatchSettings, hEnv *handlerenv.HandlerEnvironment, vmWatchResultChannel chan VMWatchResult) {
var vmWatchErr error
defer func() {
if r := recover(); r != nil {
vmWatchErr = fmt.Errorf("%w\n Additonal Details: %+v", vmWatchErr, r)
ctx.Log("error", "Recovered %+v", r)
sendTelemetry(lg, telemetry.EventLevelError, telemetry.StopVMWatchTask, fmt.Sprintf("Recovered %+v", r))
}
vmWatchResultChannel <- VMWatchResult{Status: Failed, Error: vmWatchErr}
close(vmWatchResultChannel)
@ -91,21 +94,25 @@ func executeVMWatch(ctx *log.Context, s *vmWatchSettings, hEnv HandlerEnvironmen
for !shutdown {
for i := 1; i <= VMWatchMaxProcessAttempts && !shutdown; i++ {
vmWatchResultChannel <- VMWatchResult{Status: Running}
vmWatchErr = executeVMWatchHelper(ctx, i, s, hEnv)
vmWatchErr = executeVMWatchHelper(lg, i, s, hEnv)
vmWatchResultChannel <- VMWatchResult{Status: Failed, Error: vmWatchErr}
}
ctx.Log("error", fmt.Sprintf("VMWatch reached max %d retries, sleeping for %v hours before trying again", VMWatchMaxProcessAttempts, HoursBetweenRetryAttempts))
{
// scoping the errMsg variable to avoid shadowing
errMsg := fmt.Sprintf("VMWatch reached max %d retries, sleeping for %v hours before trying again", VMWatchMaxProcessAttempts, HoursBetweenRetryAttempts)
sendTelemetry(lg, telemetry.EventLevelError, telemetry.StartVMWatchTask, errMsg, "error", errMsg)
}
// we have exceeded the retries so now we go to sleep before starting again
time.Sleep(time.Hour * HoursBetweenRetryAttempts)
}
}
func executeVMWatchHelper(ctx *log.Context, attempt int, vmWatchSettings *vmWatchSettings, hEnv HandlerEnvironment) (err error) {
func executeVMWatchHelper(lg log.Logger, attempt int, vmWatchSettings *vmWatchSettings, hEnv *handlerenv.HandlerEnvironment) (err error) {
pid := -1
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Error: %w\n Additonal Details: %+v", err, r)
ctx.Log("error", "Recovered %+v", r)
err = fmt.Errorf("error: %w\n Additonal Details: %+v", err, r)
sendTelemetry(lg, telemetry.EventLevelError, telemetry.StartVMWatchTask, fmt.Sprintf("Recovered %+v", r))
}
}()
@ -113,29 +120,31 @@ func executeVMWatchHelper(ctx *log.Context, attempt int, vmWatchSettings *vmWatc
vmWatchCommand, err = setupVMWatchCommand(vmWatchSettings, hEnv)
if err != nil {
err = fmt.Errorf("[%v][PID -1] Attempt %d: VMWatch setup failed. Error: %w", time.Now().UTC().Format(time.RFC3339), attempt, err)
ctx.Log("error", err.Error())
sendTelemetry(lg, telemetry.EventLevelError, telemetry.SetupVMWatchTask, err.Error())
return err
}
ctx.Log("event", fmt.Sprintf("Attempt %d: Setup VMWatch command: %s\nArgs: %v\nDir: %s\nEnv: %v\n", attempt, vmWatchCommand.Path, vmWatchCommand.Args, vmWatchCommand.Dir, vmWatchCommand.Env))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.SetupVMWatchTask,
fmt.Sprintf("Attempt %d: Setup VMWatch command: %s\nArgs: %v\nDir: %s\nEnv: %v\n",
attempt, vmWatchCommand.Path, vmWatchCommand.Args, vmWatchCommand.Dir, vmWatchCommand.Env),
)
// TODO: Combined output may get excessively long, especially since VMWatch is a long running process
// We should trim the output or only get from Stderr
combinedOutput := &bytes.Buffer{}
vmWatchCommand.Stdout = combinedOutput
vmWatchCommand.Stderr = combinedOutput
vmWatchCommand.SysProcAttr = &syscall.SysProcAttr{ Pdeathsig: syscall.SIGTERM }
vmWatchCommand.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGTERM}
// Start command
if err := vmWatchCommand.Start(); err != nil {
err = fmt.Errorf("[%v][PID -1] Attempt %d: VMWatch failed to start. Error: %w\nOutput: %s", time.Now().UTC().Format(time.RFC3339), attempt, err, combinedOutput.String())
ctx.Log("error", err.Error())
sendTelemetry(lg, telemetry.EventLevelError, telemetry.StartVMWatchTask, err.Error(), "error", err)
return err
}
pid = vmWatchCommand.Process.Pid // cmd.Process should be populated on success
ctx.Log("event", fmt.Sprintf("Attempt %d: VMWatch process started with pid %d", attempt, pid))
err = applyResourceGovernanceIfApplicable(ctx, vmWatchSettings, vmWatchCommand)
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.StartVMWatchTask, fmt.Sprintf("Attempt %d: Started VMWatch with PID %d", attempt, pid))
err = applyResourceGovernanceIfApplicable(lg, vmWatchSettings, vmWatchCommand)
processDone := make(chan bool)
@ -153,26 +162,26 @@ func executeVMWatchHelper(ctx *log.Context, attempt int, vmWatchSettings *vmWatc
wg.Add(1)
go func() {
defer wg.Done()
monitorHeartBeat(ctx, GetVMWatchHeartbeatFilePath(hEnv), processDone, vmWatchCommand)
monitorHeartBeat(lg, GetVMWatchHeartbeatFilePath(hEnv), processDone, vmWatchCommand)
}()
wg.Wait()
err = fmt.Errorf("[%v][PID %d] Attempt %d: VMWatch process exited. Error: %w\nOutput: %s", time.Now().UTC().Format(time.RFC3339), pid, attempt, err, combinedOutput.String())
ctx.Log("error", err.Error())
sendTelemetry(lg, telemetry.EventLevelError, telemetry.StopVMWatchTask, err.Error(), "error", err)
return err
}
// Sets resource governance for VMWatch process if applicable
// if it is already being run with system-run this is a no-op
func applyResourceGovernanceIfApplicable(ctx *log.Context, vmWatchSettings *vmWatchSettings, vmWatchCommand *exec.Cmd) error {
func applyResourceGovernanceIfApplicable(lg log.Logger, vmWatchSettings *vmWatchSettings, vmWatchCommand *exec.Cmd) error {
// The default way to run vmwatch is via systemd-run. There are some cases where system-run is not available
// (in a container or in a distro without systemd). In those cases we will manage the cgroups directly
runningInSystemd := vmWatchCommand.Path == "systemd-run"
if (!runningInSystemd) {
if !runningInSystemd {
pid := vmWatchCommand.Process.Pid
err := createAndAssignCgroups(ctx, vmWatchSettings, pid)
err := createAndAssignCgroups(lg, vmWatchSettings, pid)
if err != nil {
err = fmt.Errorf("[%v][PID %d] Failed to assign VMWatch process to cgroup. Error: %w", time.Now().UTC().Format(time.RFC3339), pid, err)
ctx.Log("error", err.Error())
sendTelemetry(lg, telemetry.EventLevelError, telemetry.StartVMWatchTask, err.Error(), "error", err)
// On real VMs we want this to stop vwmwatch from running at all since we want to make sure we are protected
// by resource governance but on dev machines, we may fail due to limitations of execution environment (ie on dev container
// or in a github pipeline container we don't have permission to assign cgroups (also on WSL environments it doesn't
@ -181,8 +190,8 @@ func applyResourceGovernanceIfApplicable(ctx *log.Context, vmWatchSettings *vmWa
// ALLOW_VMWATCH_GROUP_ASSIGNMENT_FAILURE and if they are both set we will just log and continue
// this allows us to test both cases
if os.Getenv(AllowVMWatchCgroupAssignmentFailureVariableName) == "" || os.Getenv(RunningInDevContainerVariableName) == "" {
ctx.Log("event", fmt.Sprintf("Killing VMWatch process as cgroup assignment failed"))
_ = killVMWatch(ctx, vmWatchCommand)
lg.Log("event", "Killing VMWatch process as cgroup assignment failed")
_ = killVMWatch(lg, vmWatchCommand)
return err
}
}
@ -190,7 +199,7 @@ func applyResourceGovernanceIfApplicable(ctx *log.Context, vmWatchSettings *vmWa
return nil
}
func monitorHeartBeat(ctx *log.Context, heartBeatFile string, processDone chan bool, cmd *exec.Cmd) {
func monitorHeartBeat(lg log.Logger, heartBeatFile string, processDone chan bool, cmd *exec.Cmd) {
maxTimeBetweenHeartBeatsInSeconds := 60
timer := time.NewTimer(time.Second * time.Duration(maxTimeBetweenHeartBeatsInSeconds))
@ -204,11 +213,11 @@ func monitorHeartBeat(ctx *log.Context, heartBeatFile string, processDone chan b
} else {
// heartbeat file was not updated within 60 seconds, process is hung
err = fmt.Errorf("[%v][PID %d] VMWatch process did not update heartbeat file within the time limit, killing the process", time.Now().UTC().Format(time.RFC3339), cmd.Process.Pid)
ctx.Log("error", err.Error())
err = killVMWatch(ctx, cmd)
sendTelemetry(lg, telemetry.EventLevelError, telemetry.ReportHeatBeatTask, err.Error(), "error", err)
err = killVMWatch(lg, cmd)
if err != nil {
err = fmt.Errorf("[%v][PID %d] Failed to kill vmwatch process", time.Now().UTC().Format(time.RFC3339), cmd.Process.Pid)
ctx.Log("error", err.Error())
sendTelemetry(lg, telemetry.EventLevelError, telemetry.ReportHeatBeatTask, err.Error(), "error", err)
}
}
case <-processDone:
@ -217,25 +226,26 @@ func monitorHeartBeat(ctx *log.Context, heartBeatFile string, processDone chan b
}
}
func killVMWatch(ctx *log.Context, cmd *exec.Cmd) error {
func killVMWatch(lg log.Logger, cmd *exec.Cmd) error {
if cmd == nil || cmd.Process == nil || cmd.ProcessState != nil {
ctx.Log("event", "VMWatch is not running, killing process is not necessary.")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.KillVMWatchTask, "VMWatch is not running, killing process is not necessary.")
return nil
}
if err := cmd.Process.Kill(); err != nil {
ctx.Log("error", fmt.Sprintf("Failed to kill VMWatch process with PID %d. Error: %v", cmd.Process.Pid, err))
sendTelemetry(lg, telemetry.EventLevelError, telemetry.KillVMWatchTask,
fmt.Sprintf("Failed to kill VMWatch process with PID %d. Error: %v", cmd.Process.Pid, err))
return err
}
ctx.Log("event", fmt.Sprintf("Successfully killed VMWatch process with PID %d", cmd.Process.Pid))
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.KillVMWatchTask, fmt.Sprintf("Successfully killed VMWatch process with PID %d", cmd.Process.Pid))
return nil
}
// setupVMWatchCommand sets up the command to run VMWatch
// if we are on a linux distro with systemd-run available, cmd.Path will be systemd-run else it will be the vmwatch
// binary path
func setupVMWatchCommand(s *vmWatchSettings, hEnv HandlerEnvironment) (*exec.Cmd, error) {
func setupVMWatchCommand(s *vmWatchSettings, hEnv *handlerenv.HandlerEnvironment) (*exec.Cmd, error) {
processDirectory, err := GetProcessDirectory()
if err != nil {
return nil, err
@ -249,7 +259,7 @@ func setupVMWatchCommand(s *vmWatchSettings, hEnv HandlerEnvironment) (*exec.Cmd
// 0 is the default so allow that but any value below 30MB is not allowed
if s.MemoryLimitInBytes == 0 {
s.MemoryLimitInBytes = DefaultMaxMemoryInBytes
}
if s.MemoryLimitInBytes < 30000000 {
err = fmt.Errorf("[%v] Invalid MemoryLimitInBytes specified must be at least 30000000", time.Now().UTC().Format(time.RFC3339))
@ -257,7 +267,7 @@ func setupVMWatchCommand(s *vmWatchSettings, hEnv HandlerEnvironment) (*exec.Cmd
}
// check cpu, if 0 (default) set to the default value
if (s.MaxCpuPercentage == 0) {
if s.MaxCpuPercentage == 0 {
s.MaxCpuPercentage = DefaultMaxCpuPercentage
}
@ -344,14 +354,14 @@ func isSystemdAvailable() bool {
return err == nil && info.IsDir()
}
func createAndAssignCgroups(ctx *log.Context, vmwatchSettings *vmWatchSettings, vmWatchPid int) error {
func createAndAssignCgroups(lg log.Logger, vmwatchSettings *vmWatchSettings, vmWatchPid int) error {
// get our process and use this to determine the appropriate mount points for the cgroups
myPid := os.Getpid()
memoryLimitInBytes := int64(vmwatchSettings.MemoryLimitInBytes)
// check cgroups mode
if cgroups.Mode() == cgroups.Unified {
ctx.Log("event", "cgroups v2 detected")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.StartVMWatchTask, "cgroups v2 detected")
// in cgroup v2, we need to set the period and quota relative to one another.
// Quota is the number of microseconds in the period that process can run
// Period is the length of the period in microseconds
@ -377,7 +387,7 @@ func createAndAssignCgroups(ctx *log.Context, vmwatchSettings *vmWatchSettings,
return err
}
} else {
ctx.Log("event", "cgroups v1 detected")
sendTelemetry(lg, telemetry.EventLevelInfo, telemetry.StartVMWatchTask, "cgroups v1 detected")
p := cgroup1.PidPath(myPid)
cpuPath, err := p("cpu")
@ -422,12 +432,12 @@ func GetProcessDirectory() (string, error) {
return filepath.Dir(p), nil
}
func GetVMWatchHeartbeatFilePath(hEnv HandlerEnvironment) string {
return filepath.Join(hEnv.HandlerEnvironment.LogFolder, "vmwatch-heartbeat.txt")
func GetVMWatchHeartbeatFilePath(hEnv *handlerenv.HandlerEnvironment) string {
return filepath.Join(hEnv.LogFolder, "vmwatch-heartbeat.txt")
}
func GetExecutionEnvironment(hEnv HandlerEnvironment) string {
if strings.Contains(hEnv.HandlerEnvironment.LogFolder, AppHealthPublisherNameTest) {
func GetExecutionEnvironment(hEnv *handlerenv.HandlerEnvironment) string {
if strings.Contains(hEnv.LogFolder, AppHealthPublisherNameTest) {
return AppHealthExecutionEnvironmentTest
}
return AppHealthExecutionEnvironmentProd
@ -446,7 +456,7 @@ func GetVMWatchBinaryFullPath(processDirectory string) string {
return filepath.Join(processDirectory, "VMWatch", binaryName)
}
func GetVMWatchEnvironmentVariables(parameterOverrides map[string]interface{}, hEnv HandlerEnvironment) []string {
func GetVMWatchEnvironmentVariables(parameterOverrides map[string]interface{}, hEnv *handlerenv.HandlerEnvironment) []string {
var arr []string
// make sure we get the keys out in order
keys := make([]string, 0, len(parameterOverrides))
@ -461,8 +471,8 @@ func GetVMWatchEnvironmentVariables(parameterOverrides map[string]interface{}, h
fmt.Println(k, parameterOverrides[k])
}
arr = append(arr, fmt.Sprintf("SIGNAL_FOLDER=%s", hEnv.HandlerEnvironment.EventsFolder))
arr = append(arr, fmt.Sprintf("VERBOSE_LOG_FILE_FULL_PATH=%s", filepath.Join(hEnv.HandlerEnvironment.LogFolder, VMWatchVerboseLogFileName)))
arr = append(arr, fmt.Sprintf("SIGNAL_FOLDER=%s", hEnv.EventsFolder))
arr = append(arr, fmt.Sprintf("VERBOSE_LOG_FILE_FULL_PATH=%s", filepath.Join(hEnv.LogFolder, VMWatchVerboseLogFileName)))
return arr
}

68
pkg/logging/logging.go Normal file
Просмотреть файл

@ -0,0 +1,68 @@
package logging
import (
"io"
"github.com/go-kit/log"
)
// NopLogger is a logger implementation that discards all log messages.
// It Implements the Logger interface from the Azure-extension-platform package.
type NopLogger struct {
log.Logger
}
func NewNopLogger() *NopLogger {
return &NopLogger{
Logger: log.NewNopLogger(),
}
}
func (l NopLogger) Info(format string, v ...interface{}) {
err := l.Log()
if err != nil {
panic(err)
}
}
func (l NopLogger) Warn(format string, v ...interface{}) {
err := l.Log()
if err != nil {
panic(err)
}
}
func (l NopLogger) Error(format string, v ...interface{}) {
err := l.Log()
if err != nil {
panic(err)
}
}
func (l NopLogger) ErrorFromStream(prefix string, streamReader io.Reader) {
err := l.Log()
if err != nil {
panic(err)
}
}
func (l NopLogger) WarnFromStream(prefix string, streamReader io.Reader) {
err := l.Log()
if err != nil {
panic(err)
}
}
func (l NopLogger) InfoFromStream(prefix string, streamReader io.Reader) {
err := l.Log()
if err != nil {
panic(err)
}
}
func (l NopLogger) Close() {
err := l.Log()
if err != nil {
panic(err)
}
}

Просмотреть файл

@ -25,6 +25,7 @@ RUN ln -s /var/lib/waagent/fake-waagent /sbin/fake-waagent && \
# Copy the handler files
COPY misc/HandlerManifest.json ./Extension/
COPY misc/manifest.xml ./Extension/
COPY misc/applicationhealth-shim ./Extension/bin/
COPY bin/applicationhealth-extension ./Extension/bin/

21
vendor/github.com/Azure/azure-extension-platform/LICENSE.txt сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
Copyright (c) Microsoft Corporation.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

35
vendor/github.com/Azure/azure-extension-platform/pkg/extensionerrors/errorhelper.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package extensionerrors
import (
"fmt"
"github.com/pkg/errors"
"runtime/debug"
)
func AddStackToError(err error) error {
if err == nil {
return nil
}
stackString := string(debug.Stack())
return fmt.Errorf("%+v\nCallStack: %s", err, stackString)
}
func NewErrorWithStack(errString string) error {
stackString := string(debug.Stack())
return fmt.Errorf("%s\nCallStack: %s", errString, stackString)
}
func CombineErrors(err1 error, err2 error) error {
if err1 == nil && err2 == nil {
return nil
}
if err1 != nil && err2 == nil {
return err1
}
if err1 == nil && err2 != nil {
return err2
}
return errors.Wrap(err1, err2.Error())
}

47
vendor/github.com/Azure/azure-extension-platform/pkg/extensionerrors/extensionerrors.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package extensionerrors
import "github.com/pkg/errors"
var (
// ErrArgCannotBeNull is returned if a required parameter is null
ErrArgCannotBeNull = errors.New("The argument cannot be null")
// ErrArgCannotBeNullOrEmpty is returned if a required string parameter is null or empty
ErrArgCannotBeNullOrEmpty = errors.New("The argument cannot be null or empty")
// ErrMustRunAsAdmin is returned if an operation ran at permissions below admin
ErrMustRunAsAdmin = errors.New("The process must run as Administrator")
// ErrCertWithThumbprintNotFound is returned if we couldn't find the cert
ErrCertWithThumbprintNotFound = errors.New("The certificate for the specified thumbprint was not found")
// ErrInvalidProtectedSettingsData is returned when the protected settings data is invalid
ErrInvalidProtectedSettingsData = errors.New("The protected settings data is invalid")
// ErrInvalidSettingsFile is returned if the settings file is invalid
ErrInvalidSettingsFile = errors.New("The settings file is invalid")
// ErrInvalidSettingsRuntimeSettingsCount is returned if the runtime settings count is not one
ErrInvalidSettingsRuntimeSettingsCount = errors.New("The runtime settings count in the settings file is invalid")
// ErrNoCertificateThumbprint is returned if protected setting exist but no certificate thumbprint does
ErrNoCertificateThumbprint = errors.New("No certificate thumbprint to decode protected settings")
// ErrCannotDecodeProtectedSettings is returned if we cannot base64 decode the protected settings
ErrCannotDecodeProtectedSettings = errors.New("Failed to base64 decode the protected settings")
// ErrInvalidSettingsFileName is returned if we cannot parse the .settings file name
ErrInvalidSettingsFileName = errors.New("Invalid .settings file name")
// ErrNoSettingsFiles is returned if no .settings file are found
ErrNoSettingsFiles = errors.New("No .settings files exist")
// ErrNoMrseqFile is returned if no mrseq file are found
ErrNoMrseqFile = errors.New("No mrseq file exist")
ErrNotFound = errors.New("NotFound")
ErrInvalidOperationName = errors.New("operation name is invalid")
)

124
vendor/github.com/Azure/azure-extension-platform/pkg/extensionevents/extension_events.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package extensionevents
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"time"
"github.com/Azure/azure-extension-platform/pkg/handlerenv"
"github.com/Azure/azure-extension-platform/pkg/logging"
)
const (
eventLevelCritical = "Critical"
eventLevelError = "Error"
eventLevelWarning = "Warning"
eventLevelVerbose = "Verbose"
eventLevelInformational = "Informational"
)
type extensionEvent struct {
Version string `json:"Version"`
Timestamp string `json:"Timestamp"`
TaskName string `json:"TaskName"`
EventLevel string `json:"EventLevel"`
Message string `json:"Message"`
EventPid string `json:"EventPid"`
EventTid string `json:"EventTid"`
OperationID string `json:"OperationId"`
}
// ExtensionEventManager allows extensions to log events that will be collected
// by the Guest Agent
type ExtensionEventManager struct {
extensionLogger logging.ILogger
eventsFolder string
operationID string
}
func (eem *ExtensionEventManager) logEvent(taskName string, eventLevel string, message string) {
if eem.eventsFolder == "" {
eem.extensionLogger.Warn("EventsFolder not set. Not writing event.")
return
}
extensionVersion := os.Getenv("AZURE_GUEST_AGENT_EXTENSION_VERSION")
timestamp := time.Now().UTC().Format(time.RFC3339)
pid := fmt.Sprintf("%v", os.Getpid())
tid := getThreadID()
extensionEvent := extensionEvent{
Version: extensionVersion,
Timestamp: timestamp,
TaskName: taskName,
EventLevel: eventLevel,
Message: message,
EventPid: pid,
EventTid: tid,
OperationID: eem.operationID,
}
// File name is the unix time in milliseconds
fileName := strconv.FormatInt(time.Now().UTC().UnixNano()/1000, 10)
filePath := path.Join(eem.eventsFolder, fileName) + ".json"
b, err := json.Marshal(extensionEvent)
if err != nil {
eem.extensionLogger.Error("Unable to serialize extension event: <%v>", err)
return
}
err = ioutil.WriteFile(filePath, b, 0644)
if err != nil {
eem.extensionLogger.Error("Unable to write event file: <%v>", err)
}
}
// New creates a new instance of the ExtensionEventManager
func New(el logging.ILogger, he *handlerenv.HandlerEnvironment) *ExtensionEventManager {
eem := &ExtensionEventManager{
extensionLogger: el,
eventsFolder: he.EventsFolder,
operationID: "",
}
return eem
}
// "SetOperationId()" sets operation Id passed by user while logging extension events
// This is made as separate function (not included in "logEvent()") to enable users to set Operation ID globally for their extension.
// "operationID" corresponds to "Context3" column in 'GuestAgentGenericLogs' table (Rdos cluster)
func (eem *ExtensionEventManager) SetOperationID(operationID string) {
eem.operationID = operationID
}
// LogCriticalEvent writes a message with critical status for the extension
func (eem *ExtensionEventManager) LogCriticalEvent(taskName string, message string) {
eem.logEvent(taskName, eventLevelCritical, message)
}
// LogErrorEvent writes a message with error status for the extension
func (eem *ExtensionEventManager) LogErrorEvent(taskName string, message string) {
eem.logEvent(taskName, eventLevelError, message)
}
// LogWarningEvent writes a message with warning status for the extension
func (eem *ExtensionEventManager) LogWarningEvent(taskName string, message string) {
eem.logEvent(taskName, eventLevelWarning, message)
}
// LogVerboseEvent writes a message with verbose status for the extension
func (eem *ExtensionEventManager) LogVerboseEvent(taskName string, message string) {
eem.logEvent(taskName, eventLevelVerbose, message)
}
// LogInformationalEvent writes a message with informational status for the extension
func (eem *ExtensionEventManager) LogInformationalEvent(taskName string, message string) {
eem.logEvent(taskName, eventLevelInformational, message)
}

13
vendor/github.com/Azure/azure-extension-platform/pkg/extensionevents/extension_events_linux.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package extensionevents
import (
"fmt"
"golang.org/x/sys/unix"
)
func getThreadID() string {
return fmt.Sprintf("%d", unix.Gettid())
}

12
vendor/github.com/Azure/azure-extension-platform/pkg/extensionevents/extension_events_windows.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package extensionevents
import (
"fmt"
"golang.org/x/sys/windows"
)
func getThreadID() string {
return fmt.Sprintf("%v", windows.GetCurrentThreadId())
}

128
vendor/github.com/Azure/azure-extension-platform/pkg/handlerenv/handlerenv.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package handlerenv
import (
"encoding/json"
"fmt"
"github.com/Azure/azure-extension-platform/pkg/extensionerrors"
"github.com/Azure/azure-extension-platform/pkg/utils"
"io/ioutil"
"os"
"path/filepath"
)
const handlerEnvFileName = "HandlerEnvironment.json"
// HandlerEnvironment describes the handler environment configuration for an extension
type HandlerEnvironment struct {
HeartbeatFile string
StatusFolder string
ConfigFolder string
LogFolder string
DataFolder string
EventsFolder string
DeploymentID string
RoleName string
Instance string
HostResolverAddress string
}
// HandlerEnvironment describes the handler environment configuration presented
// to the extension handler by the Azure Guest Agent.
type handlerEnvironmentInternal struct {
Version float64 `json:"version"`
Name string `json:"name"`
HandlerEnvironment struct {
HeartbeatFile string `json:"heartbeatFile"`
StatusFolder string `json:"statusFolder"`
ConfigFolder string `json:"configFolder"`
LogFolder string `json:"logFolder"`
EventsFolder string `json:"eventsFolder"`
EventsFolderPreview string `json:"eventsFolder_preview"`
DeploymentID string `json:"deploymentid"`
RoleName string `json:"rolename"`
Instance string `json:"instance"`
HostResolverAddress string `json:"hostResolverAddress"`
}
}
// GetHandlerEnv locates the HandlerEnvironment.json file by assuming it lives
// next to or one level above the extension handler (read: this) executable,
// reads, parses and returns it.
func GetHandlerEnvironment(name, version string) (he *HandlerEnvironment, _ error) {
contents, _, err := findAndReadFile(handlerEnvFileName)
if err != nil {
return nil, err
}
handlerEnvInternal, err := parseHandlerEnv(contents)
if err != nil {
return nil, err
}
// TODO: before this API goes public, remove the eventsfolder_preview
// This is only used for private preview of the events
eventsFolder := handlerEnvInternal.HandlerEnvironment.EventsFolder
if eventsFolder == "" {
eventsFolder = handlerEnvInternal.HandlerEnvironment.EventsFolderPreview
}
dataFolder := utils.GetDataFolder(name, version)
return &HandlerEnvironment{
HeartbeatFile: handlerEnvInternal.HandlerEnvironment.HeartbeatFile,
StatusFolder: handlerEnvInternal.HandlerEnvironment.StatusFolder,
ConfigFolder: handlerEnvInternal.HandlerEnvironment.ConfigFolder,
LogFolder: handlerEnvInternal.HandlerEnvironment.LogFolder,
DataFolder: dataFolder,
EventsFolder: eventsFolder,
DeploymentID: handlerEnvInternal.HandlerEnvironment.DeploymentID,
RoleName: handlerEnvInternal.HandlerEnvironment.RoleName,
Instance: handlerEnvInternal.HandlerEnvironment.Instance,
HostResolverAddress: handlerEnvInternal.HandlerEnvironment.HostResolverAddress,
}, nil
}
// ParseHandlerEnv parses the HandlerEnvironment.json format.
func parseHandlerEnv(b []byte) (*handlerEnvironmentInternal, error) {
var hf []handlerEnvironmentInternal
if err := json.Unmarshal(b, &hf); err != nil {
return nil, fmt.Errorf("vmextension: failed to parse handler env: %v", err)
}
if len(hf) != 1 {
return nil, fmt.Errorf("vmextension: expected 1 config in parsed HandlerEnvironment, found: %v", len(hf))
}
return &hf[0], nil
}
// findAndReadFile locates the specified file on disk relative to our currently
// executing process and attempts to read the file
func findAndReadFile(fileName string) (b []byte, fileLoc string, _ error) {
dir, err := utils.GetCurrentProcessWorkingDir()
if err != nil {
return nil, "", fmt.Errorf("vmextension: cannot find base directory of the running process: %v", err)
}
paths := []string{
filepath.Join(dir, fileName), // this level (i.e. executable is in [EXT_NAME]/.)
filepath.Join(dir, "..", fileName), // one up (i.e. executable is in [EXT_NAME]/bin/.)
}
for _, p := range paths {
o, err := ioutil.ReadFile(p)
if err != nil && !os.IsNotExist(err) {
return nil, "", fmt.Errorf("vmextension: error examining '%s' at '%s': %v", fileName, p, err)
} else if err == nil {
fileLoc = p
b = o
break
}
}
if b == nil {
return nil, "", extensionerrors.ErrNotFound
}
return b, fileLoc, nil
}

236
vendor/github.com/Azure/azure-extension-platform/pkg/logging/logging.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,236 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package logging
import (
"fmt"
"io"
"io/fs"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"runtime/debug"
"sort"
"strconv"
"strings"
"time"
"github.com/Azure/azure-extension-platform/pkg/handlerenv"
)
const (
logLevelError = "Error "
logLevelWarning = "Warning "
logLevelInfo = "Info "
)
const (
thirtyMB = 30 * 1024 * 1034 // 31,457,280 bytes
fortyMB = 40 * 1024 * 1024 // 41,943,040 bytes
logDirThresholdLow = thirtyMB
logDirThresholdHigh = fortyMB
)
type StreamLogReader interface {
ErrorFromStream(prefix string, streamReader io.Reader)
WarnFromStream(prefix string, streamReader io.Reader)
InfoFromStream(prefix string, streamReader io.Reader)
}
// Target interface for Extentsion-Platform
type ILogger interface {
StreamLogReader
Error(format string, v ...interface{})
Warn(format string, v ...interface{})
Info(format string, v ...interface{})
Close()
}
// ExtensionLogger exposes logging capabilities to the extension
// It automatically appends time stamps and debug level to each message
// and ensures all logs are placed in the logs folder passed by the agent
type ExtensionLogger struct {
errorLogger *log.Logger
infoLogger *log.Logger
warnLogger *log.Logger
file *os.File
}
// New creates a new logging instance. If the handlerEnvironment is nil, we'll use a
// standard output logger
func New(he *handlerenv.HandlerEnvironment) *ExtensionLogger {
return NewWithName(he, "")
}
// Allows the caller to specify their own name for the file
// Supports cycling of logs to prevent filling up the disk
func NewWithName(he *handlerenv.HandlerEnvironment, logFileFormat string) *ExtensionLogger {
if he == nil {
return newStandardOutput()
}
if logFileFormat == "" {
logFileFormat = "log_%v"
}
// Rotate log folder to prevent filling up the disk
err := rotateLogFolder(he.LogFolder, logFileFormat)
if err != nil {
return newStandardOutput()
}
fileName := fmt.Sprintf(logFileFormat, strconv.FormatInt(time.Now().UTC().Unix(), 10))
filePath := path.Join(he.LogFolder, fileName)
writer, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return newStandardOutput()
}
return &ExtensionLogger{
errorLogger: log.New(writer, logLevelError, log.Ldate|log.Ltime|log.LUTC),
infoLogger: log.New(writer, logLevelInfo, log.Ldate|log.Ltime|log.LUTC),
warnLogger: log.New(writer, logLevelWarning, log.Ldate|log.Ltime|log.LUTC),
file: writer,
}
}
func GetCallStack() string {
return string(debug.Stack())
}
func newStandardOutput() *ExtensionLogger {
return &ExtensionLogger{
errorLogger: log.New(os.Stdout, logLevelError, 0),
infoLogger: log.New(os.Stdout, logLevelInfo, 0),
warnLogger: log.New(os.Stdout, logLevelWarning, 0),
file: nil,
}
}
// Close closes the file
func (logger *ExtensionLogger) Close() {
if logger.file != nil {
logger.file.Close()
}
}
// Error logs an error. Format is the same as fmt.Print
func (logger *ExtensionLogger) Error(format string, v ...interface{}) {
logger.errorLogger.Printf(format+"\n", v...)
logger.errorLogger.Printf(GetCallStack() + "\n")
}
// Warn logs a warning. Format is the same as fmt.Print
func (logger *ExtensionLogger) Warn(format string, v ...interface{}) {
logger.warnLogger.Printf(format+"\n", v...)
}
// Info logs an information statement. Format is the same as fmt.Print
func (logger *ExtensionLogger) Info(format string, v ...interface{}) {
logger.infoLogger.Printf(format+"\n", v...)
}
// Error logs an error. Get the message from a stream directly
func (logger *ExtensionLogger) ErrorFromStream(prefix string, streamReader io.Reader) {
logger.errorLogger.Print(prefix)
io.Copy(logger.errorLogger.Writer(), streamReader)
logger.errorLogger.Writer().Write([]byte(fmt.Sprintln())) // add a newline at the end of the stream contents
}
// Warn logs a warning. Get the message from a stream directly
func (logger *ExtensionLogger) WarnFromStream(prefix string, streamReader io.Reader) {
logger.warnLogger.Print(prefix)
io.Copy(logger.warnLogger.Writer(), streamReader)
logger.warnLogger.Writer().Write([]byte(fmt.Sprintln())) // add a newline at the end of the stream contents
}
// Info logs an information statement. Get the message from a stream directly
func (logger *ExtensionLogger) InfoFromStream(prefix string, streamReader io.Reader) {
logger.infoLogger.Print(prefix)
io.Copy(logger.infoLogger.Writer(), streamReader)
logger.infoLogger.Writer().Write([]byte(fmt.Sprintln())) // add a newline at the end of the stream contents
}
// Function to get directory size
func getDirSize(dirPath string) (size int64, err error) {
err = filepath.Walk(dirPath, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += info.Size()
}
return err
})
if err != nil {
err = fmt.Errorf("unable to compute directory size, error: %v", err)
}
return
}
// Function to rotate log files present in logFolder to avoid filling customer disk space
// File name matching is done on file name pattern provided before '%'
func rotateLogFolder(logFolder string, logFileFormat string) (err error) {
size, err := getDirSize(logFolder)
if err != nil {
return
}
// If directory size is still under high threshold value, nothing to do
if size < logDirThresholdHigh {
return
}
// Get all log files in logFolder
// Files are already sorted according to filenames
// Log file names contains unix timestamp as suffix, Thus we have files sorted according to age as well
var dirEntries []fs.FileInfo
dirEntries, err = ioutil.ReadDir(logFolder)
if err != nil {
err = fmt.Errorf("unable to read log folder, error: %v", err)
return
}
// Sort directory entries according to time (oldest to newest)
sort.Slice(dirEntries, func(idx1, idx2 int) bool {
return dirEntries[idx1].ModTime().Before(dirEntries[idx2].ModTime())
})
// Get log file name prefix
logFilePrefix := strings.Split(logFileFormat, "%")
for _, file := range dirEntries {
// Once directory size goes below lower threshold limit, stop deletion
if size < logDirThresholdLow {
break
}
// Skip directories
if file.IsDir() {
continue
}
// log file names are prefixed according to logFileFormat specified
if !strings.HasPrefix(file.Name(), logFilePrefix[0]) {
continue
}
// Delete the file
err = os.Remove(filepath.Join(logFolder, file.Name()))
if err != nil {
err = fmt.Errorf("unable to delete log files, error: %v", err)
return
}
// Subtract file size from total directory size
size = size - file.Size()
}
return
}

17
vendor/github.com/Azure/azure-extension-platform/pkg/utils/utils.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package utils
import (
"os"
"path/filepath"
)
// GetCurrentProcessWorkingDir returns the absolute path of the running process.
func GetCurrentProcessWorkingDir() (string, error) {
p, err := filepath.Abs(os.Args[0])
if err != nil {
return "", err
}
return filepath.Dir(p), nil
}

113
vendor/github.com/Azure/azure-extension-platform/pkg/utils/utils_linux.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package utils
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
)
// agentDir is where the agent is located, a subdirectory of which we use as the data directory
const agentDir = "/var/lib/waagent"
func GetDataFolder(name string, version string) string {
return path.Join(agentDir, name)
}
// Try clear files whose file names match with a regular expression except the filename passed in exceptFileName argument.
// If deleteFiles is true, files will be deleted, else they will be emptied without deleting.
func TryClearRegexMatchingFilesExcept(directory string, regexFileNamePattern string,
exceptFileName string, deleteFiles bool) error {
if regexFileNamePattern == "" {
return errors.New("Empty regexFileNamePattern argument.")
}
// Check if the directory exists
directoryFDRef, err := os.Open(directory)
if err != nil {
return err
}
regex, err := regexp.Compile(regexFileNamePattern)
if err != nil {
return err
}
dirEntries, err := directoryFDRef.ReadDir(0)
if err == nil {
for _, dirEntry := range dirEntries {
fileName := dirEntry.Name()
if fileName != exceptFileName && regex.MatchString(fileName) {
fullFilePath := filepath.Join(directory, fileName)
if deleteFiles {
os.Remove(fullFilePath)
} else {
os.Truncate(fullFilePath, 0) // Calling create on existing file truncates file
}
}
}
return nil
}
return err
}
// Try delete all directories in parentDirectory excepth directory by name 'exceptDirectoryName'
func TryDeleteDirectoriesExcept(parentDirectory string, exceptDirectoryName string) error {
// Check if the directory exists
directoryFDRef, err := os.Open(parentDirectory)
if err != nil {
return err
}
dirEntries, err := directoryFDRef.ReadDir(0)
if err == nil && dirEntries != nil {
for _, dirEntry := range dirEntries {
entryName := dirEntry.Name()
if dirEntry.IsDir() && entryName != exceptDirectoryName {
fullDirectoryPath := filepath.Join(parentDirectory, entryName)
os.RemoveAll(fullDirectoryPath)
}
}
return nil
}
return err
}
//Try empty runtime settings files for an extension except last, delete scripts except last.
// runtimeSettingsRegexFormatWithAnyExtName - regex identifying all settings files- example. "\\d+.settings", "RunCommandName.\\d+.settings"
// runtimeSettingsLastSeqNumFormatWithAnyExtName - example. "%s.settings", "RunCommandName.%s.settings"
func TryClearExtensionScriptsDirectoriesAndSettingsFilesExceptMostRecent(scriptsDirectory string,
runtimeSettingsDirectory string,
extensionName string,
mostRecentSequenceNumberFinished uint64,
runtimeSettingsRegexFormatWithAnyExtName string,
runtimeSettingsLastSeqNumFormatWithAnyExtName string) error {
recentSeqNumberString := strconv.FormatUint(mostRecentSequenceNumberFinished, 10)
// Delete scripts belonging to previous sequence numbers.
err := TryDeleteDirectoriesExcept(filepath.Join(scriptsDirectory, extensionName), recentSeqNumberString)
if err != nil {
return err
}
mostRecentRuntimeSetting := fmt.Sprintf(runtimeSettingsLastSeqNumFormatWithAnyExtName, mostRecentSequenceNumberFinished)
// Empty Runtimesettings files belonging to previous sequence numbers.
err = TryClearRegexMatchingFilesExcept(runtimeSettingsDirectory,
runtimeSettingsRegexFormatWithAnyExtName,
mostRecentRuntimeSetting,
false)
if err != nil {
return err
}
return nil
}

13
vendor/github.com/Azure/azure-extension-platform/pkg/utils/utils_windows.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package utils
import (
"os"
"path"
)
func GetDataFolder(name string, version string) string {
systemDriveFolder := os.Getenv("SystemDrive")
return path.Join(systemDriveFolder, "Packages\\Plugins", name, version, "Downloads")
}

144
vendor/github.com/go-kit/kit/log/log.go сгенерированный поставляемый
Просмотреть файл

@ -1,144 +0,0 @@
package log
import "errors"
// Logger is the fundamental interface for all log operations. Log creates a
// log event from keyvals, a variadic sequence of alternating keys and values.
// Implementations must be safe for concurrent use by multiple goroutines. In
// particular, any implementation of Logger that appends to keyvals or
// modifies any of its elements must make a copy first.
type Logger interface {
Log(keyvals ...interface{}) error
}
// ErrMissingValue is appended to keyvals slices with odd length to substitute
// the missing value.
var ErrMissingValue = errors.New("(MISSING)")
// NewContext returns a new Context that logs to logger.
func NewContext(logger Logger) *Context {
if c, ok := logger.(*Context); ok {
return c
}
return &Context{logger: logger}
}
// Context must always have the same number of stack frames between calls to
// its Log method and the eventual binding of Valuers to their value. This
// requirement comes from the functional requirement to allow a context to
// resolve application call site information for a log.Caller stored in the
// context. To do this we must be able to predict the number of logging
// functions on the stack when bindValues is called.
//
// Three implementation details provide the needed stack depth consistency.
// The first two of these details also result in better amortized performance,
// and thus make sense even without the requirements regarding stack depth.
// The third detail, however, is subtle and tied to the implementation of the
// Go compiler.
//
// 1. NewContext avoids introducing an additional layer when asked to
// wrap another Context.
// 2. With avoids introducing an additional layer by returning a newly
// constructed Context with a merged keyvals rather than simply
// wrapping the existing Context.
// 3. All of Context's methods take pointer receivers even though they
// do not mutate the Context.
//
// Before explaining the last detail, first some background. The Go compiler
// generates wrapper methods to implement the auto dereferencing behavior when
// calling a value method through a pointer variable. These wrapper methods
// are also used when calling a value method through an interface variable
// because interfaces store a pointer to the underlying concrete value.
// Calling a pointer receiver through an interface does not require generating
// an additional function.
//
// If Context had value methods then calling Context.Log through a variable
// with type Logger would have an extra stack frame compared to calling
// Context.Log through a variable with type Context. Using pointer receivers
// avoids this problem.
// A Context wraps a Logger and holds keyvals that it includes in all log
// events. When logging, a Context replaces all value elements (odd indexes)
// containing a Valuer with their generated value for each call to its Log
// method.
type Context struct {
logger Logger
keyvals []interface{}
hasValuer bool
}
// Log replaces all value elements (odd indexes) containing a Valuer in the
// stored context with their generated value, appends keyvals, and passes the
// result to the wrapped Logger.
func (l *Context) Log(keyvals ...interface{}) error {
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
if l.hasValuer {
// If no keyvals were appended above then we must copy l.keyvals so
// that future log events will reevaluate the stored Valuers.
if len(keyvals) == 0 {
kvs = append([]interface{}{}, l.keyvals...)
}
bindValues(kvs[:len(l.keyvals)])
}
return l.logger.Log(kvs...)
}
// With returns a new Context with keyvals appended to those of the receiver.
func (l *Context) With(keyvals ...interface{}) *Context {
if len(keyvals) == 0 {
return l
}
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
return &Context{
logger: l.logger,
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
keyvals: kvs[:len(kvs):len(kvs)],
hasValuer: l.hasValuer || containsValuer(keyvals),
}
}
// WithPrefix returns a new Context with keyvals prepended to those of the
// receiver.
func (l *Context) WithPrefix(keyvals ...interface{}) *Context {
if len(keyvals) == 0 {
return l
}
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.keyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(kvs, l.keyvals...)
return &Context{
logger: l.logger,
keyvals: kvs,
hasValuer: l.hasValuer || containsValuer(keyvals),
}
}
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
// object that calls f.
type LoggerFunc func(...interface{}) error
// Log implements Logger by calling f(keyvals...).
func (f LoggerFunc) Log(keyvals ...interface{}) error {
return f(keyvals...)
}

62
vendor/github.com/go-kit/kit/log/value.go сгенерированный поставляемый
Просмотреть файл

@ -1,62 +0,0 @@
package log
import (
"time"
"github.com/go-stack/stack"
)
// A Valuer generates a log value. When passed to Context.With in a value
// element (odd indexes), it represents a dynamic value which is re-evaluated
// with each log event.
type Valuer func() interface{}
// bindValues replaces all value elements (odd indexes) containing a Valuer
// with their generated value.
func bindValues(keyvals []interface{}) {
for i := 1; i < len(keyvals); i += 2 {
if v, ok := keyvals[i].(Valuer); ok {
keyvals[i] = v()
}
}
}
// containsValuer returns true if any of the value elements (odd indexes)
// contain a Valuer.
func containsValuer(keyvals []interface{}) bool {
for i := 1; i < len(keyvals); i += 2 {
if _, ok := keyvals[i].(Valuer); ok {
return true
}
}
return false
}
// Timestamp returns a Valuer that invokes the underlying function when bound,
// returning a time.Time. Users will probably want to use DefaultTimestamp or
// DefaultTimestampUTC.
func Timestamp(t func() time.Time) Valuer {
return func() interface{} { return t() }
}
var (
// DefaultTimestamp is a Valuer that returns the current wallclock time,
// respecting time zones, when bound.
DefaultTimestamp Valuer = func() interface{} { return time.Now().Format(time.RFC3339) }
// DefaultTimestampUTC is a Valuer that returns the current time in UTC
// when bound.
DefaultTimestampUTC Valuer = func() interface{} { return time.Now().UTC().Format(time.RFC3339) }
)
// Caller returns a Valuer that returns a file and line from a specified depth
// in the callstack. Users will probably want to use DefaultCaller.
func Caller(depth int) Valuer {
return func() interface{} { return stack.Caller(depth) }
}
var (
// DefaultCaller is a Valuer that returns the file and line where the Log
// method was invoked. It can only be used with log.With.
DefaultCaller = Caller(3)
)

15
vendor/github.com/go-kit/log/.gitignore сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/

5
vendor/github.com/go-kit/kit/LICENSE → vendor/github.com/go-kit/log/LICENSE сгенерированный поставляемый
Просмотреть файл

@ -1,6 +1,6 @@
The MIT License (MIT)
MIT License
Copyright (c) 2015 Peter Bourgon
Copyright (c) 2021 Go kit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

79
vendor/github.com/go-kit/kit/log/README.md → vendor/github.com/go-kit/log/README.md сгенерированный поставляемый
Просмотреть файл

@ -1,20 +1,22 @@
# package log
`package log` provides a minimal interface for structured logging in services.
It may be wrapped to encode conventions, enforce type-safety, provide leveled logging, and so on.
It can be used for both typical application log events, and log-structured data streams.
It may be wrapped to encode conventions, enforce type-safety, provide leveled
logging, and so on. It can be used for both typical application log events,
and log-structured data streams.
## Structured logging
Structured logging is, basically, conceding to the reality that logs are _data_,
and warrant some level of schematic rigor.
Using a stricter, key/value-oriented message format for our logs,
containing contextual and semantic information,
makes it much easier to get insight into the operational activity of the systems we build.
Consequently, `package log` is of the strong belief that
"[the benefits of structured logging outweigh the minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
Structured logging is, basically, conceding to the reality that logs are
_data_, and warrant some level of schematic rigor. Using a stricter,
key/value-oriented message format for our logs, containing contextual and
semantic information, makes it much easier to get insight into the
operational activity of the systems we build. Consequently, `package log` is
of the strong belief that "[the benefits of structured logging outweigh the
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
Migrating from unstructured to structured logging is probably a lot easier than you'd expect.
Migrating from unstructured to structured logging is probably a lot easier
than you'd expect.
```go
// Unstructured
@ -37,17 +39,17 @@ logger.Log("question", "what is the meaning of life?", "answer", 42)
// question="what is the meaning of life?" answer=42
```
### Log contexts
### Contextual Loggers
```go
func main() {
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.NewContext(logger).With("instance_id", 123)
logger = log.With(logger, "instance_id", 123)
logger.Log("msg", "starting")
NewWorker(log.NewContext(logger).With("component", "worker")).Run()
NewSlacker(log.NewContext(logger).With("component", "slacker")).Run()
NewWorker(log.With(logger, "component", "worker")).Run()
NewSlacker(log.With(logger, "component", "slacker")).Run()
}
// Output:
@ -64,11 +66,11 @@ Redirect stdlib logger to Go kit logger.
import (
"os"
stdlog "log"
kitlog "github.com/go-kit/kit/log"
kitlog "github.com/go-kit/log"
)
func main() {
logger := kitlog.NewJSONLogger(log.NewSyncWriter(os.Stdout))
logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout))
stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
stdlog.Print("I sure like pie")
}
@ -77,9 +79,8 @@ func main() {
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
```
Or, if, for legacy reasons,
you need to pipe all of your logging through the stdlib log package,
you can redirect Go kit logger to the stdlib logger.
Or, if, for legacy reasons, you need to pipe all of your logging through the
stdlib log package, you can redirect Go kit logger to the stdlib logger.
```go
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
@ -94,7 +95,7 @@ logger.Log("legacy", true, "msg", "at least it's something")
```go
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
logger.Log("msg", "hello")
@ -102,9 +103,13 @@ logger.Log("msg", "hello")
// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
```
## Levels
Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/log/level).
## Supported output formats
- [Logfmt](https://brandur.org/logfmt)
- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
- JSON
## Enhancements
@ -117,27 +122,25 @@ type Logger interface {
}
```
This interface, and its supporting code like [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
is the product of much iteration and evaluation.
For more details on the evolution of the Logger interface,
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
a talk by [Chris Hines](https://github.com/ChrisHines).
This interface, and its supporting code like is the product of much iteration
and evaluation. For more details on the evolution of the Logger interface,
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
a talk by [Chris Hines](https://github.com/ChrisHines).
Also, please see
[#63](https://github.com/go-kit/kit/issues/63),
[#76](https://github.com/go-kit/kit/pull/76),
[#131](https://github.com/go-kit/kit/issues/131),
[#157](https://github.com/go-kit/kit/pull/157),
[#164](https://github.com/go-kit/kit/issues/164), and
[#252](https://github.com/go-kit/kit/pull/252)
to review historical conversations about package log and the Logger interface.
[#63](https://github.com/go-kit/kit/issues/63),
[#76](https://github.com/go-kit/kit/pull/76),
[#131](https://github.com/go-kit/kit/issues/131),
[#157](https://github.com/go-kit/kit/pull/157),
[#164](https://github.com/go-kit/kit/issues/164), and
[#252](https://github.com/go-kit/kit/pull/252)
to review historical conversations about package log and the Logger interface.
Value-add packages and suggestions,
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/levels),
are of course welcome.
Good proposals should
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/log/level),
are of course welcome. Good proposals should
- Be composable with [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped context, and
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/log#With),
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/log#Caller) in any wrapped contextual loggers, and
- Be friendly to packages that accept only an unadorned log.Logger.
## Benchmarks & comparisons

59
vendor/github.com/go-kit/kit/log/doc.go → vendor/github.com/go-kit/log/doc.go сгенерированный поставляемый
Просмотреть файл

@ -35,14 +35,15 @@
// idea to log simple values without formatting them. This practice allows
// the chosen logger to encode values in the most appropriate way.
//
// Log Context
// Contextual Loggers
//
// A log context stores keyvals that it includes in all log events. Building
// appropriate log contexts reduces repetition and aids consistency in the
// resulting log output. We can use a context to improve the RunTask example.
// A contextual logger stores keyvals that it includes in all log events.
// Building appropriate contextual loggers reduces repetition and aids
// consistency in the resulting log output. With, WithPrefix, and WithSuffix
// add context to a logger. We can use With to improve the RunTask example.
//
// func RunTask(task Task, logger log.Logger) string {
// logger = log.NewContext(logger).With("taskID", task.ID)
// logger = log.With(logger, "taskID", task.ID)
// logger.Log("event", "starting task")
// ...
// taskHelper(task.Cmd, logger)
@ -51,19 +52,18 @@
// }
//
// The improved version emits the same log events as the original for the
// first and last calls to Log. The call to taskHelper highlights that a
// context may be passed as a logger to other functions. Each log event
// created by the called function will include the task.ID even though the
// function does not have access to that value. Using log contexts this way
// simplifies producing log output that enables tracing the life cycle of
// individual tasks. (See the Context example for the full code of the
// above snippet.)
// first and last calls to Log. Passing the contextual logger to taskHelper
// enables each log event created by taskHelper to include the task.ID even
// though taskHelper does not have access to that value. Using contextual
// loggers this way simplifies producing log output that enables tracing the
// life cycle of individual tasks. (See the Contextual example for the full
// code of the above snippet.)
//
// Dynamic Context Values
// Dynamic Contextual Values
//
// A Valuer function stored in a log context generates a new value each time
// the context logs an event. The Valuer example demonstrates how this
// feature works.
// A Valuer function stored in a contextual logger generates a new value each
// time an event is logged. The Valuer example demonstrates how this feature
// works.
//
// Valuers provide the basis for consistently logging timestamps and source
// code location. The log package defines several valuers for that purpose.
@ -71,8 +71,8 @@
// DefaultCaller. A common logger initialization sequence that ensures all log
// entries contain a timestamp and source location looks like this:
//
// logger := log.NewLogfmtLogger(log.SyncWriter(os.Stdout))
// logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
//
// Concurrent Safety
//
@ -90,4 +90,27 @@
// handled atomically within the wrapped logger, but it typically serializes
// both the formatting and output logic. Use a SyncLogger if the formatting
// logger may perform multiple writes per log event.
//
// Error Handling
//
// This package relies on the practice of wrapping or decorating loggers with
// other loggers to provide composable pieces of functionality. It also means
// that Logger.Log must return an error because some
// implementations—especially those that output log data to an io.Writer—may
// encounter errors that cannot be handled locally. This in turn means that
// Loggers that wrap other loggers should return errors from the wrapped
// logger up the stack.
//
// Fortunately, the decorator pattern also provides a way to avoid the
// necessity to check for errors every time an application calls Logger.Log.
// An application required to panic whenever its Logger encounters
// an error could initialize its logger as follows.
//
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger := log.LoggerFunc(func(keyvals ...interface{}) error {
// if err := fmtlogger.Log(keyvals...); err != nil {
// panic(err)
// }
// return nil
// })
package log

Просмотреть файл

@ -31,7 +31,9 @@ func (l *jsonLogger) Log(keyvals ...interface{}) error {
}
merge(m, k, v)
}
return json.NewEncoder(l.Writer).Encode(m)
enc := json.NewEncoder(l.Writer)
enc.SetEscapeHTML(false)
return enc.Encode(m)
}
func merge(dst map[string]interface{}, k, v interface{}) {
@ -44,9 +46,6 @@ func merge(dst map[string]interface{}, k, v interface{}) {
default:
key = fmt.Sprint(x)
}
if x, ok := v.(error); ok {
v = safeError(x)
}
// We want json.Marshaler and encoding.TextMarshaller to take priority over
// err.Error() and v.String(). But json.Marshall (called later) does that by

179
vendor/github.com/go-kit/log/log.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,179 @@
package log
import "errors"
// Logger is the fundamental interface for all log operations. Log creates a
// log event from keyvals, a variadic sequence of alternating keys and values.
// Implementations must be safe for concurrent use by multiple goroutines. In
// particular, any implementation of Logger that appends to keyvals or
// modifies or retains any of its elements must make a copy first.
type Logger interface {
Log(keyvals ...interface{}) error
}
// ErrMissingValue is appended to keyvals slices with odd length to substitute
// the missing value.
var ErrMissingValue = errors.New("(MISSING)")
// With returns a new contextual logger with keyvals prepended to those passed
// to calls to Log. If logger is also a contextual logger created by With,
// WithPrefix, or WithSuffix, keyvals is appended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func With(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
return &context{
logger: l.logger,
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
keyvals: kvs[:len(kvs):len(kvs)],
hasValuer: l.hasValuer || containsValuer(keyvals),
sKeyvals: l.sKeyvals,
sHasValuer: l.sHasValuer,
}
}
// WithPrefix returns a new contextual logger with keyvals prepended to those
// passed to calls to Log. If logger is also a contextual logger created by
// With, WithPrefix, or WithSuffix, keyvals is prepended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.keyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(kvs, l.keyvals...)
return &context{
logger: l.logger,
keyvals: kvs,
hasValuer: l.hasValuer || containsValuer(keyvals),
sKeyvals: l.sKeyvals,
sHasValuer: l.sHasValuer,
}
}
// WithSuffix returns a new contextual logger with keyvals appended to those
// passed to calls to Log. If logger is also a contextual logger created by
// With, WithPrefix, or WithSuffix, keyvals is appended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func WithSuffix(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.sKeyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(l.sKeyvals, kvs...)
return &context{
logger: l.logger,
keyvals: l.keyvals,
hasValuer: l.hasValuer,
sKeyvals: kvs,
sHasValuer: l.sHasValuer || containsValuer(keyvals),
}
}
// context is the Logger implementation returned by With, WithPrefix, and
// WithSuffix. It wraps a Logger and holds keyvals that it includes in all
// log events. Its Log method calls bindValues to generate values for each
// Valuer in the context keyvals.
//
// A context must always have the same number of stack frames between calls to
// its Log method and the eventual binding of Valuers to their value. This
// requirement comes from the functional requirement to allow a context to
// resolve application call site information for a Caller stored in the
// context. To do this we must be able to predict the number of logging
// functions on the stack when bindValues is called.
//
// Two implementation details provide the needed stack depth consistency.
//
// 1. newContext avoids introducing an additional layer when asked to
// wrap another context.
// 2. With, WithPrefix, and WithSuffix avoid introducing an additional
// layer by returning a newly constructed context with a merged keyvals
// rather than simply wrapping the existing context.
type context struct {
logger Logger
keyvals []interface{}
sKeyvals []interface{} // suffixes
hasValuer bool
sHasValuer bool
}
func newContext(logger Logger) *context {
if c, ok := logger.(*context); ok {
return c
}
return &context{logger: logger}
}
// Log replaces all value elements (odd indexes) containing a Valuer in the
// stored context with their generated value, appends keyvals, and passes the
// result to the wrapped Logger.
func (l *context) Log(keyvals ...interface{}) error {
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
if l.hasValuer {
// If no keyvals were appended above then we must copy l.keyvals so
// that future log events will reevaluate the stored Valuers.
if len(keyvals) == 0 {
kvs = append([]interface{}{}, l.keyvals...)
}
bindValues(kvs[:(len(l.keyvals))])
}
kvs = append(kvs, l.sKeyvals...)
if l.sHasValuer {
bindValues(kvs[len(kvs)-len(l.sKeyvals):])
}
return l.logger.Log(kvs...)
}
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
// object that calls f.
type LoggerFunc func(...interface{}) error
// Log implements Logger by calling f(keyvals...).
func (f LoggerFunc) Log(keyvals ...interface{}) error {
return f(keyvals...)
}

Просмотреть файл

Просмотреть файл

49
vendor/github.com/go-kit/kit/log/stdlib.go → vendor/github.com/go-kit/log/stdlib.go сгенерированный поставляемый
Просмотреть файл

@ -1,6 +1,7 @@
package log
import (
"bytes"
"io"
"log"
"regexp"
@ -26,9 +27,11 @@ func (w StdlibWriter) Write(p []byte) (int, error) {
// messages, and place them under relevant keys.
type StdlibAdapter struct {
Logger
timestampKey string
fileKey string
messageKey string
timestampKey string
fileKey string
messageKey string
prefix string
joinPrefixToMsg bool
}
// StdlibAdapterOption sets a parameter for the StdlibAdapter.
@ -39,7 +42,7 @@ func TimestampKey(key string) StdlibAdapterOption {
return func(a *StdlibAdapter) { a.timestampKey = key }
}
// FileKey sets the key for the file and line field. By default, it's "file".
// FileKey sets the key for the file and line field. By default, it's "caller".
func FileKey(key string) StdlibAdapterOption {
return func(a *StdlibAdapter) { a.fileKey = key }
}
@ -49,13 +52,23 @@ func MessageKey(key string) StdlibAdapterOption {
return func(a *StdlibAdapter) { a.messageKey = key }
}
// Prefix configures the adapter to parse a prefix from stdlib log events. If
// you provide a non-empty prefix to the stdlib logger, then your should provide
// that same prefix to the adapter via this option.
//
// By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to
// true if you want to include the parsed prefix in the msg.
func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption {
return func(a *StdlibAdapter) { a.prefix = prefix; a.joinPrefixToMsg = joinPrefixToMsg }
}
// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
// logger. It's designed to be passed to log.SetOutput.
func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
a := StdlibAdapter{
Logger: logger,
timestampKey: "ts",
fileKey: "file",
fileKey: "caller",
messageKey: "msg",
}
for _, option := range options {
@ -65,6 +78,8 @@ func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
}
func (a StdlibAdapter) Write(p []byte) (int, error) {
p = a.handlePrefix(p)
result := subexps(p)
keyvals := []interface{}{}
var timestamp string
@ -84,6 +99,7 @@ func (a StdlibAdapter) Write(p []byte) (int, error) {
keyvals = append(keyvals, a.fileKey, file)
}
if msg, ok := result["msg"]; ok {
msg = a.handleMessagePrefix(msg)
keyvals = append(keyvals, a.messageKey, msg)
}
if err := a.Logger.Log(keyvals...); err != nil {
@ -92,11 +108,30 @@ func (a StdlibAdapter) Write(p []byte) (int, error) {
return len(p), nil
}
func (a StdlibAdapter) handlePrefix(p []byte) []byte {
if a.prefix != "" {
p = bytes.TrimPrefix(p, []byte(a.prefix))
}
return p
}
func (a StdlibAdapter) handleMessagePrefix(msg string) string {
if a.prefix == "" {
return msg
}
msg = strings.TrimPrefix(msg, a.prefix)
if a.joinPrefixToMsg {
msg = a.prefix + msg
}
return msg
}
const (
logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?`
logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?`
logRegexpFile = `(?P<file>.+?:[0-9]+)?`
logRegexpMsg = `(: )?(?P<msg>.*)`
logRegexpMsg = `(: )?(?P<msg>(?s:.*))`
)
var (
@ -110,7 +145,7 @@ func subexps(line []byte) map[string]string {
}
result := map[string]string{}
for i, name := range logRegexp.SubexpNames() {
result[name] = string(m[i])
result[name] = strings.TrimRight(string(m[i]), "\n")
}
return result
}

66
vendor/github.com/go-kit/kit/log/sync.go → vendor/github.com/go-kit/log/sync.go сгенерированный поставляемый
Просмотреть файл

@ -36,25 +36,58 @@ func (l *SwapLogger) Swap(logger Logger) {
l.logger.Store(loggerStruct{logger})
}
// SyncWriter synchronizes concurrent writes to an io.Writer.
type SyncWriter struct {
mu sync.Mutex
w io.Writer
// NewSyncWriter returns a new writer that is safe for concurrent use by
// multiple goroutines. Writes to the returned writer are passed on to w. If
// another write is already in progress, the calling goroutine blocks until
// the writer is available.
//
// If w implements the following interface, so does the returned writer.
//
// interface {
// Fd() uintptr
// }
func NewSyncWriter(w io.Writer) io.Writer {
switch w := w.(type) {
case fdWriter:
return &fdSyncWriter{fdWriter: w}
default:
return &syncWriter{Writer: w}
}
}
// NewSyncWriter returns a new SyncWriter. The returned writer is safe for
// concurrent use by multiple goroutines.
func NewSyncWriter(w io.Writer) *SyncWriter {
return &SyncWriter{w: w}
// syncWriter synchronizes concurrent writes to an io.Writer.
type syncWriter struct {
sync.Mutex
io.Writer
}
// Write writes p to the underlying io.Writer. If another write is already in
// progress, the calling goroutine blocks until the SyncWriter is available.
func (w *SyncWriter) Write(p []byte) (n int, err error) {
w.mu.Lock()
n, err = w.w.Write(p)
w.mu.Unlock()
return n, err
// progress, the calling goroutine blocks until the syncWriter is available.
func (w *syncWriter) Write(p []byte) (n int, err error) {
w.Lock()
defer w.Unlock()
return w.Writer.Write(p)
}
// fdWriter is an io.Writer that also has an Fd method. The most common
// example of an fdWriter is an *os.File.
type fdWriter interface {
io.Writer
Fd() uintptr
}
// fdSyncWriter synchronizes concurrent writes to an fdWriter.
type fdSyncWriter struct {
sync.Mutex
fdWriter
}
// Write writes p to the underlying io.Writer. If another write is already in
// progress, the calling goroutine blocks until the fdSyncWriter is available.
func (w *fdSyncWriter) Write(p []byte) (n int, err error) {
w.Lock()
defer w.Unlock()
return w.fdWriter.Write(p)
}
// syncLogger provides concurrent safe logging for another Logger.
@ -75,7 +108,6 @@ func NewSyncLogger(logger Logger) Logger {
// progress, the calling goroutine blocks until the syncLogger is available.
func (l *syncLogger) Log(keyvals ...interface{}) error {
l.mu.Lock()
err := l.logger.Log(keyvals...)
l.mu.Unlock()
return err
defer l.mu.Unlock()
return l.logger.Log(keyvals...)
}

110
vendor/github.com/go-kit/log/value.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,110 @@
package log
import (
"runtime"
"strconv"
"strings"
"time"
)
// A Valuer generates a log value. When passed to With, WithPrefix, or
// WithSuffix in a value element (odd indexes), it represents a dynamic
// value which is re-evaluated with each log event.
type Valuer func() interface{}
// bindValues replaces all value elements (odd indexes) containing a Valuer
// with their generated value.
func bindValues(keyvals []interface{}) {
for i := 1; i < len(keyvals); i += 2 {
if v, ok := keyvals[i].(Valuer); ok {
keyvals[i] = v()
}
}
}
// containsValuer returns true if any of the value elements (odd indexes)
// contain a Valuer.
func containsValuer(keyvals []interface{}) bool {
for i := 1; i < len(keyvals); i += 2 {
if _, ok := keyvals[i].(Valuer); ok {
return true
}
}
return false
}
// Timestamp returns a timestamp Valuer. It invokes the t function to get the
// time; unless you are doing something tricky, pass time.Now.
//
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
// are TimestampFormats that use the RFC3339Nano format.
func Timestamp(t func() time.Time) Valuer {
return func() interface{} { return t() }
}
// TimestampFormat returns a timestamp Valuer with a custom time format. It
// invokes the t function to get the time to format; unless you are doing
// something tricky, pass time.Now. The layout string is passed to
// Time.Format.
//
// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
// are TimestampFormats that use the RFC3339Nano format.
func TimestampFormat(t func() time.Time, layout string) Valuer {
return func() interface{} {
return timeFormat{
time: t(),
layout: layout,
}
}
}
// A timeFormat represents an instant in time and a layout used when
// marshaling to a text format.
type timeFormat struct {
time time.Time
layout string
}
func (tf timeFormat) String() string {
return tf.time.Format(tf.layout)
}
// MarshalText implements encoding.TextMarshaller.
func (tf timeFormat) MarshalText() (text []byte, err error) {
// The following code adapted from the standard library time.Time.Format
// method. Using the same undocumented magic constant to extend the size
// of the buffer as seen there.
b := make([]byte, 0, len(tf.layout)+10)
b = tf.time.AppendFormat(b, tf.layout)
return b, nil
}
// Caller returns a Valuer that returns a file and line from a specified depth
// in the callstack. Users will probably want to use DefaultCaller.
func Caller(depth int) Valuer {
return func() interface{} {
_, file, line, _ := runtime.Caller(depth)
idx := strings.LastIndexByte(file, '/')
// using idx+1 below handles both of following cases:
// idx == -1 because no "/" was found, or
// idx >= 0 and we want to start at the character after the found "/".
return file[idx+1:] + ":" + strconv.Itoa(line)
}
}
var (
// DefaultTimestamp is a Valuer that returns the current wallclock time,
// respecting time zones, when bound.
DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano)
// DefaultTimestampUTC is a Valuer that returns the current time in UTC
// when bound.
DefaultTimestampUTC = TimestampFormat(
func() time.Time { return time.Now().UTC() },
time.RFC3339Nano,
)
// DefaultCaller is a Valuer that returns the file and line where the Log
// method was invoked. It can only be used with log.With.
DefaultCaller = Caller(3)
)

5
vendor/github.com/go-logfmt/logfmt/.gitignore сгенерированный поставляемый
Просмотреть файл

@ -1,4 +1 @@
_testdata/
_testdata2/
logfmt-fuzz.zip
logfmt.test.exe
.vscode/

15
vendor/github.com/go-logfmt/logfmt/.travis.yml сгенерированный поставляемый
Просмотреть файл

@ -1,15 +0,0 @@
language: go
sudo: false
go:
- 1.3
- 1.4
- 1.5
- 1.6
- tip
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- goveralls -service=travis-ci

48
vendor/github.com/go-logfmt/logfmt/CHANGELOG.md сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,48 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.0] - 2020-01-03
### Changed
- Remove the dependency on github.com/kr/logfmt by [@ChrisHines]
- Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines]
## [0.4.0] - 2018-11-21
### Added
- Go module support by [@ChrisHines]
- CHANGELOG by [@ChrisHines]
### Changed
- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines]
- On panic while printing, attempt to print panic value by [@bboreham]
## [0.3.0] - 2016-11-15
### Added
- Pool buffers for quoted strings and byte slices by [@nussjustin]
### Fixed
- Fuzz fix, quote invalid UTF-8 values by [@judwhite]
## [0.2.0] - 2016-05-08
### Added
- Encoder.EncodeKeyvals by [@ChrisHines]
## [0.1.0] - 2016-03-28
### Added
- Encoder by [@ChrisHines]
- Decoder by [@ChrisHines]
- MarshalKeyvals by [@ChrisHines]
[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
[@ChrisHines]: https://github.com/ChrisHines
[@bboreham]: https://github.com/bboreham
[@judwhite]: https://github.com/judwhite
[@nussjustin]: https://github.com/nussjustin

66
vendor/github.com/go-logfmt/logfmt/README.md сгенерированный поставляемый
Просмотреть файл

@ -1,33 +1,33 @@
[![GoDoc](https://godoc.org/github.com/go-logfmt/logfmt?status.svg)](https://godoc.org/github.com/go-logfmt/logfmt)
[![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt)
[![TravisCI](https://travis-ci.org/go-logfmt/logfmt.svg?branch=master)](https://travis-ci.org/go-logfmt/logfmt)
[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=master)
# logfmt
Package logfmt implements utilities to marshal and unmarshal data in the [logfmt
format](https://brandur.org/logfmt). It provides an API similar to
[encoding/json](http://golang.org/pkg/encoding/json/) and
[encoding/xml](http://golang.org/pkg/encoding/xml/).
The logfmt format was first documented by Brandur Leach in [this
article](https://brandur.org/logfmt). The format has not been formally
standardized. The most authoritative public specification to date has been the
documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt)
written by Blake Mizerany and Keith Rarick.
## Goals
This project attempts to conform as closely as possible to the prior art, while
also removing ambiguity where necessary to provide well behaved encoder and
decoder implementations.
## Non-goals
This project does not attempt to formally standardize the logfmt format. In the
event that logfmt is standardized this project would take conforming to the
standard as a goal.
## Versioning
Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'.
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logfmt/logfmt.svg)](https://pkg.go.dev/github.com/go-logfmt/logfmt)
[![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt)
[![Github Actions](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml/badge.svg)](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=master)
# logfmt
Package logfmt implements utilities to marshal and unmarshal data in the [logfmt
format](https://brandur.org/logfmt). It provides an API similar to
[encoding/json](http://golang.org/pkg/encoding/json/) and
[encoding/xml](http://golang.org/pkg/encoding/xml/).
The logfmt format was first documented by Brandur Leach in [this
article](https://brandur.org/logfmt). The format has not been formally
standardized. The most authoritative public specification to date has been the
documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt)
written by Blake Mizerany and Keith Rarick.
## Goals
This project attempts to conform as closely as possible to the prior art, while
also removing ambiguity where necessary to provide well behaved encoder and
decoder implementations.
## Non-goals
This project does not attempt to formally standardize the logfmt format. In the
event that logfmt is standardized this project would take conforming to the
standard as a goal.
## Versioning
Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'.

23
vendor/github.com/go-logfmt/logfmt/decode.go сгенерированный поставляемый
Просмотреть файл

@ -2,8 +2,10 @@ package logfmt
import (
"bufio"
"bytes"
"fmt"
"io"
"unicode/utf8"
)
// A Decoder reads and decodes logfmt records from an input stream.
@ -68,13 +70,19 @@ func (dec *Decoder) ScanKeyval() bool {
return false
key:
start := dec.pos
const invalidKeyError = "invalid key"
start, multibyte := dec.pos, false
for p, c := range line[dec.pos:] {
switch {
case c == '=':
dec.pos += p
if dec.pos > start {
dec.key = line[start:dec.pos]
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
dec.syntaxError(invalidKeyError)
return false
}
}
if dec.key == nil {
dec.unexpectedByte(c)
@ -89,13 +97,23 @@ key:
dec.pos += p
if dec.pos > start {
dec.key = line[start:dec.pos]
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
dec.syntaxError(invalidKeyError)
return false
}
}
return true
case c >= utf8.RuneSelf:
multibyte = true
}
}
dec.pos = len(line)
if dec.pos > start {
dec.key = line[start:dec.pos]
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
dec.syntaxError(invalidKeyError)
return false
}
}
return true
@ -186,9 +204,6 @@ func (dec *Decoder) Value() []byte {
return dec.value
}
// func (dec *Decoder) DecodeValue() ([]byte, error) {
// }
// Err returns the first non-EOF error that was encountered by the Scanner.
func (dec *Decoder) Err() error {
return dec.err

36
vendor/github.com/go-logfmt/logfmt/encode.go сгенерированный поставляемый
Просмотреть файл

@ -8,6 +8,7 @@ import (
"io"
"reflect"
"strings"
"unicode/utf8"
)
// MarshalKeyvals returns the logfmt encoding of keyvals, a variadic sequence
@ -109,8 +110,8 @@ func (e *MarshalerError) Error() string {
// a nil interface or pointer value.
var ErrNilKey = errors.New("nil key")
// ErrInvalidKey is returned by Marshal functions and Encoder methods if a key
// contains an invalid character.
// ErrInvalidKey is returned by Marshal functions and Encoder methods if, after
// dropping invalid runes, a key is empty.
var ErrInvalidKey = errors.New("invalid key")
// ErrUnsupportedKeyType is returned by Encoder methods if a key has an
@ -164,23 +165,32 @@ func writeKey(w io.Writer, key interface{}) error {
}
}
func invalidKeyRune(r rune) bool {
return r <= ' ' || r == '=' || r == '"'
// keyRuneFilter returns r for all valid key runes, and -1 for all invalid key
// runes. When used as the mapping function for strings.Map and bytes.Map
// functions it causes them to remove invalid key runes from strings or byte
// slices respectively.
func keyRuneFilter(r rune) rune {
if r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError {
return -1
}
return r
}
func writeStringKey(w io.Writer, key string) error {
if len(key) == 0 || strings.IndexFunc(key, invalidKeyRune) != -1 {
k := strings.Map(keyRuneFilter, key)
if k == "" {
return ErrInvalidKey
}
_, err := io.WriteString(w, key)
_, err := io.WriteString(w, k)
return err
}
func writeBytesKey(w io.Writer, key []byte) error {
if len(key) == 0 || bytes.IndexFunc(key, invalidKeyRune) != -1 {
k := bytes.Map(keyRuneFilter, key)
if len(k) == 0 {
return ErrInvalidKey
}
_, err := w.Write(key)
_, err := w.Write(k)
return err
}
@ -223,7 +233,7 @@ func writeValue(w io.Writer, value interface{}) error {
}
func needsQuotedValueRune(r rune) bool {
return r <= ' ' || r == '=' || r == '"'
return r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError
}
func writeStringValue(w io.Writer, value string, ok bool) error {
@ -240,7 +250,7 @@ func writeStringValue(w io.Writer, value string, ok bool) error {
func writeBytesValue(w io.Writer, value []byte) error {
var err error
if bytes.IndexFunc(value, needsQuotedValueRune) >= 0 {
if bytes.IndexFunc(value, needsQuotedValueRune) != -1 {
_, err = writeQuotedBytes(w, value)
} else {
_, err = w.Write(value)
@ -269,7 +279,7 @@ func safeError(err error) (s string, ok bool) {
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
s, ok = "null", false
} else {
panic(panicVal)
s, ok = fmt.Sprintf("PANIC:%v", panicVal), false
}
}
}()
@ -283,7 +293,7 @@ func safeString(str fmt.Stringer) (s string, ok bool) {
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
s, ok = "null", false
} else {
panic(panicVal)
s, ok = fmt.Sprintf("PANIC:%v", panicVal), true
}
}
}()
@ -297,7 +307,7 @@ func safeMarshal(tm encoding.TextMarshaler) (b []byte, err error) {
if v := reflect.ValueOf(tm); v.Kind() == reflect.Ptr && v.IsNil() {
b, err = nil, nil
} else {
panic(panicVal)
b, err = nil, fmt.Errorf("panic when marshalling: %s", panicVal)
}
}
}()

125
vendor/github.com/go-logfmt/logfmt/fuzz.go сгенерированный поставляемый
Просмотреть файл

@ -1,125 +0,0 @@
// +build gofuzz
package logfmt
import (
"bufio"
"bytes"
"fmt"
"io"
"reflect"
kr "github.com/kr/logfmt"
)
func Fuzz(data []byte) int {
parsed, err := parse(data)
if err != nil {
return 0
}
var w1 bytes.Buffer
if err := write(parsed, &w1); err != nil {
panic(err)
}
parsed, err = parse(data)
if err != nil {
panic(err)
}
var w2 bytes.Buffer
if err := write(parsed, &w2); err != nil {
panic(err)
}
if !bytes.Equal(w1.Bytes(), w2.Bytes()) {
panic(fmt.Sprintf("reserialized data does not match:\n%q\n%q\n", w1.Bytes(), w2.Bytes()))
}
return 1
}
func FuzzVsKR(data []byte) int {
parsed, err := parse(data)
parsedKR, errKR := parseKR(data)
// github.com/go-logfmt/logfmt is a stricter parser. It returns errors for
// more inputs than github.com/kr/logfmt. Ignore any inputs that have a
// stict error.
if err != nil {
return 0
}
// Fail if the more forgiving parser finds an error not found by the
// stricter parser.
if errKR != nil {
panic(fmt.Sprintf("unmatched error: %v", errKR))
}
if !reflect.DeepEqual(parsed, parsedKR) {
panic(fmt.Sprintf("parsers disagree:\n%+v\n%+v\n", parsed, parsedKR))
}
return 1
}
type kv struct {
k, v []byte
}
func parse(data []byte) ([][]kv, error) {
var got [][]kv
dec := NewDecoder(bytes.NewReader(data))
for dec.ScanRecord() {
var kvs []kv
for dec.ScanKeyval() {
kvs = append(kvs, kv{dec.Key(), dec.Value()})
}
got = append(got, kvs)
kvs = nil
}
return got, dec.Err()
}
func parseKR(data []byte) ([][]kv, error) {
var (
s = bufio.NewScanner(bytes.NewReader(data))
err error
h saveHandler
got [][]kv
)
for err == nil && s.Scan() {
h.kvs = nil
err = kr.Unmarshal(s.Bytes(), &h)
got = append(got, h.kvs)
}
if err == nil {
err = s.Err()
}
return got, err
}
type saveHandler struct {
kvs []kv
}
func (h *saveHandler) HandleLogfmt(key, val []byte) error {
if len(key) == 0 {
key = nil
}
if len(val) == 0 {
val = nil
}
h.kvs = append(h.kvs, kv{key, val})
return nil
}
func write(recs [][]kv, w io.Writer) error {
enc := NewEncoder(w)
for _, rec := range recs {
for _, f := range rec {
if err := enc.EncodeKeyval(f.k, f.v); err != nil {
return err
}
}
if err := enc.EndRecord(); err != nil {
return err
}
}
return nil
}

6
vendor/github.com/go-logfmt/logfmt/jsonstring.go сгенерированный поставляемый
Просмотреть файл

@ -71,7 +71,7 @@ func writeQuotedString(w io.Writer, s string) (int, error) {
continue
}
c, size := utf8.DecodeRuneInString(s[i:])
if c == utf8.RuneError && size == 1 {
if c == utf8.RuneError {
if start < i {
buf.WriteString(s[start:i])
}
@ -129,7 +129,7 @@ func writeQuotedBytes(w io.Writer, s []byte) (int, error) {
continue
}
c, size := utf8.DecodeRune(s[i:])
if c == utf8.RuneError && size == 1 {
if c == utf8.RuneError {
if start < i {
buf.Write(s[start:i])
}
@ -182,7 +182,7 @@ func unquoteBytes(s []byte) (t []byte, ok bool) {
continue
}
rr, size := utf8.DecodeRune(s[r:])
if rr == utf8.RuneError && size == 1 {
if rr == utf8.RuneError {
break
}
r += size

16
vendor/github.com/go-stack/stack/.travis.yml сгенерированный поставляемый
Просмотреть файл

@ -1,16 +0,0 @@
language: go
sudo: false
go:
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- goveralls -service=travis-ci

13
vendor/github.com/go-stack/stack/LICENSE.md сгенерированный поставляемый
Просмотреть файл

@ -1,13 +0,0 @@
Copyright 2014 Chris Hines
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

38
vendor/github.com/go-stack/stack/README.md сгенерированный поставляемый
Просмотреть файл

@ -1,38 +0,0 @@
[![GoDoc](https://godoc.org/github.com/go-stack/stack?status.svg)](https://godoc.org/github.com/go-stack/stack)
[![Go Report Card](https://goreportcard.com/badge/go-stack/stack)](https://goreportcard.com/report/go-stack/stack)
[![TravisCI](https://travis-ci.org/go-stack/stack.svg?branch=master)](https://travis-ci.org/go-stack/stack)
[![Coverage Status](https://coveralls.io/repos/github/go-stack/stack/badge.svg?branch=master)](https://coveralls.io/github/go-stack/stack?branch=master)
# stack
Package stack implements utilities to capture, manipulate, and format call
stacks. It provides a simpler API than package runtime.
The implementation takes care of the minutia and special cases of interpreting
the program counter (pc) values returned by runtime.Callers.
## Versioning
Package stack publishes releases via [semver](http://semver.org/) compatible Git
tags prefixed with a single 'v'. The master branch always contains the latest
release. The develop branch contains unreleased commits.
## Formatting
Package stack's types implement fmt.Formatter, which provides a simple and
flexible way to declaratively configure formatting when used with logging or
error tracking packages.
```go
func DoTheThing() {
c := stack.Caller(0)
log.Print(c) // "source.go:10"
log.Printf("%+v", c) // "pkg/path/source.go:10"
log.Printf("%n", c) // "DoTheThing"
s := stack.Trace().TrimRuntime()
log.Print(s) // "[source.go:15 caller.go:42 main.go:14]"
}
```
See the docs for all of the supported formatting options.

349
vendor/github.com/go-stack/stack/stack.go сгенерированный поставляемый
Просмотреть файл

@ -1,349 +0,0 @@
// Package stack implements utilities to capture, manipulate, and format call
// stacks. It provides a simpler API than package runtime.
//
// The implementation takes care of the minutia and special cases of
// interpreting the program counter (pc) values returned by runtime.Callers.
//
// Package stack's types implement fmt.Formatter, which provides a simple and
// flexible way to declaratively configure formatting when used with logging
// or error tracking packages.
package stack
import (
"bytes"
"errors"
"fmt"
"io"
"runtime"
"strconv"
"strings"
)
// Call records a single function invocation from a goroutine stack.
type Call struct {
fn *runtime.Func
pc uintptr
}
// Caller returns a Call from the stack of the current goroutine. The argument
// skip is the number of stack frames to ascend, with 0 identifying the
// calling function.
func Caller(skip int) Call {
var pcs [2]uintptr
n := runtime.Callers(skip+1, pcs[:])
var c Call
if n < 2 {
return c
}
c.pc = pcs[1]
if runtime.FuncForPC(pcs[0]) != sigpanic {
c.pc--
}
c.fn = runtime.FuncForPC(c.pc)
return c
}
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
func (c Call) String() string {
return fmt.Sprint(c)
}
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
// as fmt.Sprintf("%v", c).
func (c Call) MarshalText() ([]byte, error) {
if c.fn == nil {
return nil, ErrNoFunc
}
buf := bytes.Buffer{}
fmt.Fprint(&buf, c)
return buf.Bytes(), nil
}
// ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
// cause is a Call with the zero value.
var ErrNoFunc = errors.New("no call stack information")
// Format implements fmt.Formatter with support for the following verbs.
//
// %s source file
// %d line number
// %n function name
// %v equivalent to %s:%d
//
// It accepts the '+' and '#' flags for most of the verbs as follows.
//
// %+s path of source file relative to the compile time GOPATH
// %#s full path of source file
// %+n import path qualified function name
// %+v equivalent to %+s:%d
// %#v equivalent to %#s:%d
func (c Call) Format(s fmt.State, verb rune) {
if c.fn == nil {
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
return
}
switch verb {
case 's', 'v':
file, line := c.fn.FileLine(c.pc)
switch {
case s.Flag('#'):
// done
case s.Flag('+'):
file = file[pkgIndex(file, c.fn.Name()):]
default:
const sep = "/"
if i := strings.LastIndex(file, sep); i != -1 {
file = file[i+len(sep):]
}
}
io.WriteString(s, file)
if verb == 'v' {
buf := [7]byte{':'}
s.Write(strconv.AppendInt(buf[:1], int64(line), 10))
}
case 'd':
_, line := c.fn.FileLine(c.pc)
buf := [6]byte{}
s.Write(strconv.AppendInt(buf[:0], int64(line), 10))
case 'n':
name := c.fn.Name()
if !s.Flag('+') {
const pathSep = "/"
if i := strings.LastIndex(name, pathSep); i != -1 {
name = name[i+len(pathSep):]
}
const pkgSep = "."
if i := strings.Index(name, pkgSep); i != -1 {
name = name[i+len(pkgSep):]
}
}
io.WriteString(s, name)
}
}
// PC returns the program counter for this call frame; multiple frames may
// have the same PC value.
func (c Call) PC() uintptr {
return c.pc
}
// name returns the import path qualified name of the function containing the
// call.
func (c Call) name() string {
if c.fn == nil {
return "???"
}
return c.fn.Name()
}
func (c Call) file() string {
if c.fn == nil {
return "???"
}
file, _ := c.fn.FileLine(c.pc)
return file
}
func (c Call) line() int {
if c.fn == nil {
return 0
}
_, line := c.fn.FileLine(c.pc)
return line
}
// CallStack records a sequence of function invocations from a goroutine
// stack.
type CallStack []Call
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
func (cs CallStack) String() string {
return fmt.Sprint(cs)
}
var (
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
spaceBytes = []byte(" ")
)
// MarshalText implements encoding.TextMarshaler. It formats the CallStack the
// same as fmt.Sprintf("%v", cs).
func (cs CallStack) MarshalText() ([]byte, error) {
buf := bytes.Buffer{}
buf.Write(openBracketBytes)
for i, pc := range cs {
if pc.fn == nil {
return nil, ErrNoFunc
}
if i > 0 {
buf.Write(spaceBytes)
}
fmt.Fprint(&buf, pc)
}
buf.Write(closeBracketBytes)
return buf.Bytes(), nil
}
// Format implements fmt.Formatter by printing the CallStack as square brackets
// ([, ]) surrounding a space separated list of Calls each formatted with the
// supplied verb and options.
func (cs CallStack) Format(s fmt.State, verb rune) {
s.Write(openBracketBytes)
for i, pc := range cs {
if i > 0 {
s.Write(spaceBytes)
}
pc.Format(s, verb)
}
s.Write(closeBracketBytes)
}
// findSigpanic intentionally executes faulting code to generate a stack trace
// containing an entry for runtime.sigpanic.
func findSigpanic() *runtime.Func {
var fn *runtime.Func
var p *int
func() int {
defer func() {
if p := recover(); p != nil {
var pcs [512]uintptr
n := runtime.Callers(2, pcs[:])
for _, pc := range pcs[:n] {
f := runtime.FuncForPC(pc)
if f.Name() == "runtime.sigpanic" {
fn = f
break
}
}
}
}()
// intentional nil pointer dereference to trigger sigpanic
return *p
}()
return fn
}
var sigpanic = findSigpanic()
// Trace returns a CallStack for the current goroutine with element 0
// identifying the calling function.
func Trace() CallStack {
var pcs [512]uintptr
n := runtime.Callers(2, pcs[:])
cs := make([]Call, n)
for i, pc := range pcs[:n] {
pcFix := pc
if i > 0 && cs[i-1].fn != sigpanic {
pcFix--
}
cs[i] = Call{
fn: runtime.FuncForPC(pcFix),
pc: pcFix,
}
}
return cs
}
// TrimBelow returns a slice of the CallStack with all entries below c
// removed.
func (cs CallStack) TrimBelow(c Call) CallStack {
for len(cs) > 0 && cs[0].pc != c.pc {
cs = cs[1:]
}
return cs
}
// TrimAbove returns a slice of the CallStack with all entries above c
// removed.
func (cs CallStack) TrimAbove(c Call) CallStack {
for len(cs) > 0 && cs[len(cs)-1].pc != c.pc {
cs = cs[:len(cs)-1]
}
return cs
}
// pkgIndex returns the index that results in file[index:] being the path of
// file relative to the compile time GOPATH, and file[:index] being the
// $GOPATH/src/ portion of file. funcName must be the name of a function in
// file as returned by runtime.Func.Name.
func pkgIndex(file, funcName string) int {
// As of Go 1.6.2 there is no direct way to know the compile time GOPATH
// at runtime, but we can infer the number of path segments in the GOPATH.
// We note that runtime.Func.Name() returns the function name qualified by
// the import path, which does not include the GOPATH. Thus we can trim
// segments from the beginning of the file path until the number of path
// separators remaining is one more than the number of path separators in
// the function name. For example, given:
//
// GOPATH /home/user
// file /home/user/src/pkg/sub/file.go
// fn.Name() pkg/sub.Type.Method
//
// We want to produce:
//
// file[:idx] == /home/user/src/
// file[idx:] == pkg/sub/file.go
//
// From this we can easily see that fn.Name() has one less path separator
// than our desired result for file[idx:]. We count separators from the
// end of the file path until it finds two more than in the function name
// and then move one character forward to preserve the initial path
// segment without a leading separator.
const sep = "/"
i := len(file)
for n := strings.Count(funcName, sep) + 2; n > 0; n-- {
i = strings.LastIndex(file[:i], sep)
if i == -1 {
i = -len(sep)
break
}
}
// get back to 0 or trim the leading separator
return i + len(sep)
}
var runtimePath string
func init() {
var pcs [1]uintptr
runtime.Callers(0, pcs[:])
fn := runtime.FuncForPC(pcs[0])
file, _ := fn.FileLine(pcs[0])
idx := pkgIndex(file, fn.Name())
runtimePath = file[:idx]
if runtime.GOOS == "windows" {
runtimePath = strings.ToLower(runtimePath)
}
}
func inGoroot(c Call) bool {
file := c.file()
if len(file) == 0 || file[0] == '?' {
return true
}
if runtime.GOOS == "windows" {
file = strings.ToLower(file)
}
return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go")
}
// TrimRuntime returns a slice of the CallStack with the topmost entries from
// the go runtime removed. It considers any calls originating from unknown
// files, files under GOROOT, or _testmain.go as part of the runtime.
func (cs CallStack) TrimRuntime() CallStack {
for len(cs) > 0 && inGoroot(cs[len(cs)-1]) {
cs = cs[:len(cs)-1]
}
return cs
}

3
vendor/github.com/kr/logfmt/.gitignore сгенерированный поставляемый
Просмотреть файл

@ -1,3 +0,0 @@
*.test
*.swp
*.prof

12
vendor/github.com/kr/logfmt/Readme сгенерированный поставляемый
Просмотреть файл

@ -1,12 +0,0 @@
Go package for parsing (and, eventually, generating)
log lines in the logfmt style.
See http://godoc.org/github.com/kr/logfmt for format, and other documentation and examples.
Copyright (C) 2013 Keith Rarick, Blake Mizerany
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

184
vendor/github.com/kr/logfmt/decode.go сгенерированный поставляемый
Просмотреть файл

@ -1,184 +0,0 @@
// Package implements the decoding of logfmt key-value pairs.
//
// Example logfmt message:
//
// foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf
//
// Example result in JSON:
//
// { "foo": "bar", "a": 14, "baz": "hello kitty", "cool%story": "bro", "f": true, "%^asdf": true }
//
// EBNFish:
//
// ident_byte = any byte greater than ' ', excluding '=' and '"'
// string_byte = any byte excluding '"' and '\'
// garbage = !ident_byte
// ident = ident_byte, { ident byte }
// key = ident
// value = ident | '"', { string_byte | '\', '"' }, '"'
// pair = key, '=', value | key, '=' | key
// message = { garbage, pair }, garbage
package logfmt
import (
"reflect"
"strconv"
"strings"
"time"
)
// Handler is the interface implemented by objects that accept logfmt
// key-value pairs. HandleLogfmt must copy the logfmt data if it
// wishes to retain the data after returning.
type Handler interface {
HandleLogfmt(key, val []byte) error
}
// The HandlerFunc type is an adapter to allow the use of ordinary functions as
// logfmt handlers. If f is a function with the appropriate signature,
// HandlerFunc(f) is a Handler object that calls f.
type HandlerFunc func(key, val []byte) error
func (f HandlerFunc) HandleLogfmt(key, val []byte) error {
return f(key, val)
}
// Unmarshal parses the logfmt encoding data and stores the result in the value
// pointed to by v. If v is an Handler, HandleLogfmt will be called for each
// key-value pair.
//
// If v is not a Handler, it will pass v to NewStructHandler and use the
// returned StructHandler for decoding.
func Unmarshal(data []byte, v interface{}) (err error) {
h, ok := v.(Handler)
if !ok {
h, err = NewStructHandler(v)
if err != nil {
return err
}
}
return gotoScanner(data, h)
}
// StructHandler unmarshals logfmt into a struct. It matches incoming keys to
// the the struct's fields (either the struct field name or its tag, preferring
// an exact match but also accepting a case-insensitive match.
//
// Field types supported by StructHandler are:
//
// all numeric types (e.g. float32, int, etc.)
// []byte
// string
// bool - true if key is present, false otherwise (the value is ignored).
// time.Duration - uses time.ParseDuration
//
// If a field is a pointer to an above type, and a matching key is not present
// in the logfmt data, the pointer will be untouched.
//
// If v is not a pointer to an Handler or struct, Unmarshal will return an
// error.
type StructHandler struct {
rv reflect.Value
}
func NewStructHandler(v interface{}) (Handler, error) {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return nil, &InvalidUnmarshalError{reflect.TypeOf(v)}
}
return &StructHandler{rv: rv}, nil
}
func (h *StructHandler) HandleLogfmt(key, val []byte) error {
el := h.rv.Elem()
skey := string(key)
for i := 0; i < el.NumField(); i++ {
fv := el.Field(i)
ft := el.Type().Field(i)
switch {
case ft.Name == skey:
case ft.Tag.Get("logfmt") == skey:
case strings.EqualFold(ft.Name, skey):
default:
continue
}
if fv.Kind() == reflect.Ptr {
if fv.IsNil() {
t := fv.Type().Elem()
v := reflect.New(t)
fv.Set(v)
fv = v
}
fv = fv.Elem()
}
switch fv.Interface().(type) {
case time.Duration:
d, err := time.ParseDuration(string(val))
if err != nil {
return &UnmarshalTypeError{string(val), fv.Type()}
}
fv.Set(reflect.ValueOf(d))
case string:
fv.SetString(string(val))
case []byte:
b := make([]byte, len(val))
copy(b, val)
fv.SetBytes(b)
case bool:
fv.SetBool(true)
default:
switch {
case reflect.Int <= fv.Kind() && fv.Kind() <= reflect.Int64:
v, err := strconv.ParseInt(string(val), 10, 64)
if err != nil {
return err
}
fv.SetInt(v)
case reflect.Uint32 <= fv.Kind() && fv.Kind() <= reflect.Uint64:
v, err := strconv.ParseUint(string(val), 10, 64)
if err != nil {
return err
}
fv.SetUint(v)
case reflect.Float32 <= fv.Kind() && fv.Kind() <= reflect.Float64:
v, err := strconv.ParseFloat(string(val), 10)
if err != nil {
return err
}
fv.SetFloat(v)
default:
return &UnmarshalTypeError{string(val), fv.Type()}
}
}
}
return nil
}
// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
// (The argument to Unmarshal must be a non-nil pointer.)
type InvalidUnmarshalError struct {
Type reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "logfmt: Unmarshal(nil)"
}
if e.Type.Kind() != reflect.Ptr {
return "logfmt: Unmarshal(non-pointer " + e.Type.String() + ")"
}
return "logfmt: Unmarshal(nil " + e.Type.String() + ")"
}
// An UnmarshalTypeError describes a logfmt value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
Value string // the logfmt value
Type reflect.Type // type of Go value it could not be assigned to
}
func (e *UnmarshalTypeError) Error() string {
return "logfmt: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
}

149
vendor/github.com/kr/logfmt/scanner.go сгенерированный поставляемый
Просмотреть файл

@ -1,149 +0,0 @@
package logfmt
import (
"errors"
"fmt"
)
var ErrUnterminatedString = errors.New("logfmt: unterminated string")
func gotoScanner(data []byte, h Handler) (err error) {
saveError := func(e error) {
if err == nil {
err = e
}
}
var c byte
var i int
var m int
var key []byte
var val []byte
var ok bool
var esc bool
garbage:
if i == len(data) {
return
}
c = data[i]
switch {
case c > ' ' && c != '"' && c != '=':
key, val = nil, nil
m = i
i++
goto key
default:
i++
goto garbage
}
key:
if i >= len(data) {
if m >= 0 {
key = data[m:i]
saveError(h.HandleLogfmt(key, nil))
}
return
}
c = data[i]
switch {
case c > ' ' && c != '"' && c != '=':
i++
goto key
case c == '=':
key = data[m:i]
i++
goto equal
default:
key = data[m:i]
i++
saveError(h.HandleLogfmt(key, nil))
goto garbage
}
equal:
if i >= len(data) {
if m >= 0 {
i--
key = data[m:i]
saveError(h.HandleLogfmt(key, nil))
}
return
}
c = data[i]
switch {
case c > ' ' && c != '"' && c != '=':
m = i
i++
goto ivalue
case c == '"':
m = i
i++
esc = false
goto qvalue
default:
if key != nil {
saveError(h.HandleLogfmt(key, val))
}
i++
goto garbage
}
ivalue:
if i >= len(data) {
if m >= 0 {
val = data[m:i]
saveError(h.HandleLogfmt(key, val))
}
return
}
c = data[i]
switch {
case c > ' ' && c != '"' && c != '=':
i++
goto ivalue
default:
val = data[m:i]
saveError(h.HandleLogfmt(key, val))
i++
goto garbage
}
qvalue:
if i >= len(data) {
if m >= 0 {
saveError(ErrUnterminatedString)
}
return
}
c = data[i]
switch c {
case '\\':
i += 2
esc = true
goto qvalue
case '"':
i++
val = data[m:i]
if esc {
val, ok = unquoteBytes(val)
if !ok {
saveError(fmt.Errorf("logfmt: error unquoting bytes %q", string(val)))
goto garbage
}
} else {
val = val[1 : len(val)-1]
}
saveError(h.HandleLogfmt(key, val))
goto garbage
default:
i++
goto qvalue
}
}

149
vendor/github.com/kr/logfmt/unquote.go сгенерированный поставляемый
Просмотреть файл

@ -1,149 +0,0 @@
package logfmt
import (
"strconv"
"unicode"
"unicode/utf16"
"unicode/utf8"
)
// Taken from Go's encoding/json
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
// or it returns -1.
func getu4(s []byte) rune {
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
return -1
}
r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
if err != nil {
return -1
}
return rune(r)
}
// unquote converts a quoted JSON string literal s into an actual string t.
// The rules are different than for Go, so cannot use strconv.Unquote.
func unquote(s []byte) (t string, ok bool) {
s, ok = unquoteBytes(s)
t = string(s)
return
}
func unquoteBytes(s []byte) (t []byte, ok bool) {
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
return
}
s = s[1 : len(s)-1]
// Check for unusual characters. If there are none,
// then no unquoting is needed, so return a slice of the
// original bytes.
r := 0
for r < len(s) {
c := s[r]
if c == '\\' || c == '"' || c < ' ' {
break
}
if c < utf8.RuneSelf {
r++
continue
}
rr, size := utf8.DecodeRune(s[r:])
if rr == utf8.RuneError && size == 1 {
break
}
r += size
}
if r == len(s) {
return s, true
}
b := make([]byte, len(s)+2*utf8.UTFMax)
w := copy(b, s[0:r])
for r < len(s) {
// Out of room? Can only happen if s is full of
// malformed UTF-8 and we're replacing each
// byte with RuneError.
if w >= len(b)-2*utf8.UTFMax {
nb := make([]byte, (len(b)+utf8.UTFMax)*2)
copy(nb, b[0:w])
b = nb
}
switch c := s[r]; {
case c == '\\':
r++
if r >= len(s) {
return
}
switch s[r] {
default:
return
case '"', '\\', '/', '\'':
b[w] = s[r]
r++
w++
case 'b':
b[w] = '\b'
r++
w++
case 'f':
b[w] = '\f'
r++
w++
case 'n':
b[w] = '\n'
r++
w++
case 'r':
b[w] = '\r'
r++
w++
case 't':
b[w] = '\t'
r++
w++
case 'u':
r--
rr := getu4(s[r:])
if rr < 0 {
return
}
r += 6
if utf16.IsSurrogate(rr) {
rr1 := getu4(s[r:])
if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
// A valid pair; consume.
r += 6
w += utf8.EncodeRune(b[w:], dec)
break
}
// Invalid surrogate; fall back to replacement rune.
rr = unicode.ReplacementChar
}
w += utf8.EncodeRune(b[w:], rr)
}
// Quote, control characters are invalid.
case c == '"', c < ' ':
return
// ASCII
case c < utf8.RuneSelf:
b[w] = c
r++
w++
// Coerce to well-formed UTF-8.
default:
rr, size := utf8.DecodeRune(s[r:])
r += size
w += utf8.EncodeRune(b[w:], rr)
}
}
return b[0:w], true
}

8
vendor/github.com/pkg/errors/.travis.yml сгенерированный поставляемый
Просмотреть файл

@ -1,10 +1,10 @@
language: go
go_import_path: github.com/pkg/errors
go:
- 1.4.3
- 1.5.4
- 1.6.2
- 1.11.x
- 1.12.x
- 1.13.x
- tip
script:
- go test -v ./...
- make check

44
vendor/github.com/pkg/errors/Makefile сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,44 @@
PKGS := github.com/pkg/errors
SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
GO := go
check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
test:
$(GO) test $(PKGS)
vet: | test
$(GO) vet $(PKGS)
staticcheck:
$(GO) get honnef.co/go/tools/cmd/staticcheck
staticcheck -checks all $(PKGS)
misspell:
$(GO) get github.com/client9/misspell/cmd/misspell
misspell \
-locale GB \
-error \
*.md *.go
unconvert:
$(GO) get github.com/mdempsky/unconvert
unconvert -v $(PKGS)
ineffassign:
$(GO) get github.com/gordonklaus/ineffassign
find $(SRCDIRS) -name '*.go' | xargs ineffassign
pedantic: check errcheck
unparam:
$(GO) get mvdan.cc/unparam
unparam ./...
errcheck:
$(GO) get github.com/kisielk/errcheck
errcheck $(PKGS)
gofmt:
@echo Checking code is gofmted
@test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"

17
vendor/github.com/pkg/errors/README.md сгенерированный поставляемый
Просмотреть файл

@ -1,7 +1,9 @@
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors)
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
Package errors provides simple error handling primitives.
`go get github.com/pkg/errors`
The traditional error handling idiom in Go is roughly akin to
```go
if err != nil {
@ -39,12 +41,19 @@ default:
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
## Roadmap
With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
- 1.0. Final release.
## Contributing
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
Before proposing a change, please discuss your change by raising an issue.
Before sending a PR, please discuss your change by raising an issue.
## Licence
## License
BSD-2-Clause

210
vendor/github.com/pkg/errors/errors.go сгенерированный поставляемый
Просмотреть файл

@ -6,7 +6,7 @@
// return err
// }
//
// which applied recursively up the call stack results in error reports
// which when applied recursively up the call stack results in error reports
// without context or debugging information. The errors package allows
// programmers to add context to the failure path in their code in a way
// that does not destroy the original value of the error.
@ -14,13 +14,19 @@
// Adding context to an error
//
// The errors.Wrap function returns a new error that adds context to the
// original error. For example
// original error by recording a stack trace at the point Wrap is called,
// together with the supplied message. For example
//
// _, err := ioutil.ReadAll(r)
// if err != nil {
// return errors.Wrap(err, "read failed")
// }
//
// If additional control is required, the errors.WithStack and
// errors.WithMessage functions destructure errors.Wrap into its component
// operations: annotating an error with a stack trace and with a message,
// respectively.
//
// Retrieving the cause of an error
//
// Using errors.Wrap constructs a stack of errors, adding context to the
@ -28,12 +34,12 @@
// to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface
//
// type Causer interface {
// type causer interface {
// Cause() error
// }
//
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
// the topmost error which does not implement causer, which is assumed to be
// the topmost error that does not implement causer, which is assumed to be
// the original cause. For example:
//
// switch err := errors.Cause(err).(type) {
@ -43,13 +49,16 @@
// // unknown error
// }
//
// Although the causer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported
// be formatted by the fmt package. The following verbs are supported:
//
// %s print the error. If the error has a Cause it will be
// printed recursively
// printed recursively.
// %v see %s
// %+v extended format. Each Frame of the error's StackTrace will
// be printed in detail.
@ -57,13 +66,13 @@
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface.
// invoked. This information can be retrieved with the following interface:
//
// type stackTracer interface {
// StackTrace() errors.StackTrace
// }
//
// Where errors.StackTrace is defined as
// The returned errors.StackTrace type is defined as
//
// type StackTrace []Frame
//
@ -73,10 +82,13 @@
//
// if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f)
// fmt.Printf("%+s:%d\n", f, f)
// }
// }
//
// Although the stackTracer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// See the documentation for Frame.Format for more details.
package errors
@ -85,68 +97,77 @@ import (
"io"
)
// _error is an error implementation returned by New and Errorf
// that implements its own fmt.Formatter.
type _error struct {
msg string
*stack
}
func (e _error) Error() string { return e.msg }
func (e _error) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, e.msg)
fmt.Fprintf(s, "%+v", e.StackTrace())
return
}
fallthrough
case 's':
io.WriteString(s, e.msg)
}
}
// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
return _error{
message,
callers(),
return &fundamental{
msg: message,
stack: callers(),
}
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {
return _error{
fmt.Sprintf(format, args...),
return &fundamental{
msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func (f *fundamental) Error() string { return f.msg }
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &withStack{
err,
callers(),
}
}
type cause struct {
cause error
msg string
}
func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) }
func (c cause) Cause() error { return c.cause }
// wrapper is an error implementation returned by Wrap and Wrapf
// that implements its own fmt.Formatter.
type wrapper struct {
cause
type withStack struct {
error
*stack
}
func (w wrapper) Format(s fmt.State, verb rune) {
func (w *withStack) Cause() error { return w.error }
// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withStack) Unwrap() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
fmt.Fprintf(s, "%+v", w.StackTrace())
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
@ -157,33 +178,86 @@ func (w wrapper) Format(s fmt.State, verb rune) {
}
}
// Wrap returns an error annotating err with message.
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
return wrapper{
cause: cause{
cause: err,
msg: message,
},
stack: callers(),
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
// Wrapf returns an error annotating err with the format specifier.
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return wrapper{
cause: cause{
cause: err,
msg: fmt.Sprintf(format, args...),
},
stack: callers(),
err = &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
return &withStack{
err,
callers(),
}
}
// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
// WithMessagef annotates err with the format specifier.
// If err is nil, WithMessagef returns nil.
func WithMessagef(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
}
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withMessage) Unwrap() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
return
}
fallthrough
case 's', 'q':
io.WriteString(s, w.Error())
}
}
@ -191,7 +265,7 @@ func Wrapf(err error, format string, args ...interface{}) error {
// An error value has a cause if it implements the following
// interface:
//
// type Causer interface {
// type causer interface {
// Cause() error
// }
//

38
vendor/github.com/pkg/errors/go113.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,38 @@
// +build go1.13
package errors
import (
stderrors "errors"
)
// Is reports whether any error in err's chain matches target.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func Is(err, target error) bool { return stderrors.Is(err, target) }
// As finds the first error in err's chain that matches target, and if so, sets
// target to that error value and returns true.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// As will panic if target is not a non-nil pointer to either a type that implements
// error, or to any interface type. As returns false if err is nil.
func As(err error, target interface{}) bool { return stderrors.As(err, target) }
// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
return stderrors.Unwrap(err)
}

122
vendor/github.com/pkg/errors/stack.go сгенерированный поставляемый
Просмотреть файл

@ -5,10 +5,13 @@ import (
"io"
"path"
"runtime"
"strconv"
"strings"
)
// Frame represents a program counter inside a stack frame.
// For historical reasons if Frame is interpreted as a uintptr
// its value represents the program counter + 1.
type Frame uintptr
// pc returns the program counter for this frame;
@ -37,6 +40,15 @@ func (f Frame) line() int {
return line
}
// name returns the name of this function, if known.
func (f Frame) name() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
return fn.Name()
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
@ -46,29 +58,24 @@ func (f Frame) line() int {
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s path of source file relative to the compile time GOPATH
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
pc := f.pc()
fn := runtime.FuncForPC(pc)
if fn == nil {
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
io.WriteString(s, f.name())
io.WriteString(s, "\n\t")
io.WriteString(s, f.file())
default:
io.WriteString(s, path.Base(f.file()))
}
case 'd':
fmt.Fprintf(s, "%d", f.line())
io.WriteString(s, strconv.Itoa(f.line()))
case 'n':
name := runtime.FuncForPC(f.pc()).Name()
io.WriteString(s, funcname(name))
io.WriteString(s, funcname(f.name()))
case 'v':
f.Format(s, 's')
io.WriteString(s, ":")
@ -76,30 +83,75 @@ func (f Frame) Format(s fmt.State, verb rune) {
}
}
// MarshalText formats a stacktrace Frame as a text string. The output is the
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
func (f Frame) MarshalText() ([]byte, error) {
name := f.name()
if name == "unknown" {
return []byte(name), nil
}
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame
// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
fmt.Fprintf(s, "\n%+v", f)
io.WriteString(s, "\n")
f.Format(s, verb)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
fmt.Fprintf(s, "%v", []Frame(st))
st.formatSlice(s, verb)
}
case 's':
fmt.Fprintf(s, "%s", []Frame(st))
st.formatSlice(s, verb)
}
}
// formatSlice will format this StackTrace into the given buffer as a slice of
// Frame, only valid when called with '%s' or '%v'.
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
io.WriteString(s, "[")
for i, f := range st {
if i > 0 {
io.WriteString(s, " ")
}
f.Format(s, verb)
}
io.WriteString(s, "]")
}
// stack represents a stack of program counters.
type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
@ -123,43 +175,3 @@ func funcname(name string) string {
i = strings.Index(name, ".")
return name[i+1:]
}
func trimGOPATH(name, file string) string {
// Here we want to get the source file path relative to the compile time
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
// GOPATH at runtime, but we can infer the number of path segments in the
// GOPATH. We note that fn.Name() returns the function name qualified by
// the import path, which does not include the GOPATH. Thus we can trim
// segments from the beginning of the file path until the number of path
// separators remaining is one more than the number of path separators in
// the function name. For example, given:
//
// GOPATH /home/user
// file /home/user/src/pkg/sub/file.go
// fn.Name() pkg/sub.Type.Method
//
// We want to produce:
//
// pkg/sub/file.go
//
// From this we can easily see that fn.Name() has one less path separator
// than our desired output. We count separators from the end of the file
// path until it finds two more than in the function name and then move
// one character forward to preserve the initial path segment without a
// leading separator.
const sep = "/"
goal := strings.Count(name, sep) + 2
i := len(file)
for n := 0; n < goal; n++ {
i = strings.LastIndex(file[:i], sep)
if i == -1 {
// not enough separators found, set i so that the slice expression
// below leaves file unmodified
i = -len(sep)
break
}
}
// get back to 0 or trim the leading separator
file = file[i+len(sep):]
return file
}

29
vendor/modules.txt поставляемый
Просмотреть файл

@ -1,6 +1,13 @@
# github.com/Azure/azure-docker-extension v0.0.0-20160802215703-0dd2f199467d
## explicit
github.com/Azure/azure-docker-extension/pkg/vmextension
# github.com/Azure/azure-extension-platform v0.0.0-20240327184133-73b5b3b55955
## explicit; go 1.21
github.com/Azure/azure-extension-platform/pkg/extensionerrors
github.com/Azure/azure-extension-platform/pkg/extensionevents
github.com/Azure/azure-extension-platform/pkg/handlerenv
github.com/Azure/azure-extension-platform/pkg/logging
github.com/Azure/azure-extension-platform/pkg/utils
# github.com/cilium/ebpf v0.9.1
## explicit; go 1.17
github.com/cilium/ebpf
@ -26,25 +33,21 @@ github.com/davecgh/go-spew/spew
# github.com/docker/go-units v0.4.0
## explicit
github.com/docker/go-units
# github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2
## explicit
github.com/go-kit/kit/log
# github.com/go-logfmt/logfmt v0.2.1-0.20160601130801-d4327190ff83
## explicit
# github.com/go-kit/log v0.2.0
## explicit; go 1.17
github.com/go-kit/log
# github.com/go-logfmt/logfmt v0.5.1
## explicit; go 1.17
github.com/go-logfmt/logfmt
# github.com/go-stack/stack v1.5.2
## explicit
github.com/go-stack/stack
# github.com/godbus/dbus/v5 v5.0.4
## explicit; go 1.12
github.com/godbus/dbus/v5
# github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515
## explicit
github.com/kr/logfmt
# github.com/google/go-cmp v0.5.8
## explicit; go 1.13
# github.com/opencontainers/runtime-spec v1.0.2
## explicit
github.com/opencontainers/runtime-spec/specs-go
# github.com/pkg/errors v0.7.1-0.20160627222352-a2d6902c6d2a
# github.com/pkg/errors v0.9.1
## explicit
github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0
@ -71,8 +74,6 @@ github.com/xeipuuv/gojsonschema
golang.org/x/sys/internal/unsafeheader
golang.org/x/sys/unix
golang.org/x/sys/windows
# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
## explicit; go 1.11
# google.golang.org/protobuf v1.27.1
## explicit; go 1.9
google.golang.org/protobuf/encoding/prototext