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:
Родитель
e8e69f4a0b
Коммит
b56f2ad074
|
@ -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,
|
||||
|
|
|
@ -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 }}
|
||||
|
||||
|
|
|
@ -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
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
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)
|
||||
}
|
||||
}
|
81
main/cmds.go
81
main/cmds.go
|
@ -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
|
||||
|
|
59
main/main.go
59
main/main.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
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
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
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
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
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
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
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
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
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
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
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")
|
||||
}
|
|
@ -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...)
|
||||
}
|
|
@ -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)
|
||||
)
|
|
@ -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
сгенерированный
поставляемый
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
сгенерированный
поставляемый
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
сгенерированный
поставляемый
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
|
7
vendor/github.com/go-kit/kit/log/json_logger.go → vendor/github.com/go-kit/log/json_logger.go
сгенерированный
поставляемый
7
vendor/github.com/go-kit/kit/log/json_logger.go → vendor/github.com/go-kit/log/json_logger.go
сгенерированный
поставляемый
|
@ -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
|
|
@ -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...)
|
||||
}
|
0
vendor/github.com/go-kit/kit/log/logfmt_logger.go → vendor/github.com/go-kit/log/logfmt_logger.go
сгенерированный
поставляемый
0
vendor/github.com/go-kit/kit/log/logfmt_logger.go → vendor/github.com/go-kit/log/logfmt_logger.go
сгенерированный
поставляемый
0
vendor/github.com/go-kit/kit/log/nop_logger.go → vendor/github.com/go-kit/log/nop_logger.go
сгенерированный
поставляемый
0
vendor/github.com/go-kit/kit/log/nop_logger.go → vendor/github.com/go-kit/log/nop_logger.go
сгенерированный
поставляемый
49
vendor/github.com/go-kit/kit/log/stdlib.go → vendor/github.com/go-kit/log/stdlib.go
сгенерированный
поставляемый
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
сгенерированный
поставляемый
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...)
|
||||
}
|
|
@ -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)
|
||||
)
|
|
@ -1,4 +1 @@
|
|||
_testdata/
|
||||
_testdata2/
|
||||
logfmt-fuzz.zip
|
||||
logfmt.test.exe
|
||||
.vscode/
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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'.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
*.test
|
||||
*.swp
|
||||
*.prof
|
|
@ -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.
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)"
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
// }
|
||||
//
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче