mirror of https://github.com/docker/cli.git
Merge pull request #4554 from thaJeztah/update_engine
vendor: github.com/docker/docker 06499c52e2b1 (v25.0.0-dev)
This commit is contained in:
commit
d4aca90d19
10
vendor.mod
10
vendor.mod
|
@ -13,7 +13,7 @@ require (
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/distribution/reference v0.0.0-20230830145923-e42074f83a9c
|
github.com/distribution/reference v0.0.0-20230830145923-e42074f83a9c
|
||||||
github.com/docker/distribution v2.8.2+incompatible
|
github.com/docker/distribution v2.8.2+incompatible
|
||||||
github.com/docker/docker v24.0.0-rc.2.0.20230905130451-032797ea4bcb+incompatible // master (v25.0.0-dev)
|
github.com/docker/docker v24.0.0-rc.2.0.20230907222536-06499c52e2b1+incompatible // master (v25.0.0-dev)
|
||||||
github.com/docker/docker-credential-helpers v0.8.0
|
github.com/docker/docker-credential-helpers v0.8.0
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
|
@ -55,6 +55,9 @@ require (
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||||
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
@ -71,6 +74,11 @@ require (
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
go.etcd.io/etcd/raft/v3 v3.5.6 // indirect
|
go.etcd.io/etcd/raft/v3 v3.5.6 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.4.1 // indirect
|
||||||
|
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.4.1 // indirect
|
||||||
golang.org/x/crypto v0.2.0 // indirect
|
golang.org/x/crypto v0.2.0 // indirect
|
||||||
golang.org/x/mod v0.9.0 // indirect
|
golang.org/x/mod v0.9.0 // indirect
|
||||||
golang.org/x/net v0.10.0 // indirect
|
golang.org/x/net v0.10.0 // indirect
|
||||||
|
|
31
vendor.sum
31
vendor.sum
|
@ -25,6 +25,7 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj
|
||||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
|
||||||
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||||
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
@ -52,8 +53,8 @@ github.com/distribution/reference v0.0.0-20230830145923-e42074f83a9c/go.mod h1:B
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v24.0.0-rc.2.0.20230905130451-032797ea4bcb+incompatible h1:1UUPAB9PAPUbM10+qIKPL5tKZ+VAddWgbQUf+x1QBfI=
|
github.com/docker/docker v24.0.0-rc.2.0.20230907222536-06499c52e2b1+incompatible h1:wSeDxA5kbar3It3D8VVHH50TVWwEs7sAT+UO3UtFtlU=
|
||||||
github.com/docker/docker v24.0.0-rc.2.0.20230905130451-032797ea4bcb+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v24.0.0-rc.2.0.20230907222536-06499c52e2b1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
|
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
|
||||||
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
|
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
|
@ -71,6 +72,9 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU
|
||||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||||
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
|
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
|
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
|
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
|
||||||
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
|
@ -78,7 +82,11 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||||
|
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
@ -102,6 +110,7 @@ github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519
|
||||||
github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY=
|
github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
@ -110,6 +119,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3
|
||||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
@ -261,6 +271,23 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ=
|
||||||
go.etcd.io/etcd/raft/v3 v3.5.6 h1:tOmx6Ym6rn2GpZOrvTGJZciJHek6RnC3U/zNInzIN50=
|
go.etcd.io/etcd/raft/v3 v3.5.6 h1:tOmx6Ym6rn2GpZOrvTGJZciJHek6RnC3U/zNInzIN50=
|
||||||
go.etcd.io/etcd/raft/v3 v3.5.6/go.mod h1:wL8kkRGx1Hp8FmZUuHfL3K2/OaGIDaXGr1N7i2G07J0=
|
go.etcd.io/etcd/raft/v3 v3.5.6/go.mod h1:wL8kkRGx1Hp8FmZUuHfL3K2/OaGIDaXGr1N7i2G07J0=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 h1:SLme4Porm+UwX0DdHMxlwRt7FzPSE0sys81bet2o0pU=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII=
|
||||||
|
go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
|
||||||
|
go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g=
|
||||||
|
go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 h1:imIM3vRDMyZK1ypQlQlO+brE22I9lRhJsBDXpDWjlz8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 h1:WPpPsAAs8I2rA47v5u0558meKmmwm1Dj99ZbqCV8sZ8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1 h1:8qOago/OqoFclMUUj/184tZyRdDZFpcejSjbk5Jrl6Y=
|
||||||
|
go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk=
|
||||||
|
go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw=
|
||||||
|
go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ=
|
||||||
|
go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y=
|
||||||
|
go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
|
||||||
|
go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ=
|
||||||
|
go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc=
|
||||||
|
go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||||
|
|
|
@ -56,6 +56,8 @@ import (
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DummyHost is a hostname used for local communication.
|
// DummyHost is a hostname used for local communication.
|
||||||
|
@ -123,6 +125,12 @@ type Client struct {
|
||||||
|
|
||||||
// negotiated indicates that API version negotiation took place
|
// negotiated indicates that API version negotiation took place
|
||||||
negotiated bool
|
negotiated bool
|
||||||
|
|
||||||
|
tp trace.TracerProvider
|
||||||
|
|
||||||
|
// When the client transport is an *http.Transport (default) we need to do some extra things (like closing idle connections).
|
||||||
|
// Store the original transport as the http.Client transport will be wrapped with tracing libs.
|
||||||
|
baseTransport *http.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
|
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
|
||||||
|
@ -188,6 +196,12 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tr, ok := c.client.Transport.(*http.Transport); ok {
|
||||||
|
// Store the base transport before we wrap it in tracing libs below
|
||||||
|
// This is used, as an example, to close idle connections when the client is closed
|
||||||
|
c.baseTransport = tr
|
||||||
|
}
|
||||||
|
|
||||||
if c.scheme == "" {
|
if c.scheme == "" {
|
||||||
// TODO(stevvooe): This isn't really the right way to write clients in Go.
|
// TODO(stevvooe): This isn't really the right way to write clients in Go.
|
||||||
// `NewClient` should probably only take an `*http.Client` and work from there.
|
// `NewClient` should probably only take an `*http.Client` and work from there.
|
||||||
|
@ -201,9 +215,24 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.client.Transport = otelhttp.NewTransport(
|
||||||
|
c.client.Transport,
|
||||||
|
otelhttp.WithTracerProvider(c.tp),
|
||||||
|
otelhttp.WithSpanNameFormatter(func(_ string, req *http.Request) string {
|
||||||
|
return req.Method + " " + req.URL.Path
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *Client) tlsConfig() *tls.Config {
|
||||||
|
if cli.baseTransport == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cli.baseTransport.TLSClientConfig
|
||||||
|
}
|
||||||
|
|
||||||
func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) {
|
func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) {
|
||||||
transport := &http.Transport{}
|
transport := &http.Transport{}
|
||||||
err := sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
|
err := sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
|
||||||
|
@ -216,20 +245,11 @@ func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tlsConfig returns the TLS configuration from the client's transport.
|
|
||||||
// It returns nil if the transport is not a [http.Transport], or if no
|
|
||||||
// TLSClientConfig is set.
|
|
||||||
func (cli *Client) tlsConfig() *tls.Config {
|
|
||||||
if tr, ok := cli.client.Transport.(*http.Transport); ok {
|
|
||||||
return tr.TLSClientConfig
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the transport used by the client
|
// Close the transport used by the client
|
||||||
func (cli *Client) Close() error {
|
func (cli *Client) Close() error {
|
||||||
if t, ok := cli.client.Transport.(*http.Transport); ok {
|
if cli.baseTransport != nil {
|
||||||
t.CloseIdleConnections()
|
cli.baseTransport.CloseIdleConnections()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -356,6 +376,20 @@ func ParseHostURL(host string) (*url.URL, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *Client) dialerFromTransport() func(context.Context, string, string) (net.Conn, error) {
|
||||||
|
if cli.baseTransport == nil || cli.baseTransport.DialContext == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.baseTransport.TLSClientConfig != nil {
|
||||||
|
// When using a tls config we don't use the configured dialer but instead a fallback dialer...
|
||||||
|
// Note: It seems like this should use the normal dialer and wrap the returned net.Conn in a tls.Conn
|
||||||
|
// I honestly don't know why it doesn't do that, but it doesn't and such a change is entirely unrelated to the change in this commit.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cli.baseTransport.DialContext
|
||||||
|
}
|
||||||
|
|
||||||
// Dialer returns a dialer for a raw stream connection, with an HTTP/1.1 header,
|
// Dialer returns a dialer for a raw stream connection, with an HTTP/1.1 header,
|
||||||
// that can be used for proxying the daemon connection. It is used by
|
// that can be used for proxying the daemon connection. It is used by
|
||||||
// ["docker dial-stdio"].
|
// ["docker dial-stdio"].
|
||||||
|
@ -363,10 +397,8 @@ func ParseHostURL(host string) (*url.URL, error) {
|
||||||
// ["docker dial-stdio"]: https://github.com/docker/cli/pull/1014
|
// ["docker dial-stdio"]: https://github.com/docker/cli/pull/1014
|
||||||
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
|
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
|
||||||
return func(ctx context.Context) (net.Conn, error) {
|
return func(ctx context.Context) (net.Conn, error) {
|
||||||
if transport, ok := cli.client.Transport.(*http.Transport); ok {
|
if dialFn := cli.dialerFromTransport(); dialFn != nil {
|
||||||
if transport.DialContext != nil && transport.TLSClientConfig == nil {
|
return dialFn(ctx, cli.proto, cli.addr)
|
||||||
return transport.DialContext(ctx, cli.proto, cli.addr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
switch cli.proto {
|
switch cli.proto {
|
||||||
case "unix":
|
case "unix":
|
||||||
|
|
|
@ -13,6 +13,11 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// postHijacked sends a POST request and hijacks the connection.
|
// postHijacked sends a POST request and hijacks the connection.
|
||||||
|
@ -45,11 +50,32 @@ func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[s
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, string, error) {
|
func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn, _ string, retErr error) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
req.Header.Set("Connection", "Upgrade")
|
req.Header.Set("Connection", "Upgrade")
|
||||||
req.Header.Set("Upgrade", proto)
|
req.Header.Set("Upgrade", proto)
|
||||||
|
|
||||||
|
// We aren't using the configured RoundTripper here so manually inject the trace context
|
||||||
|
tp := cli.tp
|
||||||
|
if tp == nil {
|
||||||
|
if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
|
||||||
|
tp = span.TracerProvider()
|
||||||
|
} else {
|
||||||
|
tp = otel.GetTracerProvider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, span := tp.Tracer("").Start(ctx, req.Method+" "+req.URL.Path)
|
||||||
|
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(req)...)
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
span.RecordError(retErr)
|
||||||
|
span.SetStatus(codes.Error, retErr.Error())
|
||||||
|
}
|
||||||
|
span.End()
|
||||||
|
}()
|
||||||
|
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
|
||||||
|
|
||||||
dialer := cli.Dialer()
|
dialer := cli.Dialer()
|
||||||
conn, err := dialer(ctx)
|
conn, err := dialer(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,6 +97,9 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, s
|
||||||
|
|
||||||
// Server hijacks the connection, error 'connection closed' expected
|
// Server hijacks the connection, error 'connection closed' expected
|
||||||
resp, err := clientconn.Do(req)
|
resp, err := clientconn.Do(req)
|
||||||
|
if resp != nil {
|
||||||
|
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:staticcheck // ignore SA1019 for connecting to old (pre go1.8) daemons
|
//nolint:staticcheck // ignore SA1019 for connecting to old (pre go1.8) daemons
|
||||||
if err != httputil.ErrPersistEOF {
|
if err != httputil.ErrPersistEOF {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Opt is a configuration option to initialize a [Client].
|
// Opt is a configuration option to initialize a [Client].
|
||||||
|
@ -221,3 +222,12 @@ func WithAPIVersionNegotiation() Opt {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTraceProvider sets the trace provider for the client.
|
||||||
|
// If this is not set then the global trace provider will be used.
|
||||||
|
func WithTraceProvider(provider trace.TracerProvider) Opt {
|
||||||
|
return func(c *Client) error {
|
||||||
|
c.tp = provider
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,17 +10,11 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/user"
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
entOnce sync.Once
|
|
||||||
getentCmd string
|
|
||||||
)
|
|
||||||
|
|
||||||
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
|
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
|
||||||
path, err := filepath.Abs(path)
|
path, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,10 +155,10 @@ func getentGroup(name string) (user.Group, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func callGetent(database, key string) (io.Reader, error) {
|
func callGetent(database, key string) (io.Reader, error) {
|
||||||
entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
|
getentCmd, err := resolveBinary("getent")
|
||||||
// if no `getent` command on host, can't do anything else
|
// if no `getent` command within the execution environment, can't do anything else
|
||||||
if getentCmd == "" {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to find getent command")
|
return nil, fmt.Errorf("unable to find getent command: %w", err)
|
||||||
}
|
}
|
||||||
command := exec.Command(getentCmd, database, key)
|
command := exec.Command(getentCmd, database, key)
|
||||||
// we run getent within container filesystem, but without /dev so /dev/null is not available for exec to mock stdin
|
// we run getent within container filesystem, but without /dev so /dev/null is not available for exec to mock stdin
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
package system // import "github.com/docker/docker/pkg/system"
|
||||||
|
|
||||||
import (
|
import "errors"
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNotSupportedPlatform means the platform is not supported.
|
// ErrNotSupportedPlatform means the platform is not supported.
|
||||||
ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
||||||
|
|
||||||
// ErrNotSupportedOperatingSystem means the operating system is not supported.
|
|
||||||
ErrNotSupportedOperatingSystem = errors.New("operating system is not supported")
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsOSSupported determines if an operating system is supported by the host.
|
|
||||||
func IsOSSupported(os string) bool {
|
|
||||||
return strings.EqualFold(runtime.GOOS, os)
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNotSupportedOperatingSystem means the operating system is not supported.
|
||||||
|
//
|
||||||
|
// Deprecated: use [github.com/docker/docker/image.CheckOS] and check the error returned.
|
||||||
|
var ErrNotSupportedOperatingSystem = errors.New("operating system is not supported")
|
||||||
|
|
||||||
|
// IsOSSupported determines if an operating system is supported by the host.
|
||||||
|
//
|
||||||
|
// Deprecated: use [github.com/docker/docker/image.CheckOS] and check the error returned.
|
||||||
|
func IsOSSupported(os string) bool {
|
||||||
|
return strings.EqualFold(runtime.GOOS, os)
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,10 @@
|
||||||
|
.PHONY: ci generate clean
|
||||||
|
|
||||||
|
ci: clean generate
|
||||||
|
go test -v ./...
|
||||||
|
|
||||||
|
generate:
|
||||||
|
go generate .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf *_generated*.go
|
|
@ -0,0 +1,95 @@
|
||||||
|
# httpsnoop
|
||||||
|
|
||||||
|
Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||||
|
response time, bytes written, and http status code) from your application's
|
||||||
|
http.Handlers.
|
||||||
|
|
||||||
|
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
|
||||||
|
which is also exposed for users interested in a more low-level API.
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/felixge/httpsnoop?status.svg)](https://godoc.org/github.com/felixge/httpsnoop)
|
||||||
|
[![Build Status](https://travis-ci.org/felixge/httpsnoop.svg?branch=master)](https://travis-ci.org/felixge/httpsnoop)
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
// myH is your app's http handler, perhaps a http.ServeMux or similar.
|
||||||
|
var myH http.Handler
|
||||||
|
// wrappedH wraps myH in order to log every request.
|
||||||
|
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
m := httpsnoop.CaptureMetrics(myH, w, r)
|
||||||
|
log.Printf(
|
||||||
|
"%s %s (code=%d dt=%s written=%d)",
|
||||||
|
r.Method,
|
||||||
|
r.URL,
|
||||||
|
m.Code,
|
||||||
|
m.Duration,
|
||||||
|
m.Written,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
http.ListenAndServe(":8080", wrappedH)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why this package exists
|
||||||
|
|
||||||
|
Instrumenting an application's http.Handler is surprisingly difficult.
|
||||||
|
|
||||||
|
However if you google for e.g. "capture ResponseWriter status code" you'll find
|
||||||
|
lots of advise and code examples that suggest it to be a fairly trivial
|
||||||
|
undertaking. Unfortunately everything I've seen so far has a high chance of
|
||||||
|
breaking your application.
|
||||||
|
|
||||||
|
The main problem is that a `http.ResponseWriter` often implements additional
|
||||||
|
interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
|
||||||
|
`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
|
||||||
|
in your own struct that also implements the `http.ResponseWriter` interface
|
||||||
|
will hide the additional interfaces mentioned above. This has a high change of
|
||||||
|
introducing subtle bugs into any non-trivial application.
|
||||||
|
|
||||||
|
Another approach I've seen people take is to return a struct that implements
|
||||||
|
all of the interfaces above. However, that's also problematic, because it's
|
||||||
|
difficult to fake some of these interfaces behaviors when the underlying
|
||||||
|
`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
|
||||||
|
because an application may choose to operate differently, merely because it
|
||||||
|
detects the presence of these additional interfaces.
|
||||||
|
|
||||||
|
This package solves this problem by checking which additional interfaces a
|
||||||
|
`http.ResponseWriter` implements, returning a wrapped version implementing the
|
||||||
|
exact same set of interfaces.
|
||||||
|
|
||||||
|
Additionally this package properly handles edge cases such as `WriteHeader` not
|
||||||
|
being called, or called more than once, as well as concurrent calls to
|
||||||
|
`http.ResponseWriter` methods, and even calls happening after the wrapped
|
||||||
|
`ServeHTTP` has already returned.
|
||||||
|
|
||||||
|
Unfortunately this package is not perfect either. It's possible that it is
|
||||||
|
still missing some interfaces provided by the go core (let me know if you find
|
||||||
|
one), and it won't work for applications adding their own interfaces into the
|
||||||
|
mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
|
||||||
|
`http.ResponseWriter` and type-assert the result to its other interfaces.
|
||||||
|
|
||||||
|
However, hopefully the explanation above has sufficiently scared you of rolling
|
||||||
|
your own solution to this problem. httpsnoop may still break your application,
|
||||||
|
but at least it tries to avoid it as much as possible.
|
||||||
|
|
||||||
|
Anyway, the real problem here is that smuggling additional interfaces inside
|
||||||
|
`http.ResponseWriter` is a problematic design choice, but it probably goes as
|
||||||
|
deep as the Go language specification itself. But that's okay, I still prefer
|
||||||
|
Go over the alternatives ;).
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkBaseline-8 20000 94912 ns/op
|
||||||
|
BenchmarkCaptureMetrics-8 20000 95461 ns/op
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
|
||||||
|
overhead of ~500 ns per http request on my machine. However, the margin of
|
||||||
|
error appears to be larger than that, therefor it should be reasonable to
|
||||||
|
assume that the overhead introduced by `CaptureMetrics` is absolutely
|
||||||
|
negligible.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
|
@ -0,0 +1,86 @@
|
||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metrics holds metrics captured from CaptureMetrics.
|
||||||
|
type Metrics struct {
|
||||||
|
// Code is the first http response code passed to the WriteHeader func of
|
||||||
|
// the ResponseWriter. If no such call is made, a default code of 200 is
|
||||||
|
// assumed instead.
|
||||||
|
Code int
|
||||||
|
// Duration is the time it took to execute the handler.
|
||||||
|
Duration time.Duration
|
||||||
|
// Written is the number of bytes successfully written by the Write or
|
||||||
|
// ReadFrom function of the ResponseWriter. ResponseWriters may also write
|
||||||
|
// data to their underlaying connection directly (e.g. headers), but those
|
||||||
|
// are not tracked. Therefor the number of Written bytes will usually match
|
||||||
|
// the size of the response body.
|
||||||
|
Written int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
|
||||||
|
// returns the metrics it captured from it.
|
||||||
|
func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
|
||||||
|
return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
|
||||||
|
hnd.ServeHTTP(ww, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
|
||||||
|
// resulting metrics. This is very similar to CaptureMetrics (which is just
|
||||||
|
// sugar on top of this func), but is a more usable interface if your
|
||||||
|
// application doesn't use the Go http.Handler interface.
|
||||||
|
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
|
||||||
|
m := Metrics{Code: http.StatusOK}
|
||||||
|
m.CaptureMetrics(w, fn)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMetrics wraps w and calls fn with the wrapped w and updates
|
||||||
|
// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
|
||||||
|
// but allows one to customize starting Metrics object.
|
||||||
|
func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
|
||||||
|
var (
|
||||||
|
start = time.Now()
|
||||||
|
headerWritten bool
|
||||||
|
hooks = Hooks{
|
||||||
|
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
|
||||||
|
return func(code int) {
|
||||||
|
next(code)
|
||||||
|
|
||||||
|
if !headerWritten {
|
||||||
|
m.Code = code
|
||||||
|
headerWritten = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Write: func(next WriteFunc) WriteFunc {
|
||||||
|
return func(p []byte) (int, error) {
|
||||||
|
n, err := next(p)
|
||||||
|
|
||||||
|
m.Written += int64(n)
|
||||||
|
headerWritten = true
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ReadFrom: func(next ReadFromFunc) ReadFromFunc {
|
||||||
|
return func(src io.Reader) (int64, error) {
|
||||||
|
n, err := next(src)
|
||||||
|
|
||||||
|
headerWritten = true
|
||||||
|
m.Written += n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fn(Wrap(w, hooks))
|
||||||
|
m.Duration += time.Since(start)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||||
|
// response time, bytes written, and http status code) from your application's
|
||||||
|
// http.Handlers.
|
||||||
|
//
|
||||||
|
// Doing this requires non-trivial wrapping of the http.ResponseWriter
|
||||||
|
// interface, which is also exposed for users interested in a more low-level
|
||||||
|
// API.
|
||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
//go:generate go run codegen/main.go
|
|
@ -0,0 +1,436 @@
|
||||||
|
// +build go1.8
|
||||||
|
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
|
||||||
|
|
||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type HeaderFunc func() http.Header
|
||||||
|
|
||||||
|
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteHeaderFunc func(code int)
|
||||||
|
|
||||||
|
// WriteFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteFunc func(b []byte) (int, error)
|
||||||
|
|
||||||
|
// FlushFunc is part of the http.Flusher interface.
|
||||||
|
type FlushFunc func()
|
||||||
|
|
||||||
|
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||||
|
type CloseNotifyFunc func() <-chan bool
|
||||||
|
|
||||||
|
// HijackFunc is part of the http.Hijacker interface.
|
||||||
|
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||||
|
|
||||||
|
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||||
|
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||||
|
|
||||||
|
// PushFunc is part of the http.Pusher interface.
|
||||||
|
type PushFunc func(target string, opts *http.PushOptions) error
|
||||||
|
|
||||||
|
// Hooks defines a set of method interceptors for methods included in
|
||||||
|
// http.ResponseWriter as well as some others. You can think of them as
|
||||||
|
// middleware for the function calls they target. See Wrap for more details.
|
||||||
|
type Hooks struct {
|
||||||
|
Header func(HeaderFunc) HeaderFunc
|
||||||
|
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||||
|
Write func(WriteFunc) WriteFunc
|
||||||
|
Flush func(FlushFunc) FlushFunc
|
||||||
|
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||||
|
Hijack func(HijackFunc) HijackFunc
|
||||||
|
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||||
|
Push func(PushFunc) PushFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||||
|
// as w. Specifically if w implements any combination of:
|
||||||
|
//
|
||||||
|
// - http.Flusher
|
||||||
|
// - http.CloseNotifier
|
||||||
|
// - http.Hijacker
|
||||||
|
// - io.ReaderFrom
|
||||||
|
// - http.Pusher
|
||||||
|
//
|
||||||
|
// The wrapped version will implement the exact same combination. If no hooks
|
||||||
|
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||||
|
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||||
|
// method they target and may modify the call's arguments and/or return values.
|
||||||
|
// The CaptureMetrics implementation serves as a working example for how the
|
||||||
|
// hooks can be used.
|
||||||
|
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||||
|
rw := &rw{w: w, h: hooks}
|
||||||
|
_, i0 := w.(http.Flusher)
|
||||||
|
_, i1 := w.(http.CloseNotifier)
|
||||||
|
_, i2 := w.(http.Hijacker)
|
||||||
|
_, i3 := w.(io.ReaderFrom)
|
||||||
|
_, i4 := w.(http.Pusher)
|
||||||
|
switch {
|
||||||
|
// combination 1/32
|
||||||
|
case !i0 && !i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
}{rw, rw}
|
||||||
|
// combination 2/32
|
||||||
|
case !i0 && !i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 3/32
|
||||||
|
case !i0 && !i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 4/32
|
||||||
|
case !i0 && !i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 5/32
|
||||||
|
case !i0 && !i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 6/32
|
||||||
|
case !i0 && !i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 7/32
|
||||||
|
case !i0 && !i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 8/32
|
||||||
|
case !i0 && !i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 9/32
|
||||||
|
case !i0 && i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 10/32
|
||||||
|
case !i0 && i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 11/32
|
||||||
|
case !i0 && i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 12/32
|
||||||
|
case !i0 && i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 13/32
|
||||||
|
case !i0 && i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 14/32
|
||||||
|
case !i0 && i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 15/32
|
||||||
|
case !i0 && i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 16/32
|
||||||
|
case !i0 && i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 17/32
|
||||||
|
case i0 && !i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 18/32
|
||||||
|
case i0 && !i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 19/32
|
||||||
|
case i0 && !i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 20/32
|
||||||
|
case i0 && !i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 21/32
|
||||||
|
case i0 && !i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 22/32
|
||||||
|
case i0 && !i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 23/32
|
||||||
|
case i0 && !i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 24/32
|
||||||
|
case i0 && !i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 25/32
|
||||||
|
case i0 && i1 && !i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 26/32
|
||||||
|
case i0 && i1 && !i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 27/32
|
||||||
|
case i0 && i1 && !i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 28/32
|
||||||
|
case i0 && i1 && !i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 29/32
|
||||||
|
case i0 && i1 && i2 && !i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 30/32
|
||||||
|
case i0 && i1 && i2 && !i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 31/32
|
||||||
|
case i0 && i1 && i2 && i3 && !i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
// combination 32/32
|
||||||
|
case i0 && i1 && i2 && i3 && i4:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Pusher
|
||||||
|
}{rw, rw, rw, rw, rw, rw, rw}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
type rw struct {
|
||||||
|
w http.ResponseWriter
|
||||||
|
h Hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Unwrap() http.ResponseWriter {
|
||||||
|
return w.w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Header() http.Header {
|
||||||
|
f := w.w.(http.ResponseWriter).Header
|
||||||
|
if w.h.Header != nil {
|
||||||
|
f = w.h.Header(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) WriteHeader(code int) {
|
||||||
|
f := w.w.(http.ResponseWriter).WriteHeader
|
||||||
|
if w.h.WriteHeader != nil {
|
||||||
|
f = w.h.WriteHeader(f)
|
||||||
|
}
|
||||||
|
f(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Write(b []byte) (int, error) {
|
||||||
|
f := w.w.(http.ResponseWriter).Write
|
||||||
|
if w.h.Write != nil {
|
||||||
|
f = w.h.Write(f)
|
||||||
|
}
|
||||||
|
return f(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Flush() {
|
||||||
|
f := w.w.(http.Flusher).Flush
|
||||||
|
if w.h.Flush != nil {
|
||||||
|
f = w.h.Flush(f)
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) CloseNotify() <-chan bool {
|
||||||
|
f := w.w.(http.CloseNotifier).CloseNotify
|
||||||
|
if w.h.CloseNotify != nil {
|
||||||
|
f = w.h.CloseNotify(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
f := w.w.(http.Hijacker).Hijack
|
||||||
|
if w.h.Hijack != nil {
|
||||||
|
f = w.h.Hijack(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||||
|
f := w.w.(io.ReaderFrom).ReadFrom
|
||||||
|
if w.h.ReadFrom != nil {
|
||||||
|
f = w.h.ReadFrom(f)
|
||||||
|
}
|
||||||
|
return f(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Push(target string, opts *http.PushOptions) error {
|
||||||
|
f := w.w.(http.Pusher).Push
|
||||||
|
if w.h.Push != nil {
|
||||||
|
f = w.h.Push(f)
|
||||||
|
}
|
||||||
|
return f(target, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unwrapper interface {
|
||||||
|
Unwrap() http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||||
|
// layers of httpsnoop wrappers.
|
||||||
|
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||||
|
if rw, ok := w.(Unwrapper); ok {
|
||||||
|
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||||
|
return Unwrap(rw.Unwrap())
|
||||||
|
} else {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,278 @@
|
||||||
|
// +build !go1.8
|
||||||
|
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
|
||||||
|
|
||||||
|
package httpsnoop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type HeaderFunc func() http.Header
|
||||||
|
|
||||||
|
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteHeaderFunc func(code int)
|
||||||
|
|
||||||
|
// WriteFunc is part of the http.ResponseWriter interface.
|
||||||
|
type WriteFunc func(b []byte) (int, error)
|
||||||
|
|
||||||
|
// FlushFunc is part of the http.Flusher interface.
|
||||||
|
type FlushFunc func()
|
||||||
|
|
||||||
|
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||||
|
type CloseNotifyFunc func() <-chan bool
|
||||||
|
|
||||||
|
// HijackFunc is part of the http.Hijacker interface.
|
||||||
|
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||||
|
|
||||||
|
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||||
|
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||||
|
|
||||||
|
// Hooks defines a set of method interceptors for methods included in
|
||||||
|
// http.ResponseWriter as well as some others. You can think of them as
|
||||||
|
// middleware for the function calls they target. See Wrap for more details.
|
||||||
|
type Hooks struct {
|
||||||
|
Header func(HeaderFunc) HeaderFunc
|
||||||
|
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||||
|
Write func(WriteFunc) WriteFunc
|
||||||
|
Flush func(FlushFunc) FlushFunc
|
||||||
|
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||||
|
Hijack func(HijackFunc) HijackFunc
|
||||||
|
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||||
|
// as w. Specifically if w implements any combination of:
|
||||||
|
//
|
||||||
|
// - http.Flusher
|
||||||
|
// - http.CloseNotifier
|
||||||
|
// - http.Hijacker
|
||||||
|
// - io.ReaderFrom
|
||||||
|
//
|
||||||
|
// The wrapped version will implement the exact same combination. If no hooks
|
||||||
|
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||||
|
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||||
|
// method they target and may modify the call's arguments and/or return values.
|
||||||
|
// The CaptureMetrics implementation serves as a working example for how the
|
||||||
|
// hooks can be used.
|
||||||
|
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||||
|
rw := &rw{w: w, h: hooks}
|
||||||
|
_, i0 := w.(http.Flusher)
|
||||||
|
_, i1 := w.(http.CloseNotifier)
|
||||||
|
_, i2 := w.(http.Hijacker)
|
||||||
|
_, i3 := w.(io.ReaderFrom)
|
||||||
|
switch {
|
||||||
|
// combination 1/16
|
||||||
|
case !i0 && !i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
}{rw, rw}
|
||||||
|
// combination 2/16
|
||||||
|
case !i0 && !i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 3/16
|
||||||
|
case !i0 && !i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 4/16
|
||||||
|
case !i0 && !i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 5/16
|
||||||
|
case !i0 && i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 6/16
|
||||||
|
case !i0 && i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 7/16
|
||||||
|
case !i0 && i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 8/16
|
||||||
|
case !i0 && i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 9/16
|
||||||
|
case i0 && !i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
}{rw, rw, rw}
|
||||||
|
// combination 10/16
|
||||||
|
case i0 && !i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 11/16
|
||||||
|
case i0 && !i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 12/16
|
||||||
|
case i0 && !i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 13/16
|
||||||
|
case i0 && i1 && !i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{rw, rw, rw, rw}
|
||||||
|
// combination 14/16
|
||||||
|
case i0 && i1 && !i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 15/16
|
||||||
|
case i0 && i1 && i2 && !i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
}{rw, rw, rw, rw, rw}
|
||||||
|
// combination 16/16
|
||||||
|
case i0 && i1 && i2 && i3:
|
||||||
|
return struct {
|
||||||
|
Unwrapper
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
http.Hijacker
|
||||||
|
io.ReaderFrom
|
||||||
|
}{rw, rw, rw, rw, rw, rw}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
type rw struct {
|
||||||
|
w http.ResponseWriter
|
||||||
|
h Hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Unwrap() http.ResponseWriter {
|
||||||
|
return w.w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Header() http.Header {
|
||||||
|
f := w.w.(http.ResponseWriter).Header
|
||||||
|
if w.h.Header != nil {
|
||||||
|
f = w.h.Header(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) WriteHeader(code int) {
|
||||||
|
f := w.w.(http.ResponseWriter).WriteHeader
|
||||||
|
if w.h.WriteHeader != nil {
|
||||||
|
f = w.h.WriteHeader(f)
|
||||||
|
}
|
||||||
|
f(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Write(b []byte) (int, error) {
|
||||||
|
f := w.w.(http.ResponseWriter).Write
|
||||||
|
if w.h.Write != nil {
|
||||||
|
f = w.h.Write(f)
|
||||||
|
}
|
||||||
|
return f(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Flush() {
|
||||||
|
f := w.w.(http.Flusher).Flush
|
||||||
|
if w.h.Flush != nil {
|
||||||
|
f = w.h.Flush(f)
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) CloseNotify() <-chan bool {
|
||||||
|
f := w.w.(http.CloseNotifier).CloseNotify
|
||||||
|
if w.h.CloseNotify != nil {
|
||||||
|
f = w.h.CloseNotify(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
f := w.w.(http.Hijacker).Hijack
|
||||||
|
if w.h.Hijack != nil {
|
||||||
|
f = w.h.Hijack(f)
|
||||||
|
}
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||||
|
f := w.w.(io.ReaderFrom).ReadFrom
|
||||||
|
if w.h.ReadFrom != nil {
|
||||||
|
f = w.h.ReadFrom(f)
|
||||||
|
}
|
||||||
|
return f(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unwrapper interface {
|
||||||
|
Unwrap() http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||||
|
// layers of httpsnoop wrappers.
|
||||||
|
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||||
|
if rw, ok := w.(Unwrapper); ok {
|
||||||
|
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||||
|
return Unwrap(rw.Unwrap())
|
||||||
|
} else {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
run:
|
||||||
|
timeout: 1m
|
||||||
|
tests: true
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- deadcode
|
||||||
|
- errcheck
|
||||||
|
- forcetypeassert
|
||||||
|
- gocritic
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 10
|
|
@ -0,0 +1,6 @@
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.0.0-rc1
|
||||||
|
|
||||||
|
This is the first logged release. Major changes (including breaking changes)
|
||||||
|
have occurred since earlier tags.
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Logr is open to pull-requests, provided they fit within the intended scope of
|
||||||
|
the project. Specifically, this library aims to be VERY small and minimalist,
|
||||||
|
with no external dependencies.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This project intends to follow [semantic versioning](http://semver.org) and
|
||||||
|
is very strict about compatibility. Any proposed changes MUST follow those
|
||||||
|
rules.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
As a logging library, logr must be as light-weight as possible. Any proposed
|
||||||
|
code change must include results of running the [benchmark](./benchmark)
|
||||||
|
before and after the change.
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,282 @@
|
||||||
|
# A minimal logging API for Go
|
||||||
|
|
||||||
|
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr)
|
||||||
|
|
||||||
|
logr offers an(other) opinion on how Go programs and libraries can do logging
|
||||||
|
without becoming coupled to a particular logging implementation. This is not
|
||||||
|
an implementation of logging - it is an API. In fact it is two APIs with two
|
||||||
|
different sets of users.
|
||||||
|
|
||||||
|
The `Logger` type is intended for application and library authors. It provides
|
||||||
|
a relatively small API which can be used everywhere you want to emit logs. It
|
||||||
|
defers the actual act of writing logs (to files, to stdout, or whatever) to the
|
||||||
|
`LogSink` interface.
|
||||||
|
|
||||||
|
The `LogSink` interface is intended for logging library implementers. It is a
|
||||||
|
pure interface which can be implemented by logging frameworks to provide the actual logging
|
||||||
|
functionality.
|
||||||
|
|
||||||
|
This decoupling allows application and library developers to write code in
|
||||||
|
terms of `logr.Logger` (which has very low dependency fan-out) while the
|
||||||
|
implementation of logging is managed "up stack" (e.g. in or near `main()`.)
|
||||||
|
Application developers can then switch out implementations as necessary.
|
||||||
|
|
||||||
|
Many people assert that libraries should not be logging, and as such efforts
|
||||||
|
like this are pointless. Those people are welcome to convince the authors of
|
||||||
|
the tens-of-thousands of libraries that *DO* write logs that they are all
|
||||||
|
wrong. In the meantime, logr takes a more practical approach.
|
||||||
|
|
||||||
|
## Typical usage
|
||||||
|
|
||||||
|
Somewhere, early in an application's life, it will make a decision about which
|
||||||
|
logging library (implementation) it actually wants to use. Something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
func main() {
|
||||||
|
// ... other setup code ...
|
||||||
|
|
||||||
|
// Create the "root" logger. We have chosen the "logimpl" implementation,
|
||||||
|
// which takes some initial parameters and returns a logr.Logger.
|
||||||
|
logger := logimpl.New(param1, param2)
|
||||||
|
|
||||||
|
// ... other setup code ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Most apps will call into other libraries, create structures to govern the flow,
|
||||||
|
etc. The `logr.Logger` object can be passed to these other libraries, stored
|
||||||
|
in structs, or even used as a package-global variable, if needed. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
app := createTheAppObject(logger)
|
||||||
|
app.Run()
|
||||||
|
```
|
||||||
|
|
||||||
|
Outside of this early setup, no other packages need to know about the choice of
|
||||||
|
implementation. They write logs in terms of the `logr.Logger` that they
|
||||||
|
received:
|
||||||
|
|
||||||
|
```
|
||||||
|
type appObject struct {
|
||||||
|
// ... other fields ...
|
||||||
|
logger logr.Logger
|
||||||
|
// ... other fields ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *appObject) Run() {
|
||||||
|
app.logger.Info("starting up", "timestamp", time.Now())
|
||||||
|
|
||||||
|
// ... app code ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
If the Go standard library had defined an interface for logging, this project
|
||||||
|
probably would not be needed. Alas, here we are.
|
||||||
|
|
||||||
|
### Inspiration
|
||||||
|
|
||||||
|
Before you consider this package, please read [this blog post by the
|
||||||
|
inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what
|
||||||
|
he has to say, and it largely aligns with our own experiences.
|
||||||
|
|
||||||
|
### Differences from Dave's ideas
|
||||||
|
|
||||||
|
The main differences are:
|
||||||
|
|
||||||
|
1. Dave basically proposes doing away with the notion of a logging API in favor
|
||||||
|
of `fmt.Printf()`. We disagree, especially when you consider things like output
|
||||||
|
locations, timestamps, file and line decorations, and structured logging. This
|
||||||
|
package restricts the logging API to just 2 types of logs: info and error.
|
||||||
|
|
||||||
|
Info logs are things you want to tell the user which are not errors. Error
|
||||||
|
logs are, well, errors. If your code receives an `error` from a subordinate
|
||||||
|
function call and is logging that `error` *and not returning it*, use error
|
||||||
|
logs.
|
||||||
|
|
||||||
|
2. Verbosity-levels on info logs. This gives developers a chance to indicate
|
||||||
|
arbitrary grades of importance for info logs, without assigning names with
|
||||||
|
semantic meaning such as "warning", "trace", and "debug." Superficially this
|
||||||
|
may feel very similar, but the primary difference is the lack of semantics.
|
||||||
|
Because verbosity is a numerical value, it's safe to assume that an app running
|
||||||
|
with higher verbosity means more (and less important) logs will be generated.
|
||||||
|
|
||||||
|
## Implementations (non-exhaustive)
|
||||||
|
|
||||||
|
There are implementations for the following logging libraries:
|
||||||
|
|
||||||
|
- **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr)
|
||||||
|
- **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr)
|
||||||
|
- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
|
||||||
|
- **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr)
|
||||||
|
- **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting)
|
||||||
|
- **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr)
|
||||||
|
- **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr)
|
||||||
|
- **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr)
|
||||||
|
- **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend)
|
||||||
|
- **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr)
|
||||||
|
- **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr)
|
||||||
|
- **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0)
|
||||||
|
- **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing)
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Conceptual
|
||||||
|
|
||||||
|
#### Why structured logging?
|
||||||
|
|
||||||
|
- **Structured logs are more easily queryable**: Since you've got
|
||||||
|
key-value pairs, it's much easier to query your structured logs for
|
||||||
|
particular values by filtering on the contents of a particular key --
|
||||||
|
think searching request logs for error codes, Kubernetes reconcilers for
|
||||||
|
the name and namespace of the reconciled object, etc.
|
||||||
|
|
||||||
|
- **Structured logging makes it easier to have cross-referenceable logs**:
|
||||||
|
Similarly to searchability, if you maintain conventions around your
|
||||||
|
keys, it becomes easy to gather all log lines related to a particular
|
||||||
|
concept.
|
||||||
|
|
||||||
|
- **Structured logs allow better dimensions of filtering**: if you have
|
||||||
|
structure to your logs, you've got more precise control over how much
|
||||||
|
information is logged -- you might choose in a particular configuration
|
||||||
|
to log certain keys but not others, only log lines where a certain key
|
||||||
|
matches a certain value, etc., instead of just having v-levels and names
|
||||||
|
to key off of.
|
||||||
|
|
||||||
|
- **Structured logs better represent structured data**: sometimes, the
|
||||||
|
data that you want to log is inherently structured (think tuple-link
|
||||||
|
objects.) Structured logs allow you to preserve that structure when
|
||||||
|
outputting.
|
||||||
|
|
||||||
|
#### Why V-levels?
|
||||||
|
|
||||||
|
**V-levels give operators an easy way to control the chattiness of log
|
||||||
|
operations**. V-levels provide a way for a given package to distinguish
|
||||||
|
the relative importance or verbosity of a given log message. Then, if
|
||||||
|
a particular logger or package is logging too many messages, the user
|
||||||
|
of the package can simply change the v-levels for that library.
|
||||||
|
|
||||||
|
#### Why not named levels, like Info/Warning/Error?
|
||||||
|
|
||||||
|
Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences
|
||||||
|
from Dave's ideas](#differences-from-daves-ideas).
|
||||||
|
|
||||||
|
#### Why not allow format strings, too?
|
||||||
|
|
||||||
|
**Format strings negate many of the benefits of structured logs**:
|
||||||
|
|
||||||
|
- They're not easily searchable without resorting to fuzzy searching,
|
||||||
|
regular expressions, etc.
|
||||||
|
|
||||||
|
- They don't store structured data well, since contents are flattened into
|
||||||
|
a string.
|
||||||
|
|
||||||
|
- They're not cross-referenceable.
|
||||||
|
|
||||||
|
- They don't compress easily, since the message is not constant.
|
||||||
|
|
||||||
|
(Unless you turn positional parameters into key-value pairs with numerical
|
||||||
|
keys, at which point you've gotten key-value logging with meaningless
|
||||||
|
keys.)
|
||||||
|
|
||||||
|
### Practical
|
||||||
|
|
||||||
|
#### Why key-value pairs, and not a map?
|
||||||
|
|
||||||
|
Key-value pairs are *much* easier to optimize, especially around
|
||||||
|
allocations. Zap (a structured logger that inspired logr's interface) has
|
||||||
|
[performance measurements](https://github.com/uber-go/zap#performance)
|
||||||
|
that show this quite nicely.
|
||||||
|
|
||||||
|
While the interface ends up being a little less obvious, you get
|
||||||
|
potentially better performance, plus avoid making users type
|
||||||
|
`map[string]string{}` every time they want to log.
|
||||||
|
|
||||||
|
#### What if my V-levels differ between libraries?
|
||||||
|
|
||||||
|
That's fine. Control your V-levels on a per-logger basis, and use the
|
||||||
|
`WithName` method to pass different loggers to different libraries.
|
||||||
|
|
||||||
|
Generally, you should take care to ensure that you have relatively
|
||||||
|
consistent V-levels within a given logger, however, as this makes deciding
|
||||||
|
on what verbosity of logs to request easier.
|
||||||
|
|
||||||
|
#### But I really want to use a format string!
|
||||||
|
|
||||||
|
That's not actually a question. Assuming your question is "how do
|
||||||
|
I convert my mental model of logging with format strings to logging with
|
||||||
|
constant messages":
|
||||||
|
|
||||||
|
1. Figure out what the error actually is, as you'd write in a TL;DR style,
|
||||||
|
and use that as a message.
|
||||||
|
|
||||||
|
2. For every place you'd write a format specifier, look to the word before
|
||||||
|
it, and add that as a key value pair.
|
||||||
|
|
||||||
|
For instance, consider the following examples (all taken from spots in the
|
||||||
|
Kubernetes codebase):
|
||||||
|
|
||||||
|
- `klog.V(4).Infof("Client is returning errors: code %v, error %v",
|
||||||
|
responseCode, err)` becomes `logger.Error(err, "client returned an
|
||||||
|
error", "code", responseCode)`
|
||||||
|
|
||||||
|
- `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v",
|
||||||
|
seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after
|
||||||
|
response when requesting url", "attempt", retries, "after
|
||||||
|
seconds", seconds, "url", url)`
|
||||||
|
|
||||||
|
If you *really* must use a format string, use it in a key's value, and
|
||||||
|
call `fmt.Sprintf` yourself. For instance: `log.Printf("unable to
|
||||||
|
reflect over type %T")` becomes `logger.Info("unable to reflect over
|
||||||
|
type", "type", fmt.Sprintf("%T"))`. In general though, the cases where
|
||||||
|
this is necessary should be few and far between.
|
||||||
|
|
||||||
|
#### How do I choose my V-levels?
|
||||||
|
|
||||||
|
This is basically the only hard constraint: increase V-levels to denote
|
||||||
|
more verbose or more debug-y logs.
|
||||||
|
|
||||||
|
Otherwise, you can start out with `0` as "you always want to see this",
|
||||||
|
`1` as "common logging that you might *possibly* want to turn off", and
|
||||||
|
`10` as "I would like to performance-test your log collection stack."
|
||||||
|
|
||||||
|
Then gradually choose levels in between as you need them, working your way
|
||||||
|
down from 10 (for debug and trace style logs) and up from 1 (for chattier
|
||||||
|
info-type logs.)
|
||||||
|
|
||||||
|
#### How do I choose my keys?
|
||||||
|
|
||||||
|
Keys are fairly flexible, and can hold more or less any string
|
||||||
|
value. For best compatibility with implementations and consistency
|
||||||
|
with existing code in other projects, there are a few conventions you
|
||||||
|
should consider.
|
||||||
|
|
||||||
|
- Make your keys human-readable.
|
||||||
|
- Constant keys are generally a good idea.
|
||||||
|
- Be consistent across your codebase.
|
||||||
|
- Keys should naturally match parts of the message string.
|
||||||
|
- Use lower case for simple keys and
|
||||||
|
[lowerCamelCase](https://en.wiktionary.org/wiki/lowerCamelCase) for
|
||||||
|
more complex ones. Kubernetes is one example of a project that has
|
||||||
|
[adopted that
|
||||||
|
convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments).
|
||||||
|
|
||||||
|
While key names are mostly unrestricted (and spaces are acceptable),
|
||||||
|
it's generally a good idea to stick to printable ascii characters, or at
|
||||||
|
least match the general character set of your log lines.
|
||||||
|
|
||||||
|
#### Why should keys be constant values?
|
||||||
|
|
||||||
|
The point of structured logging is to make later log processing easier. Your
|
||||||
|
keys are, effectively, the schema of each log message. If you use different
|
||||||
|
keys across instances of the same log line, you will make your structured logs
|
||||||
|
much harder to use. `Sprintf()` is for values, not for keys!
|
||||||
|
|
||||||
|
#### Why is this not a pure interface?
|
||||||
|
|
||||||
|
The Logger type is implemented as a struct in order to allow the Go compiler to
|
||||||
|
optimize things like high-V `Info` logs that are not triggered. Not all of
|
||||||
|
these implementations are implemented yet, but this structure was suggested as
|
||||||
|
a way to ensure they *can* be implemented. All of the real work is behind the
|
||||||
|
`LogSink` interface.
|
||||||
|
|
||||||
|
[warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The logr Authors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package logr
|
||||||
|
|
||||||
|
// Discard returns a Logger that discards all messages logged to it. It can be
|
||||||
|
// used whenever the caller is not interested in the logs. Logger instances
|
||||||
|
// produced by this function always compare as equal.
|
||||||
|
func Discard() Logger {
|
||||||
|
return Logger{
|
||||||
|
level: 0,
|
||||||
|
sink: discardLogSink{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardLogSink is a LogSink that discards all messages.
|
||||||
|
type discardLogSink struct{}
|
||||||
|
|
||||||
|
// Verify that it actually implements the interface
|
||||||
|
var _ LogSink = discardLogSink{}
|
||||||
|
|
||||||
|
func (l discardLogSink) Init(RuntimeInfo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l discardLogSink) Enabled(int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l discardLogSink) Info(int, string, ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l discardLogSink) Error(error, string, ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l discardLogSink) WithValues(...interface{}) LogSink {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l discardLogSink) WithName(string) LogSink {
|
||||||
|
return l
|
||||||
|
}
|
|
@ -0,0 +1,787 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The logr Authors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package funcr implements formatting of structured log messages and
|
||||||
|
// optionally captures the call site and timestamp.
|
||||||
|
//
|
||||||
|
// The simplest way to use it is via its implementation of a
|
||||||
|
// github.com/go-logr/logr.LogSink with output through an arbitrary
|
||||||
|
// "write" function. See New and NewJSON for details.
|
||||||
|
//
|
||||||
|
// Custom LogSinks
|
||||||
|
//
|
||||||
|
// For users who need more control, a funcr.Formatter can be embedded inside
|
||||||
|
// your own custom LogSink implementation. This is useful when the LogSink
|
||||||
|
// needs to implement additional methods, for example.
|
||||||
|
//
|
||||||
|
// Formatting
|
||||||
|
//
|
||||||
|
// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
|
||||||
|
// values which are being logged. When rendering a struct, funcr will use Go's
|
||||||
|
// standard JSON tags (all except "string").
|
||||||
|
package funcr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a logr.Logger which is implemented by an arbitrary function.
|
||||||
|
func New(fn func(prefix, args string), opts Options) logr.Logger {
|
||||||
|
return logr.New(newSink(fn, NewFormatter(opts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSON returns a logr.Logger which is implemented by an arbitrary function
|
||||||
|
// and produces JSON output.
|
||||||
|
func NewJSON(fn func(obj string), opts Options) logr.Logger {
|
||||||
|
fnWrapper := func(_, obj string) {
|
||||||
|
fn(obj)
|
||||||
|
}
|
||||||
|
return logr.New(newSink(fnWrapper, NewFormatterJSON(opts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underlier exposes access to the underlying logging function. Since
|
||||||
|
// callers only have a logr.Logger, they have to know which
|
||||||
|
// implementation is in use, so this interface is less of an
|
||||||
|
// abstraction and more of a way to test type conversion.
|
||||||
|
type Underlier interface {
|
||||||
|
GetUnderlying() func(prefix, args string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink {
|
||||||
|
l := &fnlogger{
|
||||||
|
Formatter: formatter,
|
||||||
|
write: fn,
|
||||||
|
}
|
||||||
|
// For skipping fnlogger.Info and fnlogger.Error.
|
||||||
|
l.Formatter.AddCallDepth(1)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options carries parameters which influence the way logs are generated.
|
||||||
|
type Options struct {
|
||||||
|
// LogCaller tells funcr to add a "caller" key to some or all log lines.
|
||||||
|
// This has some overhead, so some users might not want it.
|
||||||
|
LogCaller MessageClass
|
||||||
|
|
||||||
|
// LogCallerFunc tells funcr to also log the calling function name. This
|
||||||
|
// has no effect if caller logging is not enabled (see Options.LogCaller).
|
||||||
|
LogCallerFunc bool
|
||||||
|
|
||||||
|
// LogTimestamp tells funcr to add a "ts" key to log lines. This has some
|
||||||
|
// overhead, so some users might not want it.
|
||||||
|
LogTimestamp bool
|
||||||
|
|
||||||
|
// TimestampFormat tells funcr how to render timestamps when LogTimestamp
|
||||||
|
// is enabled. If not specified, a default format will be used. For more
|
||||||
|
// details, see docs for Go's time.Layout.
|
||||||
|
TimestampFormat string
|
||||||
|
|
||||||
|
// Verbosity tells funcr which V logs to produce. Higher values enable
|
||||||
|
// more logs. Info logs at or below this level will be written, while logs
|
||||||
|
// above this level will be discarded.
|
||||||
|
Verbosity int
|
||||||
|
|
||||||
|
// RenderBuiltinsHook allows users to mutate the list of key-value pairs
|
||||||
|
// while a log line is being rendered. The kvList argument follows logr
|
||||||
|
// conventions - each pair of slice elements is comprised of a string key
|
||||||
|
// and an arbitrary value (verified and sanitized before calling this
|
||||||
|
// hook). The value returned must follow the same conventions. This hook
|
||||||
|
// can be used to audit or modify logged data. For example, you might want
|
||||||
|
// to prefix all of funcr's built-in keys with some string. This hook is
|
||||||
|
// only called for built-in (provided by funcr itself) key-value pairs.
|
||||||
|
// Equivalent hooks are offered for key-value pairs saved via
|
||||||
|
// logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and
|
||||||
|
// for user-provided pairs (see RenderArgsHook).
|
||||||
|
RenderBuiltinsHook func(kvList []interface{}) []interface{}
|
||||||
|
|
||||||
|
// RenderValuesHook is the same as RenderBuiltinsHook, except that it is
|
||||||
|
// only called for key-value pairs saved via logr.Logger.WithValues. See
|
||||||
|
// RenderBuiltinsHook for more details.
|
||||||
|
RenderValuesHook func(kvList []interface{}) []interface{}
|
||||||
|
|
||||||
|
// RenderArgsHook is the same as RenderBuiltinsHook, except that it is only
|
||||||
|
// called for key-value pairs passed directly to Info and Error. See
|
||||||
|
// RenderBuiltinsHook for more details.
|
||||||
|
RenderArgsHook func(kvList []interface{}) []interface{}
|
||||||
|
|
||||||
|
// MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct
|
||||||
|
// that contains a struct, etc.) it may log. Every time it finds a struct,
|
||||||
|
// slice, array, or map the depth is increased by one. When the maximum is
|
||||||
|
// reached, the value will be converted to a string indicating that the max
|
||||||
|
// depth has been exceeded. If this field is not specified, a default
|
||||||
|
// value will be used.
|
||||||
|
MaxLogDepth int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageClass indicates which category or categories of messages to consider.
|
||||||
|
type MessageClass int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// None ignores all message classes.
|
||||||
|
None MessageClass = iota
|
||||||
|
// All considers all message classes.
|
||||||
|
All
|
||||||
|
// Info only considers info messages.
|
||||||
|
Info
|
||||||
|
// Error only considers error messages.
|
||||||
|
Error
|
||||||
|
)
|
||||||
|
|
||||||
|
// fnlogger inherits some of its LogSink implementation from Formatter
|
||||||
|
// and just needs to add some glue code.
|
||||||
|
type fnlogger struct {
|
||||||
|
Formatter
|
||||||
|
write func(prefix, args string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l fnlogger) WithName(name string) logr.LogSink {
|
||||||
|
l.Formatter.AddName(name)
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l fnlogger) WithValues(kvList ...interface{}) logr.LogSink {
|
||||||
|
l.Formatter.AddValues(kvList)
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
|
||||||
|
l.Formatter.AddCallDepth(depth)
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l fnlogger) Info(level int, msg string, kvList ...interface{}) {
|
||||||
|
prefix, args := l.FormatInfo(level, msg, kvList)
|
||||||
|
l.write(prefix, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l fnlogger) Error(err error, msg string, kvList ...interface{}) {
|
||||||
|
prefix, args := l.FormatError(err, msg, kvList)
|
||||||
|
l.write(prefix, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l fnlogger) GetUnderlying() func(prefix, args string) {
|
||||||
|
return l.write
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert conformance to the interfaces.
|
||||||
|
var _ logr.LogSink = &fnlogger{}
|
||||||
|
var _ logr.CallDepthLogSink = &fnlogger{}
|
||||||
|
var _ Underlier = &fnlogger{}
|
||||||
|
|
||||||
|
// NewFormatter constructs a Formatter which emits a JSON-like key=value format.
|
||||||
|
func NewFormatter(opts Options) Formatter {
|
||||||
|
return newFormatter(opts, outputKeyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFormatterJSON constructs a Formatter which emits strict JSON.
|
||||||
|
func NewFormatterJSON(opts Options) Formatter {
|
||||||
|
return newFormatter(opts, outputJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults for Options.
|
||||||
|
const defaultTimestampFormat = "2006-01-02 15:04:05.000000"
|
||||||
|
const defaultMaxLogDepth = 16
|
||||||
|
|
||||||
|
func newFormatter(opts Options, outfmt outputFormat) Formatter {
|
||||||
|
if opts.TimestampFormat == "" {
|
||||||
|
opts.TimestampFormat = defaultTimestampFormat
|
||||||
|
}
|
||||||
|
if opts.MaxLogDepth == 0 {
|
||||||
|
opts.MaxLogDepth = defaultMaxLogDepth
|
||||||
|
}
|
||||||
|
f := Formatter{
|
||||||
|
outputFormat: outfmt,
|
||||||
|
prefix: "",
|
||||||
|
values: nil,
|
||||||
|
depth: 0,
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatter is an opaque struct which can be embedded in a LogSink
|
||||||
|
// implementation. It should be constructed with NewFormatter. Some of
|
||||||
|
// its methods directly implement logr.LogSink.
|
||||||
|
type Formatter struct {
|
||||||
|
outputFormat outputFormat
|
||||||
|
prefix string
|
||||||
|
values []interface{}
|
||||||
|
valuesStr string
|
||||||
|
depth int
|
||||||
|
opts Options
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputFormat indicates which outputFormat to use.
|
||||||
|
type outputFormat int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// outputKeyValue emits a JSON-like key=value format, but not strict JSON.
|
||||||
|
outputKeyValue outputFormat = iota
|
||||||
|
// outputJSON emits strict JSON.
|
||||||
|
outputJSON
|
||||||
|
)
|
||||||
|
|
||||||
|
// PseudoStruct is a list of key-value pairs that gets logged as a struct.
|
||||||
|
type PseudoStruct []interface{}
|
||||||
|
|
||||||
|
// render produces a log line, ready to use.
|
||||||
|
func (f Formatter) render(builtins, args []interface{}) string {
|
||||||
|
// Empirically bytes.Buffer is faster than strings.Builder for this.
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
buf.WriteByte('{')
|
||||||
|
}
|
||||||
|
vals := builtins
|
||||||
|
if hook := f.opts.RenderBuiltinsHook; hook != nil {
|
||||||
|
vals = hook(f.sanitize(vals))
|
||||||
|
}
|
||||||
|
f.flatten(buf, vals, false, false) // keys are ours, no need to escape
|
||||||
|
continuing := len(builtins) > 0
|
||||||
|
if len(f.valuesStr) > 0 {
|
||||||
|
if continuing {
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continuing = true
|
||||||
|
buf.WriteString(f.valuesStr)
|
||||||
|
}
|
||||||
|
vals = args
|
||||||
|
if hook := f.opts.RenderArgsHook; hook != nil {
|
||||||
|
vals = hook(f.sanitize(vals))
|
||||||
|
}
|
||||||
|
f.flatten(buf, vals, continuing, true) // escape user-provided keys
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
buf.WriteByte('}')
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatten renders a list of key-value pairs into a buffer. If continuing is
|
||||||
|
// true, it assumes that the buffer has previous values and will emit a
|
||||||
|
// separator (which depends on the output format) before the first pair it
|
||||||
|
// writes. If escapeKeys is true, the keys are assumed to have
|
||||||
|
// non-JSON-compatible characters in them and must be evaluated for escapes.
|
||||||
|
//
|
||||||
|
// This function returns a potentially modified version of kvList, which
|
||||||
|
// ensures that there is a value for every key (adding a value if needed) and
|
||||||
|
// that each key is a string (substituting a key if needed).
|
||||||
|
func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} {
|
||||||
|
// This logic overlaps with sanitize() but saves one type-cast per key,
|
||||||
|
// which can be measurable.
|
||||||
|
if len(kvList)%2 != 0 {
|
||||||
|
kvList = append(kvList, noValue)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(kvList); i += 2 {
|
||||||
|
k, ok := kvList[i].(string)
|
||||||
|
if !ok {
|
||||||
|
k = f.nonStringKey(kvList[i])
|
||||||
|
kvList[i] = k
|
||||||
|
}
|
||||||
|
v := kvList[i+1]
|
||||||
|
|
||||||
|
if i > 0 || continuing {
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
} else {
|
||||||
|
// In theory the format could be something we don't understand. In
|
||||||
|
// practice, we control it, so it won't be.
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if escapeKeys {
|
||||||
|
buf.WriteString(prettyString(k))
|
||||||
|
} else {
|
||||||
|
// this is faster
|
||||||
|
buf.WriteByte('"')
|
||||||
|
buf.WriteString(k)
|
||||||
|
buf.WriteByte('"')
|
||||||
|
}
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
buf.WriteByte(':')
|
||||||
|
} else {
|
||||||
|
buf.WriteByte('=')
|
||||||
|
}
|
||||||
|
buf.WriteString(f.pretty(v))
|
||||||
|
}
|
||||||
|
return kvList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Formatter) pretty(value interface{}) string {
|
||||||
|
return f.prettyWithFlags(value, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagRawStruct = 0x1 // do not print braces on structs
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: This is not fast. Most of the overhead goes here.
|
||||||
|
func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string {
|
||||||
|
if depth > f.opts.MaxLogDepth {
|
||||||
|
return `"<max-log-depth-exceeded>"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle types that take full control of logging.
|
||||||
|
if v, ok := value.(logr.Marshaler); ok {
|
||||||
|
// Replace the value with what the type wants to get logged.
|
||||||
|
// That then gets handled below via reflection.
|
||||||
|
value = invokeMarshaler(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle types that want to format themselves.
|
||||||
|
switch v := value.(type) {
|
||||||
|
case fmt.Stringer:
|
||||||
|
value = invokeStringer(v)
|
||||||
|
case error:
|
||||||
|
value = invokeError(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling the most common types without reflect is a small perf win.
|
||||||
|
switch v := value.(type) {
|
||||||
|
case bool:
|
||||||
|
return strconv.FormatBool(v)
|
||||||
|
case string:
|
||||||
|
return prettyString(v)
|
||||||
|
case int:
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
case int8:
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
case int16:
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
case int32:
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
case int64:
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
case uint:
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
case uint8:
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
case uint16:
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
case uint32:
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
case uint64:
|
||||||
|
return strconv.FormatUint(v, 10)
|
||||||
|
case uintptr:
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
case float32:
|
||||||
|
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||||
|
case complex64:
|
||||||
|
return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
|
||||||
|
case complex128:
|
||||||
|
return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
|
||||||
|
case PseudoStruct:
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||||
|
v = f.sanitize(v)
|
||||||
|
if flags&flagRawStruct == 0 {
|
||||||
|
buf.WriteByte('{')
|
||||||
|
}
|
||||||
|
for i := 0; i < len(v); i += 2 {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
k, _ := v[i].(string) // sanitize() above means no need to check success
|
||||||
|
// arbitrary keys might need escaping
|
||||||
|
buf.WriteString(prettyString(k))
|
||||||
|
buf.WriteByte(':')
|
||||||
|
buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
|
||||||
|
}
|
||||||
|
if flags&flagRawStruct == 0 {
|
||||||
|
buf.WriteByte('}')
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, 256))
|
||||||
|
t := reflect.TypeOf(value)
|
||||||
|
if t == nil {
|
||||||
|
return "null"
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return strconv.FormatBool(v.Bool())
|
||||||
|
case reflect.String:
|
||||||
|
return prettyString(v.String())
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return strconv.FormatInt(int64(v.Int()), 10)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return strconv.FormatUint(uint64(v.Uint()), 10)
|
||||||
|
case reflect.Float32:
|
||||||
|
return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
|
||||||
|
case reflect.Float64:
|
||||||
|
return strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
||||||
|
case reflect.Complex64:
|
||||||
|
return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"`
|
||||||
|
case reflect.Complex128:
|
||||||
|
return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"`
|
||||||
|
case reflect.Struct:
|
||||||
|
if flags&flagRawStruct == 0 {
|
||||||
|
buf.WriteByte('{')
|
||||||
|
}
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
fld := t.Field(i)
|
||||||
|
if fld.PkgPath != "" {
|
||||||
|
// reflect says this field is only defined for non-exported fields.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !v.Field(i).CanInterface() {
|
||||||
|
// reflect isn't clear exactly what this means, but we can't use it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := ""
|
||||||
|
omitempty := false
|
||||||
|
if tag, found := fld.Tag.Lookup("json"); found {
|
||||||
|
if tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if comma := strings.Index(tag, ","); comma != -1 {
|
||||||
|
if n := tag[:comma]; n != "" {
|
||||||
|
name = n
|
||||||
|
}
|
||||||
|
rest := tag[comma:]
|
||||||
|
if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") {
|
||||||
|
omitempty = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if omitempty && isEmpty(v.Field(i)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
|
||||||
|
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
name = fld.Name
|
||||||
|
}
|
||||||
|
// field names can't contain characters which need escaping
|
||||||
|
buf.WriteByte('"')
|
||||||
|
buf.WriteString(name)
|
||||||
|
buf.WriteByte('"')
|
||||||
|
buf.WriteByte(':')
|
||||||
|
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
|
||||||
|
}
|
||||||
|
if flags&flagRawStruct == 0 {
|
||||||
|
buf.WriteByte('}')
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
buf.WriteByte('[')
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
e := v.Index(i)
|
||||||
|
buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
|
||||||
|
}
|
||||||
|
buf.WriteByte(']')
|
||||||
|
return buf.String()
|
||||||
|
case reflect.Map:
|
||||||
|
buf.WriteByte('{')
|
||||||
|
// This does not sort the map keys, for best perf.
|
||||||
|
it := v.MapRange()
|
||||||
|
i := 0
|
||||||
|
for it.Next() {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteByte(',')
|
||||||
|
}
|
||||||
|
// If a map key supports TextMarshaler, use it.
|
||||||
|
keystr := ""
|
||||||
|
if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
|
||||||
|
txt, err := m.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
|
||||||
|
} else {
|
||||||
|
keystr = string(txt)
|
||||||
|
}
|
||||||
|
keystr = prettyString(keystr)
|
||||||
|
} else {
|
||||||
|
// prettyWithFlags will produce already-escaped values
|
||||||
|
keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
|
||||||
|
if t.Key().Kind() != reflect.String {
|
||||||
|
// JSON only does string keys. Unlike Go's standard JSON, we'll
|
||||||
|
// convert just about anything to a string.
|
||||||
|
keystr = prettyString(keystr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString(keystr)
|
||||||
|
buf.WriteByte(':')
|
||||||
|
buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
buf.WriteByte('}')
|
||||||
|
return buf.String()
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
if v.IsNil() {
|
||||||
|
return "null"
|
||||||
|
}
|
||||||
|
return f.prettyWithFlags(v.Elem().Interface(), 0, depth)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func prettyString(s string) string {
|
||||||
|
// Avoid escaping (which does allocations) if we can.
|
||||||
|
if needsEscape(s) {
|
||||||
|
return strconv.Quote(s)
|
||||||
|
}
|
||||||
|
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||||
|
b.WriteByte('"')
|
||||||
|
b.WriteString(s)
|
||||||
|
b.WriteByte('"')
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// needsEscape determines whether the input string needs to be escaped or not,
|
||||||
|
// without doing any allocations.
|
||||||
|
func needsEscape(s string) bool {
|
||||||
|
for _, r := range s {
|
||||||
|
if !strconv.IsPrint(r) || r == '\\' || r == '"' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmpty(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return v.Complex() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeMarshaler(m logr.Marshaler) (ret interface{}) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ret = fmt.Sprintf("<panic: %s>", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return m.MarshalLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeStringer(s fmt.Stringer) (ret string) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ret = fmt.Sprintf("<panic: %s>", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeError(e error) (ret string) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ret = fmt.Sprintf("<panic: %s>", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return e.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caller represents the original call site for a log line, after considering
|
||||||
|
// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and
|
||||||
|
// Line fields will always be provided, while the Func field is optional.
|
||||||
|
// Users can set the render hook fields in Options to examine logged key-value
|
||||||
|
// pairs, one of which will be {"caller", Caller} if the Options.LogCaller
|
||||||
|
// field is enabled for the given MessageClass.
|
||||||
|
type Caller struct {
|
||||||
|
// File is the basename of the file for this call site.
|
||||||
|
File string `json:"file"`
|
||||||
|
// Line is the line number in the file for this call site.
|
||||||
|
Line int `json:"line"`
|
||||||
|
// Func is the function name for this call site, or empty if
|
||||||
|
// Options.LogCallerFunc is not enabled.
|
||||||
|
Func string `json:"function,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Formatter) caller() Caller {
|
||||||
|
// +1 for this frame, +1 for Info/Error.
|
||||||
|
pc, file, line, ok := runtime.Caller(f.depth + 2)
|
||||||
|
if !ok {
|
||||||
|
return Caller{"<unknown>", 0, ""}
|
||||||
|
}
|
||||||
|
fn := ""
|
||||||
|
if f.opts.LogCallerFunc {
|
||||||
|
if fp := runtime.FuncForPC(pc); fp != nil {
|
||||||
|
fn = fp.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Caller{filepath.Base(file), line, fn}
|
||||||
|
}
|
||||||
|
|
||||||
|
const noValue = "<no-value>"
|
||||||
|
|
||||||
|
func (f Formatter) nonStringKey(v interface{}) string {
|
||||||
|
return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// snippet produces a short snippet string of an arbitrary value.
|
||||||
|
func (f Formatter) snippet(v interface{}) string {
|
||||||
|
const snipLen = 16
|
||||||
|
|
||||||
|
snip := f.pretty(v)
|
||||||
|
if len(snip) > snipLen {
|
||||||
|
snip = snip[:snipLen]
|
||||||
|
}
|
||||||
|
return snip
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize ensures that a list of key-value pairs has a value for every key
|
||||||
|
// (adding a value if needed) and that each key is a string (substituting a key
|
||||||
|
// if needed).
|
||||||
|
func (f Formatter) sanitize(kvList []interface{}) []interface{} {
|
||||||
|
if len(kvList)%2 != 0 {
|
||||||
|
kvList = append(kvList, noValue)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(kvList); i += 2 {
|
||||||
|
_, ok := kvList[i].(string)
|
||||||
|
if !ok {
|
||||||
|
kvList[i] = f.nonStringKey(kvList[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kvList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init configures this Formatter from runtime info, such as the call depth
|
||||||
|
// imposed by logr itself.
|
||||||
|
// Note that this receiver is a pointer, so depth can be saved.
|
||||||
|
func (f *Formatter) Init(info logr.RuntimeInfo) {
|
||||||
|
f.depth += info.CallDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled checks whether an info message at the given level should be logged.
|
||||||
|
func (f Formatter) Enabled(level int) bool {
|
||||||
|
return level <= f.opts.Verbosity
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDepth returns the current depth of this Formatter. This is useful for
|
||||||
|
// implementations which do their own caller attribution.
|
||||||
|
func (f Formatter) GetDepth() int {
|
||||||
|
return f.depth
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatInfo renders an Info log message into strings. The prefix will be
|
||||||
|
// empty when no names were set (via AddNames), or when the output is
|
||||||
|
// configured for JSON.
|
||||||
|
func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) {
|
||||||
|
args := make([]interface{}, 0, 64) // using a constant here impacts perf
|
||||||
|
prefix = f.prefix
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
args = append(args, "logger", prefix)
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
if f.opts.LogTimestamp {
|
||||||
|
args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
|
||||||
|
}
|
||||||
|
if policy := f.opts.LogCaller; policy == All || policy == Info {
|
||||||
|
args = append(args, "caller", f.caller())
|
||||||
|
}
|
||||||
|
args = append(args, "level", level, "msg", msg)
|
||||||
|
return prefix, f.render(args, kvList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatError renders an Error log message into strings. The prefix will be
|
||||||
|
// empty when no names were set (via AddNames), or when the output is
|
||||||
|
// configured for JSON.
|
||||||
|
func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) {
|
||||||
|
args := make([]interface{}, 0, 64) // using a constant here impacts perf
|
||||||
|
prefix = f.prefix
|
||||||
|
if f.outputFormat == outputJSON {
|
||||||
|
args = append(args, "logger", prefix)
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
if f.opts.LogTimestamp {
|
||||||
|
args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
|
||||||
|
}
|
||||||
|
if policy := f.opts.LogCaller; policy == All || policy == Error {
|
||||||
|
args = append(args, "caller", f.caller())
|
||||||
|
}
|
||||||
|
args = append(args, "msg", msg)
|
||||||
|
var loggableErr interface{}
|
||||||
|
if err != nil {
|
||||||
|
loggableErr = err.Error()
|
||||||
|
}
|
||||||
|
args = append(args, "error", loggableErr)
|
||||||
|
return f.prefix, f.render(args, kvList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddName appends the specified name. funcr uses '/' characters to separate
|
||||||
|
// name elements. Callers should not pass '/' in the provided name string, but
|
||||||
|
// this library does not actually enforce that.
|
||||||
|
func (f *Formatter) AddName(name string) {
|
||||||
|
if len(f.prefix) > 0 {
|
||||||
|
f.prefix += "/"
|
||||||
|
}
|
||||||
|
f.prefix += name
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddValues adds key-value pairs to the set of saved values to be logged with
|
||||||
|
// each log line.
|
||||||
|
func (f *Formatter) AddValues(kvList []interface{}) {
|
||||||
|
// Three slice args forces a copy.
|
||||||
|
n := len(f.values)
|
||||||
|
f.values = append(f.values[:n:n], kvList...)
|
||||||
|
|
||||||
|
vals := f.values
|
||||||
|
if hook := f.opts.RenderValuesHook; hook != nil {
|
||||||
|
vals = hook(f.sanitize(vals))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-render values, so we don't have to do it on each Info/Error call.
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||||
|
f.flatten(buf, vals, false, true) // escape user-provided keys
|
||||||
|
f.valuesStr = buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCallDepth increases the number of stack-frames to skip when attributing
|
||||||
|
// the log line to a file and line.
|
||||||
|
func (f *Formatter) AddCallDepth(depth int) {
|
||||||
|
f.depth += depth
|
||||||
|
}
|
|
@ -0,0 +1,510 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The logr Authors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This design derives from Dave Cheney's blog:
|
||||||
|
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
||||||
|
|
||||||
|
// Package logr defines a general-purpose logging API and abstract interfaces
|
||||||
|
// to back that API. Packages in the Go ecosystem can depend on this package,
|
||||||
|
// while callers can implement logging with whatever backend is appropriate.
|
||||||
|
//
|
||||||
|
// Usage
|
||||||
|
//
|
||||||
|
// Logging is done using a Logger instance. Logger is a concrete type with
|
||||||
|
// methods, which defers the actual logging to a LogSink interface. The main
|
||||||
|
// methods of Logger are Info() and Error(). Arguments to Info() and Error()
|
||||||
|
// are key/value pairs rather than printf-style formatted strings, emphasizing
|
||||||
|
// "structured logging".
|
||||||
|
//
|
||||||
|
// With Go's standard log package, we might write:
|
||||||
|
// log.Printf("setting target value %s", targetValue)
|
||||||
|
//
|
||||||
|
// With logr's structured logging, we'd write:
|
||||||
|
// logger.Info("setting target", "value", targetValue)
|
||||||
|
//
|
||||||
|
// Errors are much the same. Instead of:
|
||||||
|
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
|
||||||
|
//
|
||||||
|
// We'd write:
|
||||||
|
// logger.Error(err, "failed to open the pod bay door", "user", user)
|
||||||
|
//
|
||||||
|
// Info() and Error() are very similar, but they are separate methods so that
|
||||||
|
// LogSink implementations can choose to do things like attach additional
|
||||||
|
// information (such as stack traces) on calls to Error(). Error() messages are
|
||||||
|
// always logged, regardless of the current verbosity. If there is no error
|
||||||
|
// instance available, passing nil is valid.
|
||||||
|
//
|
||||||
|
// Verbosity
|
||||||
|
//
|
||||||
|
// Often we want to log information only when the application in "verbose
|
||||||
|
// mode". To write log lines that are more verbose, Logger has a V() method.
|
||||||
|
// The higher the V-level of a log line, the less critical it is considered.
|
||||||
|
// Log-lines with V-levels that are not enabled (as per the LogSink) will not
|
||||||
|
// be written. Level V(0) is the default, and logger.V(0).Info() has the same
|
||||||
|
// meaning as logger.Info(). Negative V-levels have the same meaning as V(0).
|
||||||
|
// Error messages do not have a verbosity level and are always logged.
|
||||||
|
//
|
||||||
|
// Where we might have written:
|
||||||
|
// if flVerbose >= 2 {
|
||||||
|
// log.Printf("an unusual thing happened")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// We can write:
|
||||||
|
// logger.V(2).Info("an unusual thing happened")
|
||||||
|
//
|
||||||
|
// Logger Names
|
||||||
|
//
|
||||||
|
// Logger instances can have name strings so that all messages logged through
|
||||||
|
// that instance have additional context. For example, you might want to add
|
||||||
|
// a subsystem name:
|
||||||
|
//
|
||||||
|
// logger.WithName("compactor").Info("started", "time", time.Now())
|
||||||
|
//
|
||||||
|
// The WithName() method returns a new Logger, which can be passed to
|
||||||
|
// constructors or other functions for further use. Repeated use of WithName()
|
||||||
|
// will accumulate name "segments". These name segments will be joined in some
|
||||||
|
// way by the LogSink implementation. It is strongly recommended that name
|
||||||
|
// segments contain simple identifiers (letters, digits, and hyphen), and do
|
||||||
|
// not contain characters that could muddle the log output or confuse the
|
||||||
|
// joining operation (e.g. whitespace, commas, periods, slashes, brackets,
|
||||||
|
// quotes, etc).
|
||||||
|
//
|
||||||
|
// Saved Values
|
||||||
|
//
|
||||||
|
// Logger instances can store any number of key/value pairs, which will be
|
||||||
|
// logged alongside all messages logged through that instance. For example,
|
||||||
|
// you might want to create a Logger instance per managed object:
|
||||||
|
//
|
||||||
|
// With the standard log package, we might write:
|
||||||
|
// log.Printf("decided to set field foo to value %q for object %s/%s",
|
||||||
|
// targetValue, object.Namespace, object.Name)
|
||||||
|
//
|
||||||
|
// With logr we'd write:
|
||||||
|
// // Elsewhere: set up the logger to log the object name.
|
||||||
|
// obj.logger = mainLogger.WithValues(
|
||||||
|
// "name", obj.name, "namespace", obj.namespace)
|
||||||
|
//
|
||||||
|
// // later on...
|
||||||
|
// obj.logger.Info("setting foo", "value", targetValue)
|
||||||
|
//
|
||||||
|
// Best Practices
|
||||||
|
//
|
||||||
|
// Logger has very few hard rules, with the goal that LogSink implementations
|
||||||
|
// might have a lot of freedom to differentiate. There are, however, some
|
||||||
|
// things to consider.
|
||||||
|
//
|
||||||
|
// The log message consists of a constant message attached to the log line.
|
||||||
|
// This should generally be a simple description of what's occurring, and should
|
||||||
|
// never be a format string. Variable information can then be attached using
|
||||||
|
// named values.
|
||||||
|
//
|
||||||
|
// Keys are arbitrary strings, but should generally be constant values. Values
|
||||||
|
// may be any Go value, but how the value is formatted is determined by the
|
||||||
|
// LogSink implementation.
|
||||||
|
//
|
||||||
|
// Logger instances are meant to be passed around by value. Code that receives
|
||||||
|
// such a value can call its methods without having to check whether the
|
||||||
|
// instance is ready for use.
|
||||||
|
//
|
||||||
|
// Calling methods with the null logger (Logger{}) as instance will crash
|
||||||
|
// because it has no LogSink. Therefore this null logger should never be passed
|
||||||
|
// around. For cases where passing a logger is optional, a pointer to Logger
|
||||||
|
// should be used.
|
||||||
|
//
|
||||||
|
// Key Naming Conventions
|
||||||
|
//
|
||||||
|
// Keys are not strictly required to conform to any specification or regex, but
|
||||||
|
// it is recommended that they:
|
||||||
|
// * be human-readable and meaningful (not auto-generated or simple ordinals)
|
||||||
|
// * be constant (not dependent on input data)
|
||||||
|
// * contain only printable characters
|
||||||
|
// * not contain whitespace or punctuation
|
||||||
|
// * use lower case for simple keys and lowerCamelCase for more complex ones
|
||||||
|
//
|
||||||
|
// These guidelines help ensure that log data is processed properly regardless
|
||||||
|
// of the log implementation. For example, log implementations will try to
|
||||||
|
// output JSON data or will store data for later database (e.g. SQL) queries.
|
||||||
|
//
|
||||||
|
// While users are generally free to use key names of their choice, it's
|
||||||
|
// generally best to avoid using the following keys, as they're frequently used
|
||||||
|
// by implementations:
|
||||||
|
// * "caller": the calling information (file/line) of a particular log line
|
||||||
|
// * "error": the underlying error value in the `Error` method
|
||||||
|
// * "level": the log level
|
||||||
|
// * "logger": the name of the associated logger
|
||||||
|
// * "msg": the log message
|
||||||
|
// * "stacktrace": the stack trace associated with a particular log line or
|
||||||
|
// error (often from the `Error` message)
|
||||||
|
// * "ts": the timestamp for a log line
|
||||||
|
//
|
||||||
|
// Implementations are encouraged to make use of these keys to represent the
|
||||||
|
// above concepts, when necessary (for example, in a pure-JSON output form, it
|
||||||
|
// would be necessary to represent at least message and timestamp as ordinary
|
||||||
|
// named values).
|
||||||
|
//
|
||||||
|
// Break Glass
|
||||||
|
//
|
||||||
|
// Implementations may choose to give callers access to the underlying
|
||||||
|
// logging implementation. The recommended pattern for this is:
|
||||||
|
// // Underlier exposes access to the underlying logging implementation.
|
||||||
|
// // Since callers only have a logr.Logger, they have to know which
|
||||||
|
// // implementation is in use, so this interface is less of an abstraction
|
||||||
|
// // and more of way to test type conversion.
|
||||||
|
// type Underlier interface {
|
||||||
|
// GetUnderlying() <underlying-type>
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Logger grants access to the sink to enable type assertions like this:
|
||||||
|
// func DoSomethingWithImpl(log logr.Logger) {
|
||||||
|
// if underlier, ok := log.GetSink()(impl.Underlier) {
|
||||||
|
// implLogger := underlier.GetUnderlying()
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Custom `With*` functions can be implemented by copying the complete
|
||||||
|
// Logger struct and replacing the sink in the copy:
|
||||||
|
// // WithFooBar changes the foobar parameter in the log sink and returns a
|
||||||
|
// // new logger with that modified sink. It does nothing for loggers where
|
||||||
|
// // the sink doesn't support that parameter.
|
||||||
|
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
|
||||||
|
// if foobarLogSink, ok := log.GetSink()(FoobarSink); ok {
|
||||||
|
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
|
||||||
|
// }
|
||||||
|
// return log
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Don't use New to construct a new Logger with a LogSink retrieved from an
|
||||||
|
// existing Logger. Source code attribution might not work correctly and
|
||||||
|
// unexported fields in Logger get lost.
|
||||||
|
//
|
||||||
|
// Beware that the same LogSink instance may be shared by different logger
|
||||||
|
// instances. Calling functions that modify the LogSink will affect all of
|
||||||
|
// those.
|
||||||
|
package logr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new Logger instance. This is primarily used by libraries
|
||||||
|
// implementing LogSink, rather than end users.
|
||||||
|
func New(sink LogSink) Logger {
|
||||||
|
logger := Logger{}
|
||||||
|
logger.setSink(sink)
|
||||||
|
sink.Init(runtimeInfo)
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSink stores the sink and updates any related fields. It mutates the
|
||||||
|
// logger and thus is only safe to use for loggers that are not currently being
|
||||||
|
// used concurrently.
|
||||||
|
func (l *Logger) setSink(sink LogSink) {
|
||||||
|
l.sink = sink
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSink returns the stored sink.
|
||||||
|
func (l Logger) GetSink() LogSink {
|
||||||
|
return l.sink
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSink returns a copy of the logger with the new sink.
|
||||||
|
func (l Logger) WithSink(sink LogSink) Logger {
|
||||||
|
l.setSink(sink)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger is an interface to an abstract logging implementation. This is a
|
||||||
|
// concrete type for performance reasons, but all the real work is passed on to
|
||||||
|
// a LogSink. Implementations of LogSink should provide their own constructors
|
||||||
|
// that return Logger, not LogSink.
|
||||||
|
//
|
||||||
|
// The underlying sink can be accessed through GetSink and be modified through
|
||||||
|
// WithSink. This enables the implementation of custom extensions (see "Break
|
||||||
|
// Glass" in the package documentation). Normally the sink should be used only
|
||||||
|
// indirectly.
|
||||||
|
type Logger struct {
|
||||||
|
sink LogSink
|
||||||
|
level int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled tests whether this Logger is enabled. For example, commandline
|
||||||
|
// flags might be used to set the logging verbosity and disable some info logs.
|
||||||
|
func (l Logger) Enabled() bool {
|
||||||
|
return l.sink.Enabled(l.level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a non-error message with the given key/value pairs as context.
|
||||||
|
//
|
||||||
|
// The msg argument should be used to add some constant description to the log
|
||||||
|
// line. The key/value pairs can then be used to add additional variable
|
||||||
|
// information. The key/value pairs must alternate string keys and arbitrary
|
||||||
|
// values.
|
||||||
|
func (l Logger) Info(msg string, keysAndValues ...interface{}) {
|
||||||
|
if l.Enabled() {
|
||||||
|
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||||
|
withHelper.GetCallStackHelper()()
|
||||||
|
}
|
||||||
|
l.sink.Info(l.level, msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs an error, with the given message and key/value pairs as context.
|
||||||
|
// It functions similarly to Info, but may have unique behavior, and should be
|
||||||
|
// preferred for logging errors (see the package documentations for more
|
||||||
|
// information). The log message will always be emitted, regardless of
|
||||||
|
// verbosity level.
|
||||||
|
//
|
||||||
|
// The msg argument should be used to add context to any underlying error,
|
||||||
|
// while the err argument should be used to attach the actual error that
|
||||||
|
// triggered this log line, if present. The err parameter is optional
|
||||||
|
// and nil may be passed instead of an error instance.
|
||||||
|
func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
|
||||||
|
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||||
|
withHelper.GetCallStackHelper()()
|
||||||
|
}
|
||||||
|
l.sink.Error(err, msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// V returns a new Logger instance for a specific verbosity level, relative to
|
||||||
|
// this Logger. In other words, V-levels are additive. A higher verbosity
|
||||||
|
// level means a log message is less important. Negative V-levels are treated
|
||||||
|
// as 0.
|
||||||
|
func (l Logger) V(level int) Logger {
|
||||||
|
if level < 0 {
|
||||||
|
level = 0
|
||||||
|
}
|
||||||
|
l.level += level
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValues returns a new Logger instance with additional key/value pairs.
|
||||||
|
// See Info for documentation on how key/value pairs work.
|
||||||
|
func (l Logger) WithValues(keysAndValues ...interface{}) Logger {
|
||||||
|
l.setSink(l.sink.WithValues(keysAndValues...))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName returns a new Logger instance with the specified name element added
|
||||||
|
// to the Logger's name. Successive calls with WithName append additional
|
||||||
|
// suffixes to the Logger's name. It's strongly recommended that name segments
|
||||||
|
// contain only letters, digits, and hyphens (see the package documentation for
|
||||||
|
// more information).
|
||||||
|
func (l Logger) WithName(name string) Logger {
|
||||||
|
l.setSink(l.sink.WithName(name))
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCallDepth returns a Logger instance that offsets the call stack by the
|
||||||
|
// specified number of frames when logging call site information, if possible.
|
||||||
|
// This is useful for users who have helper functions between the "real" call
|
||||||
|
// site and the actual calls to Logger methods. If depth is 0 the attribution
|
||||||
|
// should be to the direct caller of this function. If depth is 1 the
|
||||||
|
// attribution should skip 1 call frame, and so on. Successive calls to this
|
||||||
|
// are additive.
|
||||||
|
//
|
||||||
|
// If the underlying log implementation supports a WithCallDepth(int) method,
|
||||||
|
// it will be called and the result returned. If the implementation does not
|
||||||
|
// support CallDepthLogSink, the original Logger will be returned.
|
||||||
|
//
|
||||||
|
// To skip one level, WithCallStackHelper() should be used instead of
|
||||||
|
// WithCallDepth(1) because it works with implementions that support the
|
||||||
|
// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
|
||||||
|
func (l Logger) WithCallDepth(depth int) Logger {
|
||||||
|
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||||
|
l.setSink(withCallDepth.WithCallDepth(depth))
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCallStackHelper returns a new Logger instance that skips the direct
|
||||||
|
// caller when logging call site information, if possible. This is useful for
|
||||||
|
// users who have helper functions between the "real" call site and the actual
|
||||||
|
// calls to Logger methods and want to support loggers which depend on marking
|
||||||
|
// each individual helper function, like loggers based on testing.T.
|
||||||
|
//
|
||||||
|
// In addition to using that new logger instance, callers also must call the
|
||||||
|
// returned function.
|
||||||
|
//
|
||||||
|
// If the underlying log implementation supports a WithCallDepth(int) method,
|
||||||
|
// WithCallDepth(1) will be called to produce a new logger. If it supports a
|
||||||
|
// WithCallStackHelper() method, that will be also called. If the
|
||||||
|
// implementation does not support either of these, the original Logger will be
|
||||||
|
// returned.
|
||||||
|
func (l Logger) WithCallStackHelper() (func(), Logger) {
|
||||||
|
var helper func()
|
||||||
|
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||||
|
l.setSink(withCallDepth.WithCallDepth(1))
|
||||||
|
}
|
||||||
|
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||||
|
helper = withHelper.GetCallStackHelper()
|
||||||
|
} else {
|
||||||
|
helper = func() {}
|
||||||
|
}
|
||||||
|
return helper, l
|
||||||
|
}
|
||||||
|
|
||||||
|
// contextKey is how we find Loggers in a context.Context.
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||||
|
func FromContext(ctx context.Context) (Logger, error) {
|
||||||
|
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Logger{}, notFoundError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// notFoundError exists to carry an IsNotFound method.
|
||||||
|
type notFoundError struct{}
|
||||||
|
|
||||||
|
func (notFoundError) Error() string {
|
||||||
|
return "no logr.Logger was present"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (notFoundError) IsNotFound() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||||
|
// returns a Logger that discards all log messages.
|
||||||
|
func FromContextOrDiscard(ctx context.Context) Logger {
|
||||||
|
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return Discard()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext returns a new Context, derived from ctx, which carries the
|
||||||
|
// provided Logger.
|
||||||
|
func NewContext(ctx context.Context, logger Logger) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKey{}, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeInfo holds information that the logr "core" library knows which
|
||||||
|
// LogSinks might want to know.
|
||||||
|
type RuntimeInfo struct {
|
||||||
|
// CallDepth is the number of call frames the logr library adds between the
|
||||||
|
// end-user and the LogSink. LogSink implementations which choose to print
|
||||||
|
// the original logging site (e.g. file & line) should climb this many
|
||||||
|
// additional frames to find it.
|
||||||
|
CallDepth int
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeInfo is a static global. It must not be changed at run time.
|
||||||
|
var runtimeInfo = RuntimeInfo{
|
||||||
|
CallDepth: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogSink represents a logging implementation. End-users will generally not
|
||||||
|
// interact with this type.
|
||||||
|
type LogSink interface {
|
||||||
|
// Init receives optional information about the logr library for LogSink
|
||||||
|
// implementations that need it.
|
||||||
|
Init(info RuntimeInfo)
|
||||||
|
|
||||||
|
// Enabled tests whether this LogSink is enabled at the specified V-level.
|
||||||
|
// For example, commandline flags might be used to set the logging
|
||||||
|
// verbosity and disable some info logs.
|
||||||
|
Enabled(level int) bool
|
||||||
|
|
||||||
|
// Info logs a non-error message with the given key/value pairs as context.
|
||||||
|
// The level argument is provided for optional logging. This method will
|
||||||
|
// only be called when Enabled(level) is true. See Logger.Info for more
|
||||||
|
// details.
|
||||||
|
Info(level int, msg string, keysAndValues ...interface{})
|
||||||
|
|
||||||
|
// Error logs an error, with the given message and key/value pairs as
|
||||||
|
// context. See Logger.Error for more details.
|
||||||
|
Error(err error, msg string, keysAndValues ...interface{})
|
||||||
|
|
||||||
|
// WithValues returns a new LogSink with additional key/value pairs. See
|
||||||
|
// Logger.WithValues for more details.
|
||||||
|
WithValues(keysAndValues ...interface{}) LogSink
|
||||||
|
|
||||||
|
// WithName returns a new LogSink with the specified name appended. See
|
||||||
|
// Logger.WithName for more details.
|
||||||
|
WithName(name string) LogSink
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallDepthLogSink represents a Logger that knows how to climb the call stack
|
||||||
|
// to identify the original call site and can offset the depth by a specified
|
||||||
|
// number of frames. This is useful for users who have helper functions
|
||||||
|
// between the "real" call site and the actual calls to Logger methods.
|
||||||
|
// Implementations that log information about the call site (such as file,
|
||||||
|
// function, or line) would otherwise log information about the intermediate
|
||||||
|
// helper functions.
|
||||||
|
//
|
||||||
|
// This is an optional interface and implementations are not required to
|
||||||
|
// support it.
|
||||||
|
type CallDepthLogSink interface {
|
||||||
|
// WithCallDepth returns a LogSink that will offset the call
|
||||||
|
// stack by the specified number of frames when logging call
|
||||||
|
// site information.
|
||||||
|
//
|
||||||
|
// If depth is 0, the LogSink should skip exactly the number
|
||||||
|
// of call frames defined in RuntimeInfo.CallDepth when Info
|
||||||
|
// or Error are called, i.e. the attribution should be to the
|
||||||
|
// direct caller of Logger.Info or Logger.Error.
|
||||||
|
//
|
||||||
|
// If depth is 1 the attribution should skip 1 call frame, and so on.
|
||||||
|
// Successive calls to this are additive.
|
||||||
|
WithCallDepth(depth int) LogSink
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallStackHelperLogSink represents a Logger that knows how to climb
|
||||||
|
// the call stack to identify the original call site and can skip
|
||||||
|
// intermediate helper functions if they mark themselves as
|
||||||
|
// helper. Go's testing package uses that approach.
|
||||||
|
//
|
||||||
|
// This is useful for users who have helper functions between the
|
||||||
|
// "real" call site and the actual calls to Logger methods.
|
||||||
|
// Implementations that log information about the call site (such as
|
||||||
|
// file, function, or line) would otherwise log information about the
|
||||||
|
// intermediate helper functions.
|
||||||
|
//
|
||||||
|
// This is an optional interface and implementations are not required
|
||||||
|
// to support it. Implementations that choose to support this must not
|
||||||
|
// simply implement it as WithCallDepth(1), because
|
||||||
|
// Logger.WithCallStackHelper will call both methods if they are
|
||||||
|
// present. This should only be implemented for LogSinks that actually
|
||||||
|
// need it, as with testing.T.
|
||||||
|
type CallStackHelperLogSink interface {
|
||||||
|
// GetCallStackHelper returns a function that must be called
|
||||||
|
// to mark the direct caller as helper function when logging
|
||||||
|
// call site information.
|
||||||
|
GetCallStackHelper() func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshaler is an optional interface that logged values may choose to
|
||||||
|
// implement. Loggers with structured output, such as JSON, should
|
||||||
|
// log the object return by the MarshalLog method instead of the
|
||||||
|
// original value.
|
||||||
|
type Marshaler interface {
|
||||||
|
// MarshalLog can be used to:
|
||||||
|
// - ensure that structs are not logged as strings when the original
|
||||||
|
// value has a String method: return a different type without a
|
||||||
|
// String method
|
||||||
|
// - select which fields of a complex type should get logged:
|
||||||
|
// return a simpler struct with fewer fields
|
||||||
|
// - log unexported fields: return a different struct
|
||||||
|
// with exported fields
|
||||||
|
//
|
||||||
|
// It may return any value of any type.
|
||||||
|
MarshalLog() interface{}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Minimal Go logging using logr and Go's standard library
|
||||||
|
|
||||||
|
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/stdr.svg)](https://pkg.go.dev/github.com/go-logr/stdr)
|
||||||
|
|
||||||
|
This package implements the [logr interface](https://github.com/go-logr/logr)
|
||||||
|
in terms of Go's standard log package(https://pkg.go.dev/log).
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The logr Authors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package stdr implements github.com/go-logr/logr.Logger in terms of
|
||||||
|
// Go's standard log package.
|
||||||
|
package stdr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
|
"github.com/go-logr/logr/funcr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The global verbosity level. See SetVerbosity().
|
||||||
|
var globalVerbosity int
|
||||||
|
|
||||||
|
// SetVerbosity sets the global level against which all info logs will be
|
||||||
|
// compared. If this is greater than or equal to the "V" of the logger, the
|
||||||
|
// message will be logged. A higher value here means more logs will be written.
|
||||||
|
// The previous verbosity value is returned. This is not concurrent-safe -
|
||||||
|
// callers must be sure to call it from only one goroutine.
|
||||||
|
func SetVerbosity(v int) int {
|
||||||
|
old := globalVerbosity
|
||||||
|
globalVerbosity = v
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a logr.Logger which is implemented by Go's standard log package,
|
||||||
|
// or something like it. If std is nil, this will use a default logger
|
||||||
|
// instead.
|
||||||
|
//
|
||||||
|
// Example: stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)))
|
||||||
|
func New(std StdLogger) logr.Logger {
|
||||||
|
return NewWithOptions(std, Options{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithOptions returns a logr.Logger which is implemented by Go's standard
|
||||||
|
// log package, or something like it. See New for details.
|
||||||
|
func NewWithOptions(std StdLogger, opts Options) logr.Logger {
|
||||||
|
if std == nil {
|
||||||
|
// Go's log.Default() is only available in 1.16 and higher.
|
||||||
|
std = log.New(os.Stderr, "", log.LstdFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Depth < 0 {
|
||||||
|
opts.Depth = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fopts := funcr.Options{
|
||||||
|
LogCaller: funcr.MessageClass(opts.LogCaller),
|
||||||
|
}
|
||||||
|
|
||||||
|
sl := &logger{
|
||||||
|
Formatter: funcr.NewFormatter(fopts),
|
||||||
|
std: std,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For skipping our own logger.Info/Error.
|
||||||
|
sl.Formatter.AddCallDepth(1 + opts.Depth)
|
||||||
|
|
||||||
|
return logr.New(sl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options carries parameters which influence the way logs are generated.
|
||||||
|
type Options struct {
|
||||||
|
// Depth biases the assumed number of call frames to the "true" caller.
|
||||||
|
// This is useful when the calling code calls a function which then calls
|
||||||
|
// stdr (e.g. a logging shim to another API). Values less than zero will
|
||||||
|
// be treated as zero.
|
||||||
|
Depth int
|
||||||
|
|
||||||
|
// LogCaller tells stdr to add a "caller" key to some or all log lines.
|
||||||
|
// Go's log package has options to log this natively, too.
|
||||||
|
LogCaller MessageClass
|
||||||
|
|
||||||
|
// TODO: add an option to log the date/time
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageClass indicates which category or categories of messages to consider.
|
||||||
|
type MessageClass int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// None ignores all message classes.
|
||||||
|
None MessageClass = iota
|
||||||
|
// All considers all message classes.
|
||||||
|
All
|
||||||
|
// Info only considers info messages.
|
||||||
|
Info
|
||||||
|
// Error only considers error messages.
|
||||||
|
Error
|
||||||
|
)
|
||||||
|
|
||||||
|
// StdLogger is the subset of the Go stdlib log.Logger API that is needed for
|
||||||
|
// this adapter.
|
||||||
|
type StdLogger interface {
|
||||||
|
// Output is the same as log.Output and log.Logger.Output.
|
||||||
|
Output(calldepth int, logline string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type logger struct {
|
||||||
|
funcr.Formatter
|
||||||
|
std StdLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ logr.LogSink = &logger{}
|
||||||
|
var _ logr.CallDepthLogSink = &logger{}
|
||||||
|
|
||||||
|
func (l logger) Enabled(level int) bool {
|
||||||
|
return globalVerbosity >= level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Info(level int, msg string, kvList ...interface{}) {
|
||||||
|
prefix, args := l.FormatInfo(level, msg, kvList)
|
||||||
|
if prefix != "" {
|
||||||
|
args = prefix + ": " + args
|
||||||
|
}
|
||||||
|
_ = l.std.Output(l.Formatter.GetDepth()+1, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Error(err error, msg string, kvList ...interface{}) {
|
||||||
|
prefix, args := l.FormatError(err, msg, kvList)
|
||||||
|
if prefix != "" {
|
||||||
|
args = prefix + ": " + args
|
||||||
|
}
|
||||||
|
_ = l.std.Output(l.Formatter.GetDepth()+1, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) WithName(name string) logr.LogSink {
|
||||||
|
l.Formatter.AddName(name)
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) WithValues(kvList ...interface{}) logr.LogSink {
|
||||||
|
l.Formatter.AddValues(kvList)
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) WithCallDepth(depth int) logr.LogSink {
|
||||||
|
l.Formatter.AddCallDepth(depth)
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underlier exposes access to the underlying logging implementation. Since
|
||||||
|
// callers only have a logr.Logger, they have to know which implementation is
|
||||||
|
// in use, so this interface is less of an abstraction and more of way to test
|
||||||
|
// type conversion.
|
||||||
|
type Underlier interface {
|
||||||
|
GetUnderlying() StdLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUnderlying returns the StdLogger underneath this logger. Since StdLogger
|
||||||
|
// is itself an interface, the result may or may not be a Go log.Logger.
|
||||||
|
func (l logger) GetUnderlying() StdLogger {
|
||||||
|
return l.std
|
||||||
|
}
|
201
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE
generated
vendored
Normal file
201
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
61
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/client.go
generated
vendored
Normal file
61
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/client.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultClient is the default Client and is used by Get, Head, Post and PostForm.
|
||||||
|
// Please be careful of intitialization order - for example, if you change
|
||||||
|
// the global propagator, the DefaultClient might still be using the old one
|
||||||
|
var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)}
|
||||||
|
|
||||||
|
// Get is a convenient replacement for http.Get that adds a span around the request.
|
||||||
|
func Get(ctx context.Context, url string) (resp *http.Response, err error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return DefaultClient.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head is a convenient replacement for http.Head that adds a span around the request.
|
||||||
|
func Head(ctx context.Context, url string) (resp *http.Response, err error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return DefaultClient.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post is a convenient replacement for http.Post that adds a span around the request.
|
||||||
|
func Post(ctx context.Context, url, contentType string, body io.Reader) (resp *http.Response, err error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
return DefaultClient.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostForm is a convenient replacement for http.PostForm that adds a span around the request.
|
||||||
|
func PostForm(ctx context.Context, url string, data url.Values) (resp *http.Response, err error) {
|
||||||
|
return Post(ctx, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||||
|
}
|
46
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go
generated
vendored
Normal file
46
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attribute keys that can be added to a span.
|
||||||
|
const (
|
||||||
|
ReadBytesKey = attribute.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read
|
||||||
|
ReadErrorKey = attribute.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded)
|
||||||
|
WroteBytesKey = attribute.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written
|
||||||
|
WriteErrorKey = attribute.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server HTTP metrics
|
||||||
|
const (
|
||||||
|
RequestCount = "http.server.request_count" // Incoming request count total
|
||||||
|
RequestContentLength = "http.server.request_content_length" // Incoming request bytes total
|
||||||
|
ResponseContentLength = "http.server.response_content_length" // Incoming response bytes total
|
||||||
|
ServerLatency = "http.server.duration" // Incoming end to end duration, microseconds
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filter is a predicate used to determine whether a given http.request should
|
||||||
|
// be traced. A Filter must return true if the request should be traced.
|
||||||
|
type Filter func(*http.Request) bool
|
||||||
|
|
||||||
|
func newTracer(tp trace.TracerProvider) trace.Tracer {
|
||||||
|
return tp.Tracer(instrumentationName, trace.WithInstrumentationVersion(SemVersion()))
|
||||||
|
}
|
187
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/config.go
generated
vendored
Normal file
187
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/config.go
generated
vendored
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"go.opentelemetry.io/otel/metric/global"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
instrumentationName = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// config represents the configuration options available for the http.Handler
|
||||||
|
// and http.Transport types.
|
||||||
|
type config struct {
|
||||||
|
Tracer trace.Tracer
|
||||||
|
Meter metric.Meter
|
||||||
|
Propagators propagation.TextMapPropagator
|
||||||
|
SpanStartOptions []trace.SpanStartOption
|
||||||
|
ReadEvent bool
|
||||||
|
WriteEvent bool
|
||||||
|
Filters []Filter
|
||||||
|
SpanNameFormatter func(string, *http.Request) string
|
||||||
|
ClientTrace func(context.Context) *httptrace.ClientTrace
|
||||||
|
|
||||||
|
TracerProvider trace.TracerProvider
|
||||||
|
MeterProvider metric.MeterProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option interface used for setting optional config properties.
|
||||||
|
type Option interface {
|
||||||
|
apply(*config)
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionFunc func(*config)
|
||||||
|
|
||||||
|
func (o optionFunc) apply(c *config) {
|
||||||
|
o(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newConfig creates a new config struct and applies opts to it.
|
||||||
|
func newConfig(opts ...Option) *config {
|
||||||
|
c := &config{
|
||||||
|
Propagators: otel.GetTextMapPropagator(),
|
||||||
|
MeterProvider: global.GetMeterProvider(),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.apply(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context.
|
||||||
|
if c.TracerProvider != nil {
|
||||||
|
c.Tracer = newTracer(c.TracerProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Meter = c.MeterProvider.Meter(
|
||||||
|
instrumentationName,
|
||||||
|
metric.WithInstrumentationVersion(SemVersion()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
|
||||||
|
// If none is specified, the global provider is used.
|
||||||
|
func WithTracerProvider(provider trace.TracerProvider) Option {
|
||||||
|
return optionFunc(func(cfg *config) {
|
||||||
|
if provider != nil {
|
||||||
|
cfg.TracerProvider = provider
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMeterProvider specifies a meter provider to use for creating a meter.
|
||||||
|
// If none is specified, the global provider is used.
|
||||||
|
func WithMeterProvider(provider metric.MeterProvider) Option {
|
||||||
|
return optionFunc(func(cfg *config) {
|
||||||
|
if provider != nil {
|
||||||
|
cfg.MeterProvider = provider
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPublicEndpoint configures the Handler to link the span with an incoming
|
||||||
|
// span context. If this option is not provided, then the association is a child
|
||||||
|
// association instead of a link.
|
||||||
|
func WithPublicEndpoint() Option {
|
||||||
|
return optionFunc(func(c *config) {
|
||||||
|
c.SpanStartOptions = append(c.SpanStartOptions, trace.WithNewRoot())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPropagators configures specific propagators. If this
|
||||||
|
// option isn't specified, then the global TextMapPropagator is used.
|
||||||
|
func WithPropagators(ps propagation.TextMapPropagator) Option {
|
||||||
|
return optionFunc(func(c *config) {
|
||||||
|
if ps != nil {
|
||||||
|
c.Propagators = ps
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSpanOptions configures an additional set of
|
||||||
|
// trace.SpanOptions, which are applied to each new span.
|
||||||
|
func WithSpanOptions(opts ...trace.SpanStartOption) Option {
|
||||||
|
return optionFunc(func(c *config) {
|
||||||
|
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFilter adds a filter to the list of filters used by the handler.
|
||||||
|
// If any filter indicates to exclude a request then the request will not be
|
||||||
|
// traced. All filters must allow a request to be traced for a Span to be created.
|
||||||
|
// If no filters are provided then all requests are traced.
|
||||||
|
// Filters will be invoked for each processed request, it is advised to make them
|
||||||
|
// simple and fast.
|
||||||
|
func WithFilter(f Filter) Option {
|
||||||
|
return optionFunc(func(c *config) {
|
||||||
|
c.Filters = append(c.Filters, f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type event int
|
||||||
|
|
||||||
|
// Different types of events that can be recorded, see WithMessageEvents
|
||||||
|
const (
|
||||||
|
ReadEvents event = iota
|
||||||
|
WriteEvents
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithMessageEvents configures the Handler to record the specified events
|
||||||
|
// (span.AddEvent) on spans. By default only summary attributes are added at the
|
||||||
|
// end of the request.
|
||||||
|
//
|
||||||
|
// Valid events are:
|
||||||
|
// * ReadEvents: Record the number of bytes read after every http.Request.Body.Read
|
||||||
|
// using the ReadBytesKey
|
||||||
|
// * WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
|
||||||
|
// using the WriteBytesKey
|
||||||
|
func WithMessageEvents(events ...event) Option {
|
||||||
|
return optionFunc(func(c *config) {
|
||||||
|
for _, e := range events {
|
||||||
|
switch e {
|
||||||
|
case ReadEvents:
|
||||||
|
c.ReadEvent = true
|
||||||
|
case WriteEvents:
|
||||||
|
c.WriteEvent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSpanNameFormatter takes a function that will be called on every
|
||||||
|
// request and the returned string will become the Span Name
|
||||||
|
func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option {
|
||||||
|
return optionFunc(func(c *config) {
|
||||||
|
c.SpanNameFormatter = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithClientTrace takes a function that returns client trace instance that will be
|
||||||
|
// applied to the requests sent through the otelhttp Transport.
|
||||||
|
func WithClientTrace(f func(context.Context) *httptrace.ClientTrace) Option {
|
||||||
|
return optionFunc(func(c *config) {
|
||||||
|
c.ClientTrace = f
|
||||||
|
})
|
||||||
|
}
|
18
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/doc.go
generated
vendored
Normal file
18
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package otelhttp provides an http.Handler and functions that are intended
|
||||||
|
// to be used to add tracing by wrapping existing handlers (with Handler) and
|
||||||
|
// routes WithRouteTag.
|
||||||
|
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
236
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go
generated
vendored
Normal file
236
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go
generated
vendored
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/felixge/httpsnoop"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ http.Handler = &Handler{}
|
||||||
|
|
||||||
|
// Handler is http middleware that corresponds to the http.Handler interface and
|
||||||
|
// is designed to wrap a http.Mux (or equivalent), while individual routes on
|
||||||
|
// the mux are wrapped with WithRouteTag. A Handler will add various attributes
|
||||||
|
// to the span using the attribute.Keys defined in this package.
|
||||||
|
type Handler struct {
|
||||||
|
operation string
|
||||||
|
handler http.Handler
|
||||||
|
|
||||||
|
tracer trace.Tracer
|
||||||
|
meter metric.Meter
|
||||||
|
propagators propagation.TextMapPropagator
|
||||||
|
spanStartOptions []trace.SpanStartOption
|
||||||
|
readEvent bool
|
||||||
|
writeEvent bool
|
||||||
|
filters []Filter
|
||||||
|
spanNameFormatter func(string, *http.Request) string
|
||||||
|
counters map[string]metric.Int64Counter
|
||||||
|
valueRecorders map[string]metric.Float64Histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultHandlerFormatter(operation string, _ *http.Request) string {
|
||||||
|
return operation
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler wraps the passed handler, functioning like middleware, in a span
|
||||||
|
// named after the operation and with any provided Options.
|
||||||
|
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
|
||||||
|
h := Handler{
|
||||||
|
handler: handler,
|
||||||
|
operation: operation,
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultOpts := []Option{
|
||||||
|
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||||
|
WithSpanNameFormatter(defaultHandlerFormatter),
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newConfig(append(defaultOpts, opts...)...)
|
||||||
|
h.configure(c)
|
||||||
|
h.createMeasures()
|
||||||
|
|
||||||
|
return &h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) configure(c *config) {
|
||||||
|
h.tracer = c.Tracer
|
||||||
|
h.meter = c.Meter
|
||||||
|
h.propagators = c.Propagators
|
||||||
|
h.spanStartOptions = c.SpanStartOptions
|
||||||
|
h.readEvent = c.ReadEvent
|
||||||
|
h.writeEvent = c.WriteEvent
|
||||||
|
h.filters = c.Filters
|
||||||
|
h.spanNameFormatter = c.SpanNameFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
otel.Handle(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) createMeasures() {
|
||||||
|
h.counters = make(map[string]metric.Int64Counter)
|
||||||
|
h.valueRecorders = make(map[string]metric.Float64Histogram)
|
||||||
|
|
||||||
|
requestBytesCounter, err := h.meter.NewInt64Counter(RequestContentLength)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
responseBytesCounter, err := h.meter.NewInt64Counter(ResponseContentLength)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
serverLatencyMeasure, err := h.meter.NewFloat64Histogram(ServerLatency)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
h.counters[RequestContentLength] = requestBytesCounter
|
||||||
|
h.counters[ResponseContentLength] = responseBytesCounter
|
||||||
|
h.valueRecorders[ServerLatency] = serverLatencyMeasure
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP serves HTTP requests (http.Handler)
|
||||||
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requestStartTime := time.Now()
|
||||||
|
for _, f := range h.filters {
|
||||||
|
if !f(r) {
|
||||||
|
// Simply pass through to the handler if a filter rejects the request
|
||||||
|
h.handler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := append([]trace.SpanStartOption{
|
||||||
|
trace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", r)...),
|
||||||
|
trace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(r)...),
|
||||||
|
trace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(h.operation, "", r)...),
|
||||||
|
}, h.spanStartOptions...) // start with the configured options
|
||||||
|
|
||||||
|
tracer := h.tracer
|
||||||
|
|
||||||
|
if tracer == nil {
|
||||||
|
if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
|
||||||
|
tracer = newTracer(span.TracerProvider())
|
||||||
|
} else {
|
||||||
|
tracer = newTracer(otel.GetTracerProvider())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
|
||||||
|
ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
readRecordFunc := func(int64) {}
|
||||||
|
if h.readEvent {
|
||||||
|
readRecordFunc = func(n int64) {
|
||||||
|
span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bw bodyWrapper
|
||||||
|
// if request body is nil we don't want to mutate the body as it will affect
|
||||||
|
// the identity of it in a unforeseeable way because we assert ReadCloser
|
||||||
|
// fullfills a certain interface and it is indeed nil.
|
||||||
|
if r.Body != nil {
|
||||||
|
bw.ReadCloser = r.Body
|
||||||
|
bw.record = readRecordFunc
|
||||||
|
r.Body = &bw
|
||||||
|
}
|
||||||
|
|
||||||
|
writeRecordFunc := func(int64) {}
|
||||||
|
if h.writeEvent {
|
||||||
|
writeRecordFunc = func(n int64) {
|
||||||
|
span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, props: h.propagators}
|
||||||
|
|
||||||
|
// Wrap w to use our ResponseWriter methods while also exposing
|
||||||
|
// other interfaces that w may implement (http.CloseNotifier,
|
||||||
|
// http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom).
|
||||||
|
|
||||||
|
w = httpsnoop.Wrap(w, httpsnoop.Hooks{
|
||||||
|
Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc {
|
||||||
|
return rww.Header
|
||||||
|
},
|
||||||
|
Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
|
||||||
|
return rww.Write
|
||||||
|
},
|
||||||
|
WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
|
||||||
|
return rww.WriteHeader
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
labeler := &Labeler{}
|
||||||
|
ctx = injectLabeler(ctx, labeler)
|
||||||
|
|
||||||
|
h.handler.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
|
||||||
|
setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
|
||||||
|
|
||||||
|
// Add metrics
|
||||||
|
attributes := append(labeler.Get(), semconv.HTTPServerMetricAttributesFromHTTPRequest(h.operation, r)...)
|
||||||
|
h.counters[RequestContentLength].Add(ctx, bw.read, attributes...)
|
||||||
|
h.counters[ResponseContentLength].Add(ctx, rww.written, attributes...)
|
||||||
|
|
||||||
|
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||||
|
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
|
||||||
|
|
||||||
|
h.valueRecorders[ServerLatency].Record(ctx, elapsedTime, attributes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) {
|
||||||
|
attributes := []attribute.KeyValue{}
|
||||||
|
|
||||||
|
// TODO: Consider adding an event after each read and write, possibly as an
|
||||||
|
// option (defaulting to off), so as to not create needlessly verbose spans.
|
||||||
|
if read > 0 {
|
||||||
|
attributes = append(attributes, ReadBytesKey.Int64(read))
|
||||||
|
}
|
||||||
|
if rerr != nil && rerr != io.EOF {
|
||||||
|
attributes = append(attributes, ReadErrorKey.String(rerr.Error()))
|
||||||
|
}
|
||||||
|
if wrote > 0 {
|
||||||
|
attributes = append(attributes, WroteBytesKey.Int64(wrote))
|
||||||
|
}
|
||||||
|
if statusCode > 0 {
|
||||||
|
attributes = append(attributes, semconv.HTTPAttributesFromHTTPStatusCode(statusCode)...)
|
||||||
|
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(statusCode))
|
||||||
|
}
|
||||||
|
if werr != nil && werr != io.EOF {
|
||||||
|
attributes = append(attributes, WriteErrorKey.String(werr.Error()))
|
||||||
|
}
|
||||||
|
span.SetAttributes(attributes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRouteTag annotates a span with the provided route name using the
|
||||||
|
// RouteKey Tag.
|
||||||
|
func WithRouteTag(route string, h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
span := trace.SpanFromContext(r.Context())
|
||||||
|
span.SetAttributes(semconv.HTTPRouteKey.String(route))
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
65
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/labeler.go
generated
vendored
Normal file
65
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/labeler.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Labeler is used to allow instrumented HTTP handlers to add custom attributes to
|
||||||
|
// the metrics recorded by the net/http instrumentation.
|
||||||
|
type Labeler struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
attributes []attribute.KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add attributes to a Labeler.
|
||||||
|
func (l *Labeler) Add(ls ...attribute.KeyValue) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.attributes = append(l.attributes, ls...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a copy of the attributes added to the Labeler.
|
||||||
|
func (l *Labeler) Get() []attribute.KeyValue {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
ret := make([]attribute.KeyValue, len(l.attributes))
|
||||||
|
copy(ret, l.attributes)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
type labelerContextKeyType int
|
||||||
|
|
||||||
|
const lablelerContextKey labelerContextKeyType = 0
|
||||||
|
|
||||||
|
func injectLabeler(ctx context.Context, l *Labeler) context.Context {
|
||||||
|
return context.WithValue(ctx, lablelerContextKey, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelerFromContext retrieves a Labeler instance from the provided context if
|
||||||
|
// one is available. If no Labeler was found in the provided context a new, empty
|
||||||
|
// Labeler is returned and the second return value is false. In this case it is
|
||||||
|
// safe to use the Labeler but any attributes added to it will not be used.
|
||||||
|
func LabelerFromContext(ctx context.Context) (*Labeler, bool) {
|
||||||
|
l, ok := ctx.Value(lablelerContextKey).(*Labeler)
|
||||||
|
if !ok {
|
||||||
|
l = &Labeler{}
|
||||||
|
}
|
||||||
|
return l, ok
|
||||||
|
}
|
190
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go
generated
vendored
Normal file
190
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go
generated
vendored
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transport implements the http.RoundTripper interface and wraps
|
||||||
|
// outbound HTTP(S) requests with a span.
|
||||||
|
type Transport struct {
|
||||||
|
rt http.RoundTripper
|
||||||
|
|
||||||
|
tracer trace.Tracer
|
||||||
|
propagators propagation.TextMapPropagator
|
||||||
|
spanStartOptions []trace.SpanStartOption
|
||||||
|
filters []Filter
|
||||||
|
spanNameFormatter func(string, *http.Request) string
|
||||||
|
clientTrace func(context.Context) *httptrace.ClientTrace
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ http.RoundTripper = &Transport{}
|
||||||
|
|
||||||
|
// NewTransport wraps the provided http.RoundTripper with one that
|
||||||
|
// starts a span and injects the span context into the outbound request headers.
|
||||||
|
//
|
||||||
|
// If the provided http.RoundTripper is nil, http.DefaultTransport will be used
|
||||||
|
// as the base http.RoundTripper
|
||||||
|
func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
|
||||||
|
if base == nil {
|
||||||
|
base = http.DefaultTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
t := Transport{
|
||||||
|
rt: base,
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultOpts := []Option{
|
||||||
|
WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)),
|
||||||
|
WithSpanNameFormatter(defaultTransportFormatter),
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newConfig(append(defaultOpts, opts...)...)
|
||||||
|
t.applyConfig(c)
|
||||||
|
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) applyConfig(c *config) {
|
||||||
|
t.tracer = c.Tracer
|
||||||
|
t.propagators = c.Propagators
|
||||||
|
t.spanStartOptions = c.SpanStartOptions
|
||||||
|
t.filters = c.Filters
|
||||||
|
t.spanNameFormatter = c.SpanNameFormatter
|
||||||
|
t.clientTrace = c.ClientTrace
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultTransportFormatter(_ string, r *http.Request) string {
|
||||||
|
return "HTTP " + r.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip creates a Span and propagates its context via the provided request's headers
|
||||||
|
// before handing the request to the configured base RoundTripper. The created span will
|
||||||
|
// end when the response body is closed or when a read from the body returns io.EOF.
|
||||||
|
func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
for _, f := range t.filters {
|
||||||
|
if !f(r) {
|
||||||
|
// Simply pass through to the base RoundTripper if a filter rejects the request
|
||||||
|
return t.rt.RoundTrip(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracer := t.tracer
|
||||||
|
|
||||||
|
if tracer == nil {
|
||||||
|
if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
|
||||||
|
tracer = newTracer(span.TracerProvider())
|
||||||
|
} else {
|
||||||
|
tracer = newTracer(otel.GetTracerProvider())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options
|
||||||
|
|
||||||
|
ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
|
||||||
|
|
||||||
|
if t.clientTrace != nil {
|
||||||
|
ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...)
|
||||||
|
t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))
|
||||||
|
|
||||||
|
res, err := t.rt.RoundTrip(r)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
span.End()
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(res.StatusCode)...)
|
||||||
|
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(res.StatusCode))
|
||||||
|
res.Body = newWrappedBody(span, res.Body)
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// newWrappedBody returns a new and appropriately scoped *wrappedBody as an
|
||||||
|
// io.ReadCloser. If the passed body implements io.Writer, the returned value
|
||||||
|
// will implement io.ReadWriteCloser.
|
||||||
|
func newWrappedBody(span trace.Span, body io.ReadCloser) io.ReadCloser {
|
||||||
|
// The successful protocol switch responses will have a body that
|
||||||
|
// implement an io.ReadWriteCloser. Ensure this interface type continues
|
||||||
|
// to be satisfied if that is the case.
|
||||||
|
if _, ok := body.(io.ReadWriteCloser); ok {
|
||||||
|
return &wrappedBody{span: span, body: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the implementation of the io.ReadWriteCloser and only implement
|
||||||
|
// the io.ReadCloser.
|
||||||
|
return struct{ io.ReadCloser }{&wrappedBody{span: span, body: body}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrappedBody is the response body type returned by the transport
|
||||||
|
// instrumentation to complete a span. Errors encountered when using the
|
||||||
|
// response body are recorded in span tracking the response.
|
||||||
|
//
|
||||||
|
// The span tracking the response is ended when this body is closed.
|
||||||
|
//
|
||||||
|
// If the response body implements the io.Writer interface (i.e. for
|
||||||
|
// successful protocol switches), the wrapped body also will.
|
||||||
|
type wrappedBody struct {
|
||||||
|
span trace.Span
|
||||||
|
body io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.ReadWriteCloser = &wrappedBody{}
|
||||||
|
|
||||||
|
func (wb *wrappedBody) Write(p []byte) (int, error) {
|
||||||
|
// This will not panic given the guard in newWrappedBody.
|
||||||
|
n, err := wb.body.(io.Writer).Write(p)
|
||||||
|
if err != nil {
|
||||||
|
wb.span.RecordError(err)
|
||||||
|
wb.span.SetStatus(codes.Error, err.Error())
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wb *wrappedBody) Read(b []byte) (int, error) {
|
||||||
|
n, err := wb.body.Read(b)
|
||||||
|
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
// nothing to do here but fall through to the return
|
||||||
|
case io.EOF:
|
||||||
|
wb.span.End()
|
||||||
|
default:
|
||||||
|
wb.span.RecordError(err)
|
||||||
|
wb.span.SetStatus(codes.Error, err.Error())
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wb *wrappedBody) Close() error {
|
||||||
|
wb.span.End()
|
||||||
|
return wb.body.Close()
|
||||||
|
}
|
26
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go
generated
vendored
Normal file
26
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
// Version is the current release version of the otelhttp instrumentation.
|
||||||
|
func Version() string {
|
||||||
|
return "0.29.0"
|
||||||
|
// This string is updated by the pre_release.sh script during release
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemVersion is the semantic version to be supplied to tracer/meter creation.
|
||||||
|
func SemVersion() string {
|
||||||
|
return "semver:" + Version()
|
||||||
|
}
|
96
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/wrap.go
generated
vendored
Normal file
96
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/wrap.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otelhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ io.ReadCloser = &bodyWrapper{}
|
||||||
|
|
||||||
|
// bodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number
|
||||||
|
// of bytes read and the last error
|
||||||
|
type bodyWrapper struct {
|
||||||
|
io.ReadCloser
|
||||||
|
record func(n int64) // must not be nil
|
||||||
|
|
||||||
|
read int64
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *bodyWrapper) Read(b []byte) (int, error) {
|
||||||
|
n, err := w.ReadCloser.Read(b)
|
||||||
|
n1 := int64(n)
|
||||||
|
w.read += n1
|
||||||
|
w.err = err
|
||||||
|
w.record(n1)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *bodyWrapper) Close() error {
|
||||||
|
return w.ReadCloser.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ http.ResponseWriter = &respWriterWrapper{}
|
||||||
|
|
||||||
|
// respWriterWrapper wraps a http.ResponseWriter in order to track the number of
|
||||||
|
// bytes written, the last error, and to catch the returned statusCode
|
||||||
|
// TODO: The wrapped http.ResponseWriter doesn't implement any of the optional
|
||||||
|
// types (http.Hijacker, http.Pusher, http.CloseNotifier, http.Flusher, etc)
|
||||||
|
// that may be useful when using it in real life situations.
|
||||||
|
type respWriterWrapper struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
record func(n int64) // must not be nil
|
||||||
|
|
||||||
|
// used to inject the header
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
props propagation.TextMapPropagator
|
||||||
|
|
||||||
|
written int64
|
||||||
|
statusCode int
|
||||||
|
err error
|
||||||
|
wroteHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *respWriterWrapper) Header() http.Header {
|
||||||
|
return w.ResponseWriter.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *respWriterWrapper) Write(p []byte) (int, error) {
|
||||||
|
if !w.wroteHeader {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
n, err := w.ResponseWriter.Write(p)
|
||||||
|
n1 := int64(n)
|
||||||
|
w.record(n1)
|
||||||
|
w.written += n1
|
||||||
|
w.err = err
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *respWriterWrapper) WriteHeader(statusCode int) {
|
||||||
|
if w.wroteHeader {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.wroteHeader = true
|
||||||
|
w.statusCode = statusCode
|
||||||
|
w.props.Inject(w.ctx, propagation.HeaderCarrier(w.Header()))
|
||||||
|
w.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||||
|
*.{bat,[bB][aA][tT]} text eol=crlf
|
|
@ -0,0 +1,21 @@
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
.tools/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.iml
|
||||||
|
*.so
|
||||||
|
coverage.*
|
||||||
|
|
||||||
|
gen/
|
||||||
|
|
||||||
|
/example/fib/fib
|
||||||
|
/example/jaeger/jaeger
|
||||||
|
/example/namedtracer/namedtracer
|
||||||
|
/example/opencensus/opencensus
|
||||||
|
/example/passthrough/passthrough
|
||||||
|
/example/prometheus/prometheus
|
||||||
|
/example/prom-collector/prom-collector
|
||||||
|
/example/zipkin/zipkin
|
||||||
|
/example/otel-collector/otel-collector
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "opentelemetry-proto"]
|
||||||
|
path = exporters/otlp/internal/opentelemetry-proto
|
||||||
|
url = https://github.com/open-telemetry/opentelemetry-proto
|
|
@ -0,0 +1,47 @@
|
||||||
|
# See https://github.com/golangci/golangci-lint#config-file
|
||||||
|
run:
|
||||||
|
issues-exit-code: 1 #Default
|
||||||
|
tests: true #Default
|
||||||
|
|
||||||
|
linters:
|
||||||
|
# Disable everything by default so upgrades to not include new "default
|
||||||
|
# enabled" linters.
|
||||||
|
disable-all: true
|
||||||
|
# Specifically enable linters we want to use.
|
||||||
|
enable:
|
||||||
|
- deadcode
|
||||||
|
- errcheck
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
# helpers in tests often (rightfully) pass a *testing.T as their first argument
|
||||||
|
- path: _test\.go
|
||||||
|
text: "context.Context should be the first parameter of a function"
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
# Yes, they are, but it's okay in a test
|
||||||
|
- path: _test\.go
|
||||||
|
text: "exported func.*returns unexported type.*which can be annoying to use"
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
ignore-words:
|
||||||
|
- cancelled
|
||||||
|
goimports:
|
||||||
|
local-prefixes: go.opentelemetry.io
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"ignorePatterns": [
|
||||||
|
{
|
||||||
|
"pattern": "^http(s)?://localhost"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"replacementPatterns": [
|
||||||
|
{
|
||||||
|
"pattern": "^/registry",
|
||||||
|
"replacement": "https://opentelemetry.io/registry"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"retryOn429": true,
|
||||||
|
"retryCount": 5,
|
||||||
|
"fallbackRetryDelay": "30s"
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Default state for all rules
|
||||||
|
default: true
|
||||||
|
|
||||||
|
# ul-style
|
||||||
|
MD004: false
|
||||||
|
|
||||||
|
# hard-tabs
|
||||||
|
MD010: false
|
||||||
|
|
||||||
|
# line-length
|
||||||
|
MD013: false
|
||||||
|
|
||||||
|
# no-duplicate-header
|
||||||
|
MD024:
|
||||||
|
siblings_only: true
|
||||||
|
|
||||||
|
#single-title
|
||||||
|
MD025: false
|
||||||
|
|
||||||
|
# ol-prefix
|
||||||
|
MD029:
|
||||||
|
style: ordered
|
||||||
|
|
||||||
|
# no-inline-html
|
||||||
|
MD033: false
|
||||||
|
|
||||||
|
# fenced-code-language
|
||||||
|
MD040: false
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,17 @@
|
||||||
|
#####################################################
|
||||||
|
#
|
||||||
|
# List of approvers for this repository
|
||||||
|
#
|
||||||
|
#####################################################
|
||||||
|
#
|
||||||
|
# Learn about membership in OpenTelemetry community:
|
||||||
|
# https://github.com/open-telemetry/community/blob/main/community-membership.md
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Learn about CODEOWNERS file format:
|
||||||
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
#
|
||||||
|
|
||||||
|
* @jmacd @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @paivagustavo @MadVikingGod @pellared
|
||||||
|
|
||||||
|
CODEOWNERS @MrAlias @Aneurysm9 @MadVikingGod
|
|
@ -0,0 +1,521 @@
|
||||||
|
# Contributing to opentelemetry-go
|
||||||
|
|
||||||
|
The Go special interest group (SIG) meets regularly. See the
|
||||||
|
OpenTelemetry
|
||||||
|
[community](https://github.com/open-telemetry/community#golang-sdk)
|
||||||
|
repo for information on this and other language SIGs.
|
||||||
|
|
||||||
|
See the [public meeting
|
||||||
|
notes](https://docs.google.com/document/d/1A63zSWX0x2CyCK_LoNhmQC4rqhLpYXJzXbEPDUQ2n6w/edit#heading=h.9tngw7jdwd6b)
|
||||||
|
for a summary description of past meetings. To request edit access,
|
||||||
|
join the meeting or get in touch on
|
||||||
|
[Slack](https://cloud-native.slack.com/archives/C01NPAXACKT).
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
You can view and edit the source code by cloning this repository:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/open-telemetry/opentelemetry-go.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `make test` to run the tests instead of `go test`.
|
||||||
|
|
||||||
|
There are some generated files checked into the repo. To make sure
|
||||||
|
that the generated files are up-to-date, run `make` (or `make
|
||||||
|
precommit` - the `precommit` target is the default).
|
||||||
|
|
||||||
|
The `precommit` target also fixes the formatting of the code and
|
||||||
|
checks the status of the go module files.
|
||||||
|
|
||||||
|
If after running `make precommit` the output of `git status` contains
|
||||||
|
`nothing to commit, working tree clean` then it means that everything
|
||||||
|
is up-to-date and properly formatted.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
### How to Send Pull Requests
|
||||||
|
|
||||||
|
Everyone is welcome to contribute code to `opentelemetry-go` via
|
||||||
|
GitHub pull requests (PRs).
|
||||||
|
|
||||||
|
To create a new PR, fork the project in GitHub and clone the upstream
|
||||||
|
repo:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -d go.opentelemetry.io/otel
|
||||||
|
```
|
||||||
|
|
||||||
|
(This may print some warning about "build constraints exclude all Go
|
||||||
|
files", just ignore it.)
|
||||||
|
|
||||||
|
This will put the project in `${GOPATH}/src/go.opentelemetry.io/otel`. You
|
||||||
|
can alternatively use `git` directly with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/open-telemetry/opentelemetry-go
|
||||||
|
```
|
||||||
|
|
||||||
|
(Note that `git clone` is *not* using the `go.opentelemetry.io/otel` name -
|
||||||
|
that name is a kind of a redirector to GitHub that `go get` can
|
||||||
|
understand, but `git` does not.)
|
||||||
|
|
||||||
|
This would put the project in the `opentelemetry-go` directory in
|
||||||
|
current working directory.
|
||||||
|
|
||||||
|
Enter the newly created directory and add your fork as a new remote:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git remote add <YOUR_FORK> git@github.com:<YOUR_GITHUB_USERNAME>/opentelemetry-go
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out a new branch, make modifications, run linters and tests, update
|
||||||
|
`CHANGELOG.md`, and push the branch to your fork:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git checkout -b <YOUR_BRANCH_NAME>
|
||||||
|
# edit files
|
||||||
|
# update changelog
|
||||||
|
make precommit
|
||||||
|
git add -p
|
||||||
|
git commit
|
||||||
|
git push <YOUR_FORK> <YOUR_BRANCH_NAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
Open a pull request against the main `opentelemetry-go` repo. Be sure to add the pull
|
||||||
|
request ID to the entry you added to `CHANGELOG.md`.
|
||||||
|
|
||||||
|
### How to Receive Comments
|
||||||
|
|
||||||
|
* If the PR is not ready for review, please put `[WIP]` in the title,
|
||||||
|
tag it as `work-in-progress`, or mark it as
|
||||||
|
[`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/).
|
||||||
|
* Make sure CLA is signed and CI is clear.
|
||||||
|
|
||||||
|
### How to Get PRs Merged
|
||||||
|
|
||||||
|
A PR is considered to be **ready to merge** when:
|
||||||
|
|
||||||
|
* It has received two approvals from Collaborators/Maintainers (at
|
||||||
|
different companies). This is not enforced through technical means
|
||||||
|
and a PR may be **ready to merge** with a single approval if the change
|
||||||
|
and its approach have been discussed and consensus reached.
|
||||||
|
* Feedback has been addressed.
|
||||||
|
* Any substantive changes to your PR will require that you clear any prior
|
||||||
|
Approval reviews, this includes changes resulting from other feedback. Unless
|
||||||
|
the approver explicitly stated that their approval will persist across
|
||||||
|
changes it should be assumed that the PR needs their review again. Other
|
||||||
|
project members (e.g. approvers, maintainers) can help with this if there are
|
||||||
|
any questions or if you forget to clear reviews.
|
||||||
|
* It has been open for review for at least one working day. This gives
|
||||||
|
people reasonable time to review.
|
||||||
|
* Trivial changes (typo, cosmetic, doc, etc.) do not have to wait for
|
||||||
|
one day and may be merged with a single Maintainer's approval.
|
||||||
|
* `CHANGELOG.md` has been updated to reflect what has been
|
||||||
|
added, changed, removed, or fixed.
|
||||||
|
* `README.md` has been updated if necessary.
|
||||||
|
* Urgent fix can take exception as long as it has been actively
|
||||||
|
communicated.
|
||||||
|
|
||||||
|
Any Maintainer can merge the PR once it is **ready to merge**.
|
||||||
|
|
||||||
|
## Design Choices
|
||||||
|
|
||||||
|
As with other OpenTelemetry clients, opentelemetry-go follows the
|
||||||
|
[opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification).
|
||||||
|
|
||||||
|
It's especially valuable to read through the [library
|
||||||
|
guidelines](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/library-guidelines.md).
|
||||||
|
|
||||||
|
### Focus on Capabilities, Not Structure Compliance
|
||||||
|
|
||||||
|
OpenTelemetry is an evolving specification, one where the desires and
|
||||||
|
use cases are clear, but the method to satisfy those uses cases are
|
||||||
|
not.
|
||||||
|
|
||||||
|
As such, Contributions should provide functionality and behavior that
|
||||||
|
conforms to the specification, but the interface and structure is
|
||||||
|
flexible.
|
||||||
|
|
||||||
|
It is preferable to have contributions follow the idioms of the
|
||||||
|
language rather than conform to specific API names or argument
|
||||||
|
patterns in the spec.
|
||||||
|
|
||||||
|
For a deeper discussion, see
|
||||||
|
[this](https://github.com/open-telemetry/opentelemetry-specification/issues/165).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Each non-example Go Module should have its own `README.md` containing:
|
||||||
|
|
||||||
|
- A pkg.go.dev badge which can be generated [here](https://pkg.go.dev/badge/).
|
||||||
|
- Brief description.
|
||||||
|
- Installation instructions (and requirements if applicable).
|
||||||
|
- Hyperlink to an example. Depending on the component the example can be:
|
||||||
|
- An `example_test.go` like [here](exporters/stdout/stdouttrace/example_test.go).
|
||||||
|
- A sample Go application with its own `README.md`, like [here](example/zipkin).
|
||||||
|
- Additional documentation sections such us:
|
||||||
|
- Configuration,
|
||||||
|
- Contributing,
|
||||||
|
- References.
|
||||||
|
|
||||||
|
[Here](exporters/jaeger/README.md) is an example of a concise `README.md`.
|
||||||
|
|
||||||
|
Moreover, it should be possible to navigate to any `README.md` from the
|
||||||
|
root `README.md`.
|
||||||
|
|
||||||
|
## Style Guide
|
||||||
|
|
||||||
|
One of the primary goals of this project is that it is actually used by
|
||||||
|
developers. With this goal in mind the project strives to build
|
||||||
|
user-friendly and idiomatic Go code adhering to the Go community's best
|
||||||
|
practices.
|
||||||
|
|
||||||
|
For a non-comprehensive but foundational overview of these best practices
|
||||||
|
the [Effective Go](https://golang.org/doc/effective_go.html) documentation
|
||||||
|
is an excellent starting place.
|
||||||
|
|
||||||
|
As a convenience for developers building this project the `make precommit`
|
||||||
|
will format, lint, validate, and in some cases fix the changes you plan to
|
||||||
|
submit. This check will need to pass for your changes to be able to be
|
||||||
|
merged.
|
||||||
|
|
||||||
|
In addition to idiomatic Go, the project has adopted certain standards for
|
||||||
|
implementations of common patterns. These standards should be followed as a
|
||||||
|
default, and if they are not followed documentation needs to be included as
|
||||||
|
to the reasons why.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
When creating an instantiation function for a complex `type T struct`, it is
|
||||||
|
useful to allow variable number of options to be applied. However, the strong
|
||||||
|
type system of Go restricts the function design options. There are a few ways
|
||||||
|
to solve this problem, but we have landed on the following design.
|
||||||
|
|
||||||
|
#### `config`
|
||||||
|
|
||||||
|
Configuration should be held in a `struct` named `config`, or prefixed with
|
||||||
|
specific type name this Configuration applies to if there are multiple
|
||||||
|
`config` in the package. This type must contain configuration options.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// config contains configuration options for a thing.
|
||||||
|
type config struct {
|
||||||
|
// options ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In general the `config` type will not need to be used externally to the
|
||||||
|
package and should be unexported. If, however, it is expected that the user
|
||||||
|
will likely want to build custom options for the configuration, the `config`
|
||||||
|
should be exported. Please, include in the documentation for the `config`
|
||||||
|
how the user can extend the configuration.
|
||||||
|
|
||||||
|
It is important that internal `config` are not shared across package boundaries.
|
||||||
|
Meaning a `config` from one package should not be directly used by another. The
|
||||||
|
one exception is the API packages. The configs from the base API, eg.
|
||||||
|
`go.opentelemetry.io/otel/trace.TracerConfig` and
|
||||||
|
`go.opentelemetry.io/otel/metric.InstrumentConfig`, are intended to be consumed
|
||||||
|
by the SDK therefor it is expected that these are exported.
|
||||||
|
|
||||||
|
When a config is exported we want to maintain forward and backward
|
||||||
|
compatibility, to achieve this no fields should be exported but should
|
||||||
|
instead be accessed by methods.
|
||||||
|
|
||||||
|
Optionally, it is common to include a `newConfig` function (with the same
|
||||||
|
naming scheme). This function wraps any defaults setting and looping over
|
||||||
|
all options to create a configured `config`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// newConfig returns an appropriately configured config.
|
||||||
|
func newConfig(options ...Option) config {
|
||||||
|
// Set default values for config.
|
||||||
|
config := config{/* […] */}
|
||||||
|
for _, option := range options {
|
||||||
|
config = option.apply(config)
|
||||||
|
}
|
||||||
|
// Preform any validation here.
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If validation of the `config` options is also preformed this can return an
|
||||||
|
error as well that is expected to be handled by the instantiation function
|
||||||
|
or propagated to the user.
|
||||||
|
|
||||||
|
Given the design goal of not having the user need to work with the `config`,
|
||||||
|
the `newConfig` function should also be unexported.
|
||||||
|
|
||||||
|
#### `Option`
|
||||||
|
|
||||||
|
To set the value of the options a `config` contains, a corresponding
|
||||||
|
`Option` interface type should be used.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Option interface {
|
||||||
|
apply(config) config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Having `apply` unexported makes sure that it will not be used externally.
|
||||||
|
Moreover, the interface becomes sealed so the user cannot easily implement
|
||||||
|
the interface on its own.
|
||||||
|
|
||||||
|
The `apply` method should return a modified version of the passed config.
|
||||||
|
This approach, instead of passing a pointer, is used to prevent the config from being allocated to the heap.
|
||||||
|
|
||||||
|
The name of the interface should be prefixed in the same way the
|
||||||
|
corresponding `config` is (if at all).
|
||||||
|
|
||||||
|
#### Options
|
||||||
|
|
||||||
|
All user configurable options for a `config` must have a related unexported
|
||||||
|
implementation of the `Option` interface and an exported configuration
|
||||||
|
function that wraps this implementation.
|
||||||
|
|
||||||
|
The wrapping function name should be prefixed with `With*` (or in the
|
||||||
|
special case of a boolean options `Without*`) and should have the following
|
||||||
|
function signature.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func With*(…) Option { … }
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `bool` Options
|
||||||
|
|
||||||
|
```go
|
||||||
|
type defaultFalseOption bool
|
||||||
|
|
||||||
|
func (o defaultFalseOption) apply(c config) config {
|
||||||
|
c.Bool = bool(o)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOption sets a T to have an option included.
|
||||||
|
func WithOption() Option {
|
||||||
|
return defaultFalseOption(true)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type defaultTrueOption bool
|
||||||
|
|
||||||
|
func (o defaultTrueOption) apply(c config) config {
|
||||||
|
c.Bool = bool(o)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutOption sets a T to have Bool option excluded.
|
||||||
|
func WithoutOption() Option {
|
||||||
|
return defaultTrueOption(false)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Declared Type Options
|
||||||
|
|
||||||
|
```go
|
||||||
|
type myTypeOption struct {
|
||||||
|
MyType MyType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o myTypeOption) apply(c config) config {
|
||||||
|
c.MyType = o.MyType
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMyType sets T to have include MyType.
|
||||||
|
func WithMyType(t MyType) Option {
|
||||||
|
return myTypeOption{t}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Functional Options
|
||||||
|
|
||||||
|
```go
|
||||||
|
type optionFunc func(config) config
|
||||||
|
|
||||||
|
func (fn optionFunc) apply(c config) config {
|
||||||
|
return fn(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMyType sets t as MyType.
|
||||||
|
func WithMyType(t MyType) Option {
|
||||||
|
return optionFunc(func(c config) config {
|
||||||
|
c.MyType = t
|
||||||
|
return c
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Instantiation
|
||||||
|
|
||||||
|
Using this configuration pattern to configure instantiation with a `NewT`
|
||||||
|
function.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewT(options ...Option) T {…}
|
||||||
|
```
|
||||||
|
|
||||||
|
Any required parameters can be declared before the variadic `options`.
|
||||||
|
|
||||||
|
#### Dealing with Overlap
|
||||||
|
|
||||||
|
Sometimes there are multiple complex `struct` that share common
|
||||||
|
configuration and also have distinct configuration. To avoid repeated
|
||||||
|
portions of `config`s, a common `config` can be used with the union of
|
||||||
|
options being handled with the `Option` interface.
|
||||||
|
|
||||||
|
For example.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// config holds options for all animals.
|
||||||
|
type config struct {
|
||||||
|
Weight float64
|
||||||
|
Color string
|
||||||
|
MaxAltitude float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// DogOption apply Dog specific options.
|
||||||
|
type DogOption interface {
|
||||||
|
applyDog(config) config
|
||||||
|
}
|
||||||
|
|
||||||
|
// BirdOption apply Bird specific options.
|
||||||
|
type BirdOption interface {
|
||||||
|
applyBird(config) config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option apply options for all animals.
|
||||||
|
type Option interface {
|
||||||
|
BirdOption
|
||||||
|
DogOption
|
||||||
|
}
|
||||||
|
|
||||||
|
type weightOption float64
|
||||||
|
|
||||||
|
func (o weightOption) applyDog(c config) config {
|
||||||
|
c.Weight = float64(o)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o weightOption) applyBird(c config) config {
|
||||||
|
c.Weight = float64(o)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithWeight(w float64) Option { return weightOption(w) }
|
||||||
|
|
||||||
|
type furColorOption string
|
||||||
|
|
||||||
|
func (o furColorOption) applyDog(c config) config {
|
||||||
|
c.Color = string(o)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFurColor(c string) DogOption { return furColorOption(c) }
|
||||||
|
|
||||||
|
type maxAltitudeOption float64
|
||||||
|
|
||||||
|
func (o maxAltitudeOption) applyBird(c config) config {
|
||||||
|
c.MaxAltitude = float64(o)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMaxAltitude(a float64) BirdOption { return maxAltitudeOption(a) }
|
||||||
|
|
||||||
|
func NewDog(name string, o ...DogOption) Dog {…}
|
||||||
|
func NewBird(name string, o ...BirdOption) Bird {…}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interfaces
|
||||||
|
|
||||||
|
To allow other developers to better comprehend the code, it is important
|
||||||
|
to ensure it is sufficiently documented. One simple measure that contributes
|
||||||
|
to this aim is self-documenting by naming method parameters. Therefore,
|
||||||
|
where appropriate, methods of every exported interface type should have
|
||||||
|
their parameters appropriately named.
|
||||||
|
|
||||||
|
#### Interface Stability
|
||||||
|
|
||||||
|
All exported stable interfaces that include the following warning in their
|
||||||
|
doumentation are allowed to be extended with additional methods.
|
||||||
|
|
||||||
|
> Warning: methods may be added to this interface in minor releases.
|
||||||
|
|
||||||
|
Otherwise, stable interfaces MUST NOT be modified.
|
||||||
|
|
||||||
|
If new functionality is needed for an interface that cannot be changed it MUST
|
||||||
|
be added by including an additional interface. That added interface can be a
|
||||||
|
simple interface for the specific functionality that you want to add or it can
|
||||||
|
be a super-set of the original interface. For example, if you wanted to a
|
||||||
|
`Close` method to the `Exporter` interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Exporter interface {
|
||||||
|
Export()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A new interface, `Closer`, can be added:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Closer interface {
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Code that is passed the `Exporter` interface can now check to see if the passed
|
||||||
|
value also satisfies the new interface. E.g.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func caller(e Exporter) {
|
||||||
|
/* ... */
|
||||||
|
if c, ok := e.(Closer); ok {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, a new type that is the super-set of an `Exporter` can be created.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type ClosingExporter struct {
|
||||||
|
Exporter
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This new type can be used similar to the simple interface above in that a
|
||||||
|
passed `Exporter` type can be asserted to satisfy the `ClosingExporter` type
|
||||||
|
and the `Close` method called.
|
||||||
|
|
||||||
|
This super-set approach can be useful if there is explicit behavior that needs
|
||||||
|
to be coupled with the original type and passed as a unified type to a new
|
||||||
|
function, but, because of this coupling, it also limits the applicability of
|
||||||
|
the added functionality. If there exist other interfaces where this
|
||||||
|
functionality should be added, each one will need their own super-set
|
||||||
|
interfaces and will duplicate the pattern. For this reason, the simple targeted
|
||||||
|
interface that defines the specific functionality should be preferred.
|
||||||
|
|
||||||
|
## Approvers and Maintainers
|
||||||
|
|
||||||
|
Approvers:
|
||||||
|
|
||||||
|
- [Evan Torrie](https://github.com/evantorrie), Verizon Media
|
||||||
|
- [Josh MacDonald](https://github.com/jmacd), LightStep
|
||||||
|
- [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics
|
||||||
|
- [David Ashpole](https://github.com/dashpole), Google
|
||||||
|
- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep
|
||||||
|
- [Robert Pająk](https://github.com/pellared), Splunk
|
||||||
|
|
||||||
|
Maintainers:
|
||||||
|
|
||||||
|
- [Aaron Clawson](https://github.com/MadVikingGod), LightStep
|
||||||
|
- [Anthony Mirabella](https://github.com/Aneurysm9), AWS
|
||||||
|
- [Tyler Yahn](https://github.com/MrAlias), Splunk
|
||||||
|
|
||||||
|
### Become an Approver or a Maintainer
|
||||||
|
|
||||||
|
See the [community membership document in OpenTelemetry community
|
||||||
|
repo](https://github.com/open-telemetry/community/blob/main/community-membership.md).
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,212 @@
|
||||||
|
# Copyright The OpenTelemetry Authors
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
TOOLS_MOD_DIR := ./internal/tools
|
||||||
|
|
||||||
|
ALL_DOCS := $(shell find . -name '*.md' -type f | sort)
|
||||||
|
ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||||
|
OTEL_GO_MOD_DIRS := $(filter-out $(TOOLS_MOD_DIR), $(ALL_GO_MOD_DIRS))
|
||||||
|
ALL_COVERAGE_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | egrep -v '^./example|^$(TOOLS_MOD_DIR)' | sort)
|
||||||
|
|
||||||
|
GO = go
|
||||||
|
TIMEOUT = 60
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := precommit
|
||||||
|
|
||||||
|
.PHONY: precommit ci
|
||||||
|
precommit: license-check misspell go-mod-tidy golangci-lint-fix test-default
|
||||||
|
ci: dependabot-check license-check lint vanity-import-check build test-default check-clean-work-tree test-coverage
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
|
||||||
|
TOOLS = $(CURDIR)/.tools
|
||||||
|
|
||||||
|
$(TOOLS):
|
||||||
|
@mkdir -p $@
|
||||||
|
$(TOOLS)/%: | $(TOOLS)
|
||||||
|
cd $(TOOLS_MOD_DIR) && \
|
||||||
|
$(GO) build -o $@ $(PACKAGE)
|
||||||
|
|
||||||
|
MULTIMOD = $(TOOLS)/multimod
|
||||||
|
$(TOOLS)/multimod: PACKAGE=go.opentelemetry.io/build-tools/multimod
|
||||||
|
|
||||||
|
SEMCONVGEN = $(TOOLS)/semconvgen
|
||||||
|
$(TOOLS)/semconvgen: PACKAGE=go.opentelemetry.io/build-tools/semconvgen
|
||||||
|
|
||||||
|
CROSSLINK = $(TOOLS)/crosslink
|
||||||
|
$(TOOLS)/crosslink: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/crosslink
|
||||||
|
|
||||||
|
GOLANGCI_LINT = $(TOOLS)/golangci-lint
|
||||||
|
$(TOOLS)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||||
|
|
||||||
|
MISSPELL = $(TOOLS)/misspell
|
||||||
|
$(TOOLS)/misspell: PACKAGE=github.com/client9/misspell/cmd/misspell
|
||||||
|
|
||||||
|
GOCOVMERGE = $(TOOLS)/gocovmerge
|
||||||
|
$(TOOLS)/gocovmerge: PACKAGE=github.com/wadey/gocovmerge
|
||||||
|
|
||||||
|
STRINGER = $(TOOLS)/stringer
|
||||||
|
$(TOOLS)/stringer: PACKAGE=golang.org/x/tools/cmd/stringer
|
||||||
|
|
||||||
|
PORTO = $(TOOLS)/porto
|
||||||
|
$(TOOLS)/porto: PACKAGE=github.com/jcchavezs/porto/cmd/porto
|
||||||
|
|
||||||
|
GOJQ = $(TOOLS)/gojq
|
||||||
|
$(TOOLS)/gojq: PACKAGE=github.com/itchyny/gojq/cmd/gojq
|
||||||
|
|
||||||
|
.PHONY: tools
|
||||||
|
tools: $(CROSSLINK) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD)
|
||||||
|
|
||||||
|
# Build
|
||||||
|
|
||||||
|
.PHONY: generate build
|
||||||
|
|
||||||
|
generate: $(OTEL_GO_MOD_DIRS:%=generate/%)
|
||||||
|
generate/%: DIR=$*
|
||||||
|
generate/%: | $(STRINGER) $(PORTO)
|
||||||
|
@echo "$(GO) generate $(DIR)/..." \
|
||||||
|
&& cd $(DIR) \
|
||||||
|
&& PATH="$(TOOLS):$${PATH}" $(GO) generate ./... && $(PORTO) -w .
|
||||||
|
|
||||||
|
build: generate $(OTEL_GO_MOD_DIRS:%=build/%) $(OTEL_GO_MOD_DIRS:%=build-tests/%)
|
||||||
|
build/%: DIR=$*
|
||||||
|
build/%:
|
||||||
|
@echo "$(GO) build $(DIR)/..." \
|
||||||
|
&& cd $(DIR) \
|
||||||
|
&& $(GO) build ./...
|
||||||
|
|
||||||
|
build-tests/%: DIR=$*
|
||||||
|
build-tests/%:
|
||||||
|
@echo "$(GO) build tests $(DIR)/..." \
|
||||||
|
&& cd $(DIR) \
|
||||||
|
&& $(GO) list ./... \
|
||||||
|
| grep -v third_party \
|
||||||
|
| xargs $(GO) test -vet=off -run xxxxxMatchNothingxxxxx >/dev/null
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
|
||||||
|
TEST_TARGETS := test-default test-bench test-short test-verbose test-race
|
||||||
|
.PHONY: $(TEST_TARGETS) test
|
||||||
|
test-default test-race: ARGS=-race
|
||||||
|
test-bench: ARGS=-run=xxxxxMatchNothingxxxxx -test.benchtime=1ms -bench=.
|
||||||
|
test-short: ARGS=-short
|
||||||
|
test-verbose: ARGS=-v -race
|
||||||
|
$(TEST_TARGETS): test
|
||||||
|
test: $(OTEL_GO_MOD_DIRS:%=test/%)
|
||||||
|
test/%: DIR=$*
|
||||||
|
test/%:
|
||||||
|
@echo "$(GO) test -timeout $(TIMEOUT)s $(ARGS) $(DIR)/..." \
|
||||||
|
&& cd $(DIR) \
|
||||||
|
&& $(GO) list ./... \
|
||||||
|
| grep -v third_party \
|
||||||
|
| xargs $(GO) test -timeout $(TIMEOUT)s $(ARGS)
|
||||||
|
|
||||||
|
COVERAGE_MODE = atomic
|
||||||
|
COVERAGE_PROFILE = coverage.out
|
||||||
|
.PHONY: test-coverage
|
||||||
|
test-coverage: | $(GOCOVMERGE)
|
||||||
|
@set -e; \
|
||||||
|
printf "" > coverage.txt; \
|
||||||
|
for dir in $(ALL_COVERAGE_MOD_DIRS); do \
|
||||||
|
echo "$(GO) test -coverpkg=go.opentelemetry.io/otel/... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" $${dir}/..."; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
$(GO) list ./... \
|
||||||
|
| grep -v third_party \
|
||||||
|
| xargs $(GO) test -coverpkg=./... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" && \
|
||||||
|
$(GO) tool cover -html=coverage.out -o coverage.html); \
|
||||||
|
done; \
|
||||||
|
$(GOCOVMERGE) $$(find . -name coverage.out) > coverage.txt
|
||||||
|
|
||||||
|
.PHONY: golangci-lint golangci-lint-fix
|
||||||
|
golangci-lint-fix: ARGS=--fix
|
||||||
|
golangci-lint-fix: golangci-lint
|
||||||
|
golangci-lint: $(OTEL_GO_MOD_DIRS:%=golangci-lint/%)
|
||||||
|
golangci-lint/%: DIR=$*
|
||||||
|
golangci-lint/%: | $(GOLANGCI_LINT)
|
||||||
|
@echo 'golangci-lint $(if $(ARGS),$(ARGS) ,)$(DIR)' \
|
||||||
|
&& cd $(DIR) \
|
||||||
|
&& $(GOLANGCI_LINT) run --allow-serial-runners $(ARGS)
|
||||||
|
|
||||||
|
.PHONY: crosslink
|
||||||
|
crosslink: | $(CROSSLINK)
|
||||||
|
@echo "cross-linking all go modules" \
|
||||||
|
&& $(CROSSLINK)
|
||||||
|
|
||||||
|
.PHONY: go-mod-tidy
|
||||||
|
go-mod-tidy: $(ALL_GO_MOD_DIRS:%=go-mod-tidy/%)
|
||||||
|
go-mod-tidy/%: DIR=$*
|
||||||
|
go-mod-tidy/%: | crosslink
|
||||||
|
@echo "$(GO) mod tidy in $(DIR)" \
|
||||||
|
&& cd $(DIR) \
|
||||||
|
&& $(GO) mod tidy
|
||||||
|
|
||||||
|
.PHONY: lint-modules
|
||||||
|
lint-modules: go-mod-tidy
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: misspell lint-modules golangci-lint
|
||||||
|
|
||||||
|
.PHONY: vanity-import-check
|
||||||
|
vanity-import-check: | $(PORTO)
|
||||||
|
@$(PORTO) --include-internal -l .
|
||||||
|
|
||||||
|
.PHONY: misspell
|
||||||
|
misspell: | $(MISSPELL)
|
||||||
|
@$(MISSPELL) -w $(ALL_DOCS)
|
||||||
|
|
||||||
|
.PHONY: license-check
|
||||||
|
license-check:
|
||||||
|
@licRes=$$(for f in $$(find . -type f \( -iname '*.go' -o -iname '*.sh' \) ! -path '**/third_party/*') ; do \
|
||||||
|
awk '/Copyright The OpenTelemetry Authors|generated|GENERATED/ && NR<=3 { found=1; next } END { if (!found) print FILENAME }' $$f; \
|
||||||
|
done); \
|
||||||
|
if [ -n "$${licRes}" ]; then \
|
||||||
|
echo "license header checking failed:"; echo "$${licRes}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEPENDABOT_PATH=./.github/dependabot.yml
|
||||||
|
.PHONY: dependabot-check
|
||||||
|
dependabot-check:
|
||||||
|
@result=$$( \
|
||||||
|
for f in $$( find . -type f -name go.mod -exec dirname {} \; | sed 's/^.//' ); \
|
||||||
|
do grep -q "directory: \+$$f" $(DEPENDABOT_PATH) \
|
||||||
|
|| echo "$$f"; \
|
||||||
|
done; \
|
||||||
|
); \
|
||||||
|
if [ -n "$$result" ]; then \
|
||||||
|
echo "missing dependabot entry:"; echo "$$result"; \
|
||||||
|
echo "new modules need to be added to the $(DEPENDABOT_PATH) file"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: check-clean-work-tree
|
||||||
|
check-clean-work-tree:
|
||||||
|
@if ! git diff --quiet; then \
|
||||||
|
echo; \
|
||||||
|
echo 'Working tree is not clean, did you forget to run "make precommit"?'; \
|
||||||
|
echo; \
|
||||||
|
git status; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: prerelease
|
||||||
|
prerelease: | $(MULTIMOD)
|
||||||
|
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
|
||||||
|
$(MULTIMOD) verify && $(MULTIMOD) prerelease -m ${MODSET}
|
||||||
|
|
||||||
|
COMMIT ?= "HEAD"
|
||||||
|
.PHONY: add-tags
|
||||||
|
add-tags: | $(MULTIMOD)
|
||||||
|
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
|
||||||
|
$(MULTIMOD) verify && $(MULTIMOD) tag -m ${MODSET} -c ${COMMIT}
|
|
@ -0,0 +1,102 @@
|
||||||
|
# OpenTelemetry-Go
|
||||||
|
|
||||||
|
[![CI](https://github.com/open-telemetry/opentelemetry-go/workflows/ci/badge.svg)](https://github.com/open-telemetry/opentelemetry-go/actions?query=workflow%3Aci+branch%3Amain)
|
||||||
|
[![codecov.io](https://codecov.io/gh/open-telemetry/opentelemetry-go/coverage.svg?branch=main)](https://app.codecov.io/gh/open-telemetry/opentelemetry-go?branch=main)
|
||||||
|
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel)](https://pkg.go.dev/go.opentelemetry.io/otel)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/go.opentelemetry.io/otel)](https://goreportcard.com/report/go.opentelemetry.io/otel)
|
||||||
|
[![Slack](https://img.shields.io/badge/slack-@cncf/otel--go-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C01NPAXACKT)
|
||||||
|
|
||||||
|
OpenTelemetry-Go is the [Go](https://golang.org/) implementation of [OpenTelemetry](https://opentelemetry.io/).
|
||||||
|
It provides a set of APIs to directly measure performance and behavior of your software and send this data to observability platforms.
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
| Signal | Status | Project |
|
||||||
|
| ------- | ---------- | ------- |
|
||||||
|
| Traces | Stable | N/A |
|
||||||
|
| Metrics | Alpha | N/A |
|
||||||
|
| Logs | Frozen [1] | N/A |
|
||||||
|
|
||||||
|
- [1]: The Logs signal development is halted for this project while we develop both Traces and Metrics.
|
||||||
|
No Logs Pull Requests are currently being accepted.
|
||||||
|
|
||||||
|
Progress and status specific to this repository is tracked in our local
|
||||||
|
[project boards](https://github.com/open-telemetry/opentelemetry-go/projects)
|
||||||
|
and
|
||||||
|
[milestones](https://github.com/open-telemetry/opentelemetry-go/milestones).
|
||||||
|
|
||||||
|
Project versioning information and stability guarantees can be found in the
|
||||||
|
[versioning documentation](./VERSIONING.md).
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
|
||||||
|
OpenTelemetry-Go attempts to track the current supported versions of the
|
||||||
|
[Go language](https://golang.org/doc/devel/release#policy). The release
|
||||||
|
schedule after a new minor version of go is as follows:
|
||||||
|
|
||||||
|
- The first release or one month, which ever is sooner, will add build steps for the new go version.
|
||||||
|
- The first release after three months will remove support for the oldest go version.
|
||||||
|
|
||||||
|
This project is tested on the following systems.
|
||||||
|
|
||||||
|
| OS | Go Version | Architecture |
|
||||||
|
| ------- | ---------- | ------------ |
|
||||||
|
| Ubuntu | 1.17 | amd64 |
|
||||||
|
| Ubuntu | 1.16 | amd64 |
|
||||||
|
| Ubuntu | 1.17 | 386 |
|
||||||
|
| Ubuntu | 1.16 | 386 |
|
||||||
|
| MacOS | 1.17 | amd64 |
|
||||||
|
| MacOS | 1.16 | amd64 |
|
||||||
|
| Windows | 1.17 | amd64 |
|
||||||
|
| Windows | 1.16 | amd64 |
|
||||||
|
| Windows | 1.17 | 386 |
|
||||||
|
| Windows | 1.16 | 386 |
|
||||||
|
|
||||||
|
While this project should work for other systems, no compatibility guarantees
|
||||||
|
are made for those systems currently.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
You can find a getting started guide on [opentelemetry.io](https://opentelemetry.io/docs/go/getting-started/).
|
||||||
|
|
||||||
|
OpenTelemetry's goal is to provide a single set of APIs to capture distributed
|
||||||
|
traces and metrics from your application and send them to an observability
|
||||||
|
platform. This project allows you to do just that for applications written in
|
||||||
|
Go. There are two steps to this process: instrument your application, and
|
||||||
|
configure an exporter.
|
||||||
|
|
||||||
|
### Instrumentation
|
||||||
|
|
||||||
|
To start capturing distributed traces and metric events from your application
|
||||||
|
it first needs to be instrumented. The easiest way to do this is by using an
|
||||||
|
instrumentation library for your code. Be sure to check out [the officially
|
||||||
|
supported instrumentation
|
||||||
|
libraries](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation).
|
||||||
|
|
||||||
|
If you need to extend the telemetry an instrumentation library provides or want
|
||||||
|
to build your own instrumentation for your application directly you will need
|
||||||
|
to use the
|
||||||
|
[go.opentelemetry.io/otel/api](https://pkg.go.dev/go.opentelemetry.io/otel/api)
|
||||||
|
package. The included [examples](./example/) are a good way to see some
|
||||||
|
practical uses of this process.
|
||||||
|
|
||||||
|
### Export
|
||||||
|
|
||||||
|
Now that your application is instrumented to collect telemetry, it needs an
|
||||||
|
export pipeline to send that telemetry to an observability platform.
|
||||||
|
|
||||||
|
All officially supported exporters for the OpenTelemetry project are contained in the [exporters directory](./exporters).
|
||||||
|
|
||||||
|
| Exporter | Metrics | Traces |
|
||||||
|
| :-----------------------------------: | :-----: | :----: |
|
||||||
|
| [Jaeger](./exporters/jaeger/) | | ✓ |
|
||||||
|
| [OTLP](./exporters/otlp/) | ✓ | ✓ |
|
||||||
|
| [Prometheus](./exporters/prometheus/) | ✓ | |
|
||||||
|
| [stdout](./exporters/stdout/) | ✓ | ✓ |
|
||||||
|
| [Zipkin](./exporters/zipkin/) | | ✓ |
|
||||||
|
|
||||||
|
Additionally, OpenTelemetry community supported exporters can be found in the [contrib repository](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/exporters).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See the [contributing documentation](CONTRIBUTING.md).
|
|
@ -0,0 +1,132 @@
|
||||||
|
# Release Process
|
||||||
|
|
||||||
|
## Semantic Convention Generation
|
||||||
|
|
||||||
|
If a new version of the OpenTelemetry Specification has been released it will be necessary to generate a new
|
||||||
|
semantic convention package from the YAML definitions in the specification repository. There is a `semconvgen` utility
|
||||||
|
installed by `make tools` that can be used to generate the a package with the name matching the specification
|
||||||
|
version number under the `semconv` package. This will ideally be done soon after the specification release is
|
||||||
|
tagged. Make sure that the specification repo contains a checkout of the the latest tagged release so that the
|
||||||
|
generated files match the released semantic conventions.
|
||||||
|
|
||||||
|
There are currently two categories of semantic conventions that must be generated, `resource` and `trace`.
|
||||||
|
|
||||||
|
```
|
||||||
|
.tools/semconvgen -i /path/to/specification/repo/semantic_conventions/resource -t semconv/template.j2
|
||||||
|
.tools/semconvgen -i /path/to/specification/repo/semantic_conventions/trace -t semconv/template.j2
|
||||||
|
```
|
||||||
|
|
||||||
|
Using default values for all options other than `input` will result in using the `template.j2` template to
|
||||||
|
generate `resource.go` and `trace.go` in `/path/to/otelgo/repo/semconv/<version>`.
|
||||||
|
|
||||||
|
There are several ancillary files that are not generated and should be copied into the new package from the
|
||||||
|
prior package, with updates made as appropriate to canonical import path statements and constant values.
|
||||||
|
These files include:
|
||||||
|
|
||||||
|
* doc.go
|
||||||
|
* exception.go
|
||||||
|
* http(_test)?.go
|
||||||
|
* schema.go
|
||||||
|
|
||||||
|
Uses of the previous schema version in this repository should be updated to use the newly generated version.
|
||||||
|
No tooling for this exists at present, so use find/replace in your editor of choice or craft a `grep | sed`
|
||||||
|
pipeline if you like living on the edge.
|
||||||
|
|
||||||
|
## Pre-Release
|
||||||
|
|
||||||
|
First, decide which module sets will be released and update their versions
|
||||||
|
in `versions.yaml`. Commit this change to a new branch.
|
||||||
|
|
||||||
|
Update go.mod for submodules to depend on the new release which will happen in the next step.
|
||||||
|
|
||||||
|
1. Run the `prerelease` make target. It creates a branch
|
||||||
|
`prerelease_<module set>_<new tag>` that will contain all release changes.
|
||||||
|
|
||||||
|
```
|
||||||
|
make prerelease MODSET=<module set>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verify the changes.
|
||||||
|
|
||||||
|
```
|
||||||
|
git diff ...prerelease_<module set>_<new tag>
|
||||||
|
```
|
||||||
|
|
||||||
|
This should have changed the version for all modules to be `<new tag>`.
|
||||||
|
If these changes look correct, merge them into your pre-release branch:
|
||||||
|
|
||||||
|
```go
|
||||||
|
git merge prerelease_<module set>_<new tag>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Update the [Changelog](./CHANGELOG.md).
|
||||||
|
- Make sure all relevant changes for this release are included and are in language that non-contributors to the project can understand.
|
||||||
|
To verify this, you can look directly at the commits since the `<last tag>`.
|
||||||
|
|
||||||
|
```
|
||||||
|
git --no-pager log --pretty=oneline "<last tag>..HEAD"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Move all the `Unreleased` changes into a new section following the title scheme (`[<new tag>] - <date of release>`).
|
||||||
|
- Update all the appropriate links at the bottom.
|
||||||
|
|
||||||
|
4. Push the changes to upstream and create a Pull Request on GitHub.
|
||||||
|
Be sure to include the curated changes from the [Changelog](./CHANGELOG.md) in the description.
|
||||||
|
|
||||||
|
## Tag
|
||||||
|
|
||||||
|
Once the Pull Request with all the version changes has been approved and merged it is time to tag the merged commit.
|
||||||
|
|
||||||
|
***IMPORTANT***: It is critical you use the same tag that you used in the Pre-Release step!
|
||||||
|
Failure to do so will leave things in a broken state. As long as you do not
|
||||||
|
change `versions.yaml` between pre-release and this step, things should be fine.
|
||||||
|
|
||||||
|
***IMPORTANT***: [There is currently no way to remove an incorrectly tagged version of a Go module](https://github.com/golang/go/issues/34189).
|
||||||
|
It is critical you make sure the version you push upstream is correct.
|
||||||
|
[Failure to do so will lead to minor emergencies and tough to work around](https://github.com/open-telemetry/opentelemetry-go/issues/331).
|
||||||
|
|
||||||
|
1. For each module set that will be released, run the `add-tags` make target
|
||||||
|
using the `<commit-hash>` of the commit on the main branch for the merged Pull Request.
|
||||||
|
|
||||||
|
```
|
||||||
|
make add-tags MODSET=<module set> COMMIT=<commit hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
It should only be necessary to provide an explicit `COMMIT` value if the
|
||||||
|
current `HEAD` of your working directory is not the correct commit.
|
||||||
|
|
||||||
|
2. Push tags to the upstream remote (not your fork: `github.com/open-telemetry/opentelemetry-go.git`).
|
||||||
|
Make sure you push all sub-modules as well.
|
||||||
|
|
||||||
|
```
|
||||||
|
git push upstream <new tag>
|
||||||
|
git push upstream <submodules-path/new tag>
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
Finally create a Release for the new `<new tag>` on GitHub.
|
||||||
|
The release body should include all the release notes from the Changelog for this release.
|
||||||
|
|
||||||
|
## Verify Examples
|
||||||
|
|
||||||
|
After releasing verify that examples build outside of the repository.
|
||||||
|
|
||||||
|
```
|
||||||
|
./verify_examples.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script copies examples into a different directory removes any `replace` declarations in `go.mod` and builds them.
|
||||||
|
This ensures they build with the published release, not the local copy.
|
||||||
|
|
||||||
|
## Post-Release
|
||||||
|
|
||||||
|
### Contrib Repository
|
||||||
|
|
||||||
|
Once verified be sure to [make a release for the `contrib` repository](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/RELEASING.md) that uses this release.
|
||||||
|
|
||||||
|
### Website Documentation
|
||||||
|
|
||||||
|
Update [the documentation](./website_docs) for [the OpenTelemetry website](https://opentelemetry.io/docs/go/).
|
||||||
|
Importantly, bump any package versions referenced to be the latest one you just released and ensure all code examples still compile and are accurate.
|
|
@ -0,0 +1,224 @@
|
||||||
|
# Versioning
|
||||||
|
|
||||||
|
This document describes the versioning policy for this repository. This policy
|
||||||
|
is designed so the following goals can be achieved.
|
||||||
|
|
||||||
|
**Users are provided a codebase of value that is stable and secure.**
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
* Versioning of this project will be idiomatic of a Go project using [Go
|
||||||
|
modules](https://github.com/golang/go/wiki/Modules).
|
||||||
|
* [Semantic import
|
||||||
|
versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning)
|
||||||
|
will be used.
|
||||||
|
* Versions will comply with [semver
|
||||||
|
2.0](https://semver.org/spec/v2.0.0.html) with the following exceptions.
|
||||||
|
* New methods may be added to exported API interfaces. All exported
|
||||||
|
interfaces that fall within this exception will include the following
|
||||||
|
paragraph in their public documentation.
|
||||||
|
|
||||||
|
> Warning: methods may be added to this interface in minor releases.
|
||||||
|
|
||||||
|
* If a module is version `v2` or higher, the major version of the module
|
||||||
|
must be included as a `/vN` at the end of the module paths used in
|
||||||
|
`go.mod` files (e.g., `module go.opentelemetry.io/otel/v2`, `require
|
||||||
|
go.opentelemetry.io/otel/v2 v2.0.1`) and in the package import path
|
||||||
|
(e.g., `import "go.opentelemetry.io/otel/v2/trace"`). This includes the
|
||||||
|
paths used in `go get` commands (e.g., `go get
|
||||||
|
go.opentelemetry.io/otel/v2@v2.0.1`. Note there is both a `/v2` and a
|
||||||
|
`@v2.0.1` in that example. One way to think about it is that the module
|
||||||
|
name now includes the `/v2`, so include `/v2` whenever you are using the
|
||||||
|
module name).
|
||||||
|
* If a module is version `v0` or `v1`, do not include the major version in
|
||||||
|
either the module path or the import path.
|
||||||
|
* Modules will be used to encapsulate signals and components.
|
||||||
|
* Experimental modules still under active development will be versioned at
|
||||||
|
`v0` to imply the stability guarantee defined by
|
||||||
|
[semver](https://semver.org/spec/v2.0.0.html#spec-item-4).
|
||||||
|
|
||||||
|
> Major version zero (0.y.z) is for initial development. Anything MAY
|
||||||
|
> change at any time. The public API SHOULD NOT be considered stable.
|
||||||
|
|
||||||
|
* Mature modules for which we guarantee a stable public API will be versioned
|
||||||
|
with a major version greater than `v0`.
|
||||||
|
* The decision to make a module stable will be made on a case-by-case
|
||||||
|
basis by the maintainers of this project.
|
||||||
|
* Experimental modules will start their versioning at `v0.0.0` and will
|
||||||
|
increment their minor version when backwards incompatible changes are
|
||||||
|
released and increment their patch version when backwards compatible
|
||||||
|
changes are released.
|
||||||
|
* All stable modules that use the same major version number will use the
|
||||||
|
same entire version number.
|
||||||
|
* Stable modules may be released with an incremented minor or patch
|
||||||
|
version even though that module has not been changed, but rather so
|
||||||
|
that it will remain at the same version as other stable modules that
|
||||||
|
did undergo change.
|
||||||
|
* When an experimental module becomes stable a new stable module version
|
||||||
|
will be released and will include this now stable module. The new
|
||||||
|
stable module version will be an increment of the minor version number
|
||||||
|
and will be applied to all existing stable modules as well as the newly
|
||||||
|
stable module being released.
|
||||||
|
* Versioning of the associated [contrib
|
||||||
|
repository](https://github.com/open-telemetry/opentelemetry-go-contrib) of
|
||||||
|
this project will be idiomatic of a Go project using [Go
|
||||||
|
modules](https://github.com/golang/go/wiki/Modules).
|
||||||
|
* [Semantic import
|
||||||
|
versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning)
|
||||||
|
will be used.
|
||||||
|
* Versions will comply with [semver 2.0](https://semver.org/spec/v2.0.0.html).
|
||||||
|
* If a module is version `v2` or higher, the
|
||||||
|
major version of the module must be included as a `/vN` at the end of the
|
||||||
|
module paths used in `go.mod` files (e.g., `module
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/host/v2`, `require
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/host/v2 v2.0.1`) and in the
|
||||||
|
package import path (e.g., `import
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/host/v2"`). This includes
|
||||||
|
the paths used in `go get` commands (e.g., `go get
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/host/v2@v2.0.1`. Note there
|
||||||
|
is both a `/v2` and a `@v2.0.1` in that example. One way to think about
|
||||||
|
it is that the module name now includes the `/v2`, so include `/v2`
|
||||||
|
whenever you are using the module name).
|
||||||
|
* If a module is version `v0` or `v1`, do not include the major version
|
||||||
|
in either the module path or the import path.
|
||||||
|
* In addition to public APIs, telemetry produced by stable instrumentation
|
||||||
|
will remain stable and backwards compatible. This is to avoid breaking
|
||||||
|
alerts and dashboard.
|
||||||
|
* Modules will be used to encapsulate instrumentation, detectors, exporters,
|
||||||
|
propagators, and any other independent sets of related components.
|
||||||
|
* Experimental modules still under active development will be versioned at
|
||||||
|
`v0` to imply the stability guarantee defined by
|
||||||
|
[semver](https://semver.org/spec/v2.0.0.html#spec-item-4).
|
||||||
|
|
||||||
|
> Major version zero (0.y.z) is for initial development. Anything MAY
|
||||||
|
> change at any time. The public API SHOULD NOT be considered stable.
|
||||||
|
|
||||||
|
* Mature modules for which we guarantee a stable public API and telemetry will
|
||||||
|
be versioned with a major version greater than `v0`.
|
||||||
|
* Experimental modules will start their versioning at `v0.0.0` and will
|
||||||
|
increment their minor version when backwards incompatible changes are
|
||||||
|
released and increment their patch version when backwards compatible
|
||||||
|
changes are released.
|
||||||
|
* Stable contrib modules cannot depend on experimental modules from this
|
||||||
|
project.
|
||||||
|
* All stable contrib modules of the same major version with this project
|
||||||
|
will use the same entire version as this project.
|
||||||
|
* Stable modules may be released with an incremented minor or patch
|
||||||
|
version even though that module's code has not been changed. Instead
|
||||||
|
the only change that will have been included is to have updated that
|
||||||
|
modules dependency on this project's stable APIs.
|
||||||
|
* When an experimental module in contrib becomes stable a new stable
|
||||||
|
module version will be released and will include this now stable
|
||||||
|
module. The new stable module version will be an increment of the minor
|
||||||
|
version number and will be applied to all existing stable contrib
|
||||||
|
modules, this project's modules, and the newly stable module being
|
||||||
|
released.
|
||||||
|
* Contrib modules will be kept up to date with this project's releases.
|
||||||
|
* Due to the dependency contrib modules will implicitly have on this
|
||||||
|
project's modules the release of stable contrib modules to match the
|
||||||
|
released version number will be staggered after this project's release.
|
||||||
|
There is no explicit time guarantee for how long after this projects
|
||||||
|
release the contrib release will be. Effort should be made to keep them
|
||||||
|
as close in time as possible.
|
||||||
|
* No additional stable release in this project can be made until the
|
||||||
|
contrib repository has a matching stable release.
|
||||||
|
* No release can be made in the contrib repository after this project's
|
||||||
|
stable release except for a stable release of the contrib repository.
|
||||||
|
* GitHub releases will be made for all releases.
|
||||||
|
* Go modules will be made available at Go package mirrors.
|
||||||
|
|
||||||
|
## Example Versioning Lifecycle
|
||||||
|
|
||||||
|
To better understand the implementation of the above policy the following
|
||||||
|
example is provided. This project is simplified to include only the following
|
||||||
|
modules and their versions:
|
||||||
|
|
||||||
|
* `otel`: `v0.14.0`
|
||||||
|
* `otel/trace`: `v0.14.0`
|
||||||
|
* `otel/metric`: `v0.14.0`
|
||||||
|
* `otel/baggage`: `v0.14.0`
|
||||||
|
* `otel/sdk/trace`: `v0.14.0`
|
||||||
|
* `otel/sdk/metric`: `v0.14.0`
|
||||||
|
|
||||||
|
These modules have been developed to a point where the `otel/trace`,
|
||||||
|
`otel/baggage`, and `otel/sdk/trace` modules have reached a point that they
|
||||||
|
should be considered for a stable release. The `otel/metric` and
|
||||||
|
`otel/sdk/metric` are still under active development and the `otel` module
|
||||||
|
depends on both `otel/trace` and `otel/metric`.
|
||||||
|
|
||||||
|
The `otel` package is refactored to remove its dependencies on `otel/metric` so
|
||||||
|
it can be released as stable as well. With that done the following release
|
||||||
|
candidates are made:
|
||||||
|
|
||||||
|
* `otel`: `v1.0.0-RC1`
|
||||||
|
* `otel/trace`: `v1.0.0-RC1`
|
||||||
|
* `otel/baggage`: `v1.0.0-RC1`
|
||||||
|
* `otel/sdk/trace`: `v1.0.0-RC1`
|
||||||
|
|
||||||
|
The `otel/metric` and `otel/sdk/metric` modules remain at `v0.14.0`.
|
||||||
|
|
||||||
|
A few minor issues are discovered in the `otel/trace` package. These issues are
|
||||||
|
resolved with some minor, but backwards incompatible, changes and are released
|
||||||
|
as a second release candidate:
|
||||||
|
|
||||||
|
* `otel`: `v1.0.0-RC2`
|
||||||
|
* `otel/trace`: `v1.0.0-RC2`
|
||||||
|
* `otel/baggage`: `v1.0.0-RC2`
|
||||||
|
* `otel/sdk/trace`: `v1.0.0-RC2`
|
||||||
|
|
||||||
|
Notice that all module version numbers are incremented to adhere to our
|
||||||
|
versioning policy.
|
||||||
|
|
||||||
|
After these release candidates have been evaluated to satisfaction, they are
|
||||||
|
released as version `v1.0.0`.
|
||||||
|
|
||||||
|
* `otel`: `v1.0.0`
|
||||||
|
* `otel/trace`: `v1.0.0`
|
||||||
|
* `otel/baggage`: `v1.0.0`
|
||||||
|
* `otel/sdk/trace`: `v1.0.0`
|
||||||
|
|
||||||
|
Since both the `go` utility and the Go module system support [the semantic
|
||||||
|
versioning definition of
|
||||||
|
precedence](https://semver.org/spec/v2.0.0.html#spec-item-11), this release
|
||||||
|
will correctly be interpreted as the successor to the previous release
|
||||||
|
candidates.
|
||||||
|
|
||||||
|
Active development of this project continues. The `otel/metric` module now has
|
||||||
|
backwards incompatible changes to its API that need to be released and the
|
||||||
|
`otel/baggage` module has a minor bug fix that needs to be released. The
|
||||||
|
following release is made:
|
||||||
|
|
||||||
|
* `otel`: `v1.0.1`
|
||||||
|
* `otel/trace`: `v1.0.1`
|
||||||
|
* `otel/metric`: `v0.15.0`
|
||||||
|
* `otel/baggage`: `v1.0.1`
|
||||||
|
* `otel/sdk/trace`: `v1.0.1`
|
||||||
|
* `otel/sdk/metric`: `v0.15.0`
|
||||||
|
|
||||||
|
Notice that, again, all stable module versions are incremented in unison and
|
||||||
|
the `otel/sdk/metric` package, which depends on the `otel/metric` package, also
|
||||||
|
bumped its version. This bump of the `otel/sdk/metric` package makes sense
|
||||||
|
given their coupling, though it is not explicitly required by our versioning
|
||||||
|
policy.
|
||||||
|
|
||||||
|
As we progress, the `otel/metric` and `otel/sdk/metric` packages have reached a
|
||||||
|
point where they should be evaluated for stability. The `otel` module is
|
||||||
|
reintegrated with the `otel/metric` package and the following release is made:
|
||||||
|
|
||||||
|
* `otel`: `v1.1.0-RC1`
|
||||||
|
* `otel/trace`: `v1.1.0-RC1`
|
||||||
|
* `otel/metric`: `v1.1.0-RC1`
|
||||||
|
* `otel/baggage`: `v1.1.0-RC1`
|
||||||
|
* `otel/sdk/trace`: `v1.1.0-RC1`
|
||||||
|
* `otel/sdk/metric`: `v1.1.0-RC1`
|
||||||
|
|
||||||
|
All the modules are evaluated and determined to a viable stable release. They
|
||||||
|
are then released as version `v1.1.0` (the minor version is incremented to
|
||||||
|
indicate the addition of new signal).
|
||||||
|
|
||||||
|
* `otel`: `v1.1.0`
|
||||||
|
* `otel/trace`: `v1.1.0`
|
||||||
|
* `otel/metric`: `v1.1.0`
|
||||||
|
* `otel/baggage`: `v1.1.0`
|
||||||
|
* `otel/sdk/trace`: `v1.1.0`
|
||||||
|
* `otel/sdk/metric`: `v1.1.0`
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package attribute provides key and value attributes.
|
||||||
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
|
@ -0,0 +1,150 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Encoder is a mechanism for serializing a label set into a
|
||||||
|
// specific string representation that supports caching, to
|
||||||
|
// avoid repeated serialization. An example could be an
|
||||||
|
// exporter encoding the label set into a wire representation.
|
||||||
|
Encoder interface {
|
||||||
|
// Encode returns the serialized encoding of the label
|
||||||
|
// set using its Iterator. This result may be cached
|
||||||
|
// by a attribute.Set.
|
||||||
|
Encode(iterator Iterator) string
|
||||||
|
|
||||||
|
// ID returns a value that is unique for each class of
|
||||||
|
// label encoder. Label encoders allocate these using
|
||||||
|
// `NewEncoderID`.
|
||||||
|
ID() EncoderID
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncoderID is used to identify distinct Encoder
|
||||||
|
// implementations, for caching encoded results.
|
||||||
|
EncoderID struct {
|
||||||
|
value uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultLabelEncoder uses a sync.Pool of buffers to reduce
|
||||||
|
// the number of allocations used in encoding labels. This
|
||||||
|
// implementation encodes a comma-separated list of key=value,
|
||||||
|
// with '/'-escaping of '=', ',', and '\'.
|
||||||
|
defaultLabelEncoder struct {
|
||||||
|
// pool is a pool of labelset builders. The buffers in this
|
||||||
|
// pool grow to a size that most label encodings will not
|
||||||
|
// allocate new memory.
|
||||||
|
pool sync.Pool // *bytes.Buffer
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// escapeChar is used to ensure uniqueness of the label encoding where
|
||||||
|
// keys or values contain either '=' or ','. Since there is no parser
|
||||||
|
// needed for this encoding and its only requirement is to be unique,
|
||||||
|
// this choice is arbitrary. Users will see these in some exporters
|
||||||
|
// (e.g., stdout), so the backslash ('\') is used as a conventional choice.
|
||||||
|
const escapeChar = '\\'
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Encoder = &defaultLabelEncoder{}
|
||||||
|
|
||||||
|
// encoderIDCounter is for generating IDs for other label
|
||||||
|
// encoders.
|
||||||
|
encoderIDCounter uint64
|
||||||
|
|
||||||
|
defaultEncoderOnce sync.Once
|
||||||
|
defaultEncoderID = NewEncoderID()
|
||||||
|
defaultEncoderInstance *defaultLabelEncoder
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEncoderID returns a unique label encoder ID. It should be
|
||||||
|
// called once per each type of label encoder. Preferably in init() or
|
||||||
|
// in var definition.
|
||||||
|
func NewEncoderID() EncoderID {
|
||||||
|
return EncoderID{value: atomic.AddUint64(&encoderIDCounter, 1)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultEncoder returns a label encoder that encodes labels
|
||||||
|
// in such a way that each escaped label's key is followed by an equal
|
||||||
|
// sign and then by an escaped label's value. All key-value pairs are
|
||||||
|
// separated by a comma.
|
||||||
|
//
|
||||||
|
// Escaping is done by prepending a backslash before either a
|
||||||
|
// backslash, equal sign or a comma.
|
||||||
|
func DefaultEncoder() Encoder {
|
||||||
|
defaultEncoderOnce.Do(func() {
|
||||||
|
defaultEncoderInstance = &defaultLabelEncoder{
|
||||||
|
pool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &bytes.Buffer{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return defaultEncoderInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode is a part of an implementation of the LabelEncoder
|
||||||
|
// interface.
|
||||||
|
func (d *defaultLabelEncoder) Encode(iter Iterator) string {
|
||||||
|
buf := d.pool.Get().(*bytes.Buffer)
|
||||||
|
defer d.pool.Put(buf)
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
for iter.Next() {
|
||||||
|
i, keyValue := iter.IndexedLabel()
|
||||||
|
if i > 0 {
|
||||||
|
_, _ = buf.WriteRune(',')
|
||||||
|
}
|
||||||
|
copyAndEscape(buf, string(keyValue.Key))
|
||||||
|
|
||||||
|
_, _ = buf.WriteRune('=')
|
||||||
|
|
||||||
|
if keyValue.Value.Type() == STRING {
|
||||||
|
copyAndEscape(buf, keyValue.Value.AsString())
|
||||||
|
} else {
|
||||||
|
_, _ = buf.WriteString(keyValue.Value.Emit())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID is a part of an implementation of the LabelEncoder interface.
|
||||||
|
func (*defaultLabelEncoder) ID() EncoderID {
|
||||||
|
return defaultEncoderID
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyAndEscape escapes `=`, `,` and its own escape character (`\`),
|
||||||
|
// making the default encoding unique.
|
||||||
|
func copyAndEscape(buf *bytes.Buffer, val string) {
|
||||||
|
for _, ch := range val {
|
||||||
|
switch ch {
|
||||||
|
case '=', ',', escapeChar:
|
||||||
|
buf.WriteRune(escapeChar)
|
||||||
|
}
|
||||||
|
buf.WriteRune(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if this encoder ID was allocated by
|
||||||
|
// `NewEncoderID`. Invalid encoder IDs will not be cached.
|
||||||
|
func (id EncoderID) Valid() bool {
|
||||||
|
return id.value != 0
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
// Iterator allows iterating over the set of labels in order,
|
||||||
|
// sorted by key.
|
||||||
|
type Iterator struct {
|
||||||
|
storage *Set
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeIterator supports iterating over two sets of labels while
|
||||||
|
// eliminating duplicate values from the combined set. The first
|
||||||
|
// iterator value takes precedence.
|
||||||
|
type MergeIterator struct {
|
||||||
|
one oneIterator
|
||||||
|
two oneIterator
|
||||||
|
current KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type oneIterator struct {
|
||||||
|
iter Iterator
|
||||||
|
done bool
|
||||||
|
label KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next moves the iterator to the next position. Returns false if there
|
||||||
|
// are no more labels.
|
||||||
|
func (i *Iterator) Next() bool {
|
||||||
|
i.idx++
|
||||||
|
return i.idx < i.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label returns current KeyValue. Must be called only after Next returns
|
||||||
|
// true.
|
||||||
|
func (i *Iterator) Label() KeyValue {
|
||||||
|
kv, _ := i.storage.Get(i.idx)
|
||||||
|
return kv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute is a synonym for Label().
|
||||||
|
func (i *Iterator) Attribute() KeyValue {
|
||||||
|
return i.Label()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexedLabel returns current index and attribute. Must be called only
|
||||||
|
// after Next returns true.
|
||||||
|
func (i *Iterator) IndexedLabel() (int, KeyValue) {
|
||||||
|
return i.idx, i.Label()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns a number of labels in the iterator's `*Set`.
|
||||||
|
func (i *Iterator) Len() int {
|
||||||
|
return i.storage.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSlice is a convenience function that creates a slice of labels
|
||||||
|
// from the passed iterator. The iterator is set up to start from the
|
||||||
|
// beginning before creating the slice.
|
||||||
|
func (i *Iterator) ToSlice() []KeyValue {
|
||||||
|
l := i.Len()
|
||||||
|
if l == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i.idx = -1
|
||||||
|
slice := make([]KeyValue, 0, l)
|
||||||
|
for i.Next() {
|
||||||
|
slice = append(slice, i.Label())
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMergeIterator returns a MergeIterator for merging two label sets
|
||||||
|
// Duplicates are resolved by taking the value from the first set.
|
||||||
|
func NewMergeIterator(s1, s2 *Set) MergeIterator {
|
||||||
|
mi := MergeIterator{
|
||||||
|
one: makeOne(s1.Iter()),
|
||||||
|
two: makeOne(s2.Iter()),
|
||||||
|
}
|
||||||
|
return mi
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeOne(iter Iterator) oneIterator {
|
||||||
|
oi := oneIterator{
|
||||||
|
iter: iter,
|
||||||
|
}
|
||||||
|
oi.advance()
|
||||||
|
return oi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oi *oneIterator) advance() {
|
||||||
|
if oi.done = !oi.iter.Next(); !oi.done {
|
||||||
|
oi.label = oi.iter.Label()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns true if there is another label available.
|
||||||
|
func (m *MergeIterator) Next() bool {
|
||||||
|
if m.one.done && m.two.done {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m.one.done {
|
||||||
|
m.current = m.two.label
|
||||||
|
m.two.advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if m.two.done {
|
||||||
|
m.current = m.one.label
|
||||||
|
m.one.advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if m.one.label.Key == m.two.label.Key {
|
||||||
|
m.current = m.one.label // first iterator label value wins
|
||||||
|
m.one.advance()
|
||||||
|
m.two.advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if m.one.label.Key < m.two.label.Key {
|
||||||
|
m.current = m.one.label
|
||||||
|
m.one.advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
m.current = m.two.label
|
||||||
|
m.two.advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label returns the current value after Next() returns true.
|
||||||
|
func (m *MergeIterator) Label() KeyValue {
|
||||||
|
return m.current
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
// Key represents the key part in key-value pairs. It's a string. The
|
||||||
|
// allowed character set in the key depends on the use of the key.
|
||||||
|
type Key string
|
||||||
|
|
||||||
|
// Bool creates a KeyValue instance with a BOOL Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- Bool(name, value).
|
||||||
|
func (k Key) Bool(v bool) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: BoolValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSlice creates a KeyValue instance with a BOOLSLICE Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- BoolSlice(name, value).
|
||||||
|
func (k Key) BoolSlice(v []bool) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: BoolSliceValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int creates a KeyValue instance with an INT64 Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- Int(name, value).
|
||||||
|
func (k Key) Int(v int) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: IntValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntSlice creates a KeyValue instance with an INT64SLICE Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- IntSlice(name, value).
|
||||||
|
func (k Key) IntSlice(v []int) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: IntSliceValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 creates a KeyValue instance with an INT64 Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- Int64(name, value).
|
||||||
|
func (k Key) Int64(v int64) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: Int64Value(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Slice creates a KeyValue instance with an INT64SLICE Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- Int64Slice(name, value).
|
||||||
|
func (k Key) Int64Slice(v []int64) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: Int64SliceValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 creates a KeyValue instance with a FLOAT64 Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- Float64(name, value).
|
||||||
|
func (k Key) Float64(v float64) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: Float64Value(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Slice creates a KeyValue instance with a FLOAT64SLICE Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- Float64(name, value).
|
||||||
|
func (k Key) Float64Slice(v []float64) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: Float64SliceValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String creates a KeyValue instance with a STRING Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- String(name, value).
|
||||||
|
func (k Key) String(v string) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: StringValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice creates a KeyValue instance with a STRINGSLICE Value.
|
||||||
|
//
|
||||||
|
// If creating both a key and value at the same time, use the provided
|
||||||
|
// convenience function instead -- StringSlice(name, value).
|
||||||
|
func (k Key) StringSlice(v []string) KeyValue {
|
||||||
|
return KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: StringSliceValue(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defined returns true for non-empty keys.
|
||||||
|
func (k Key) Defined() bool {
|
||||||
|
return len(k) != 0
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyValue holds a key and value pair.
|
||||||
|
type KeyValue struct {
|
||||||
|
Key Key
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns if kv is a valid OpenTelemetry attribute.
|
||||||
|
func (kv KeyValue) Valid() bool {
|
||||||
|
return kv.Key.Defined() && kv.Value.Type() != INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool creates a KeyValue with a BOOL Value type.
|
||||||
|
func Bool(k string, v bool) KeyValue {
|
||||||
|
return Key(k).Bool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSlice creates a KeyValue with a BOOLSLICE Value type.
|
||||||
|
func BoolSlice(k string, v []bool) KeyValue {
|
||||||
|
return Key(k).BoolSlice(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int creates a KeyValue with an INT64 Value type.
|
||||||
|
func Int(k string, v int) KeyValue {
|
||||||
|
return Key(k).Int(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntSlice creates a KeyValue with an INT64SLICE Value type.
|
||||||
|
func IntSlice(k string, v []int) KeyValue {
|
||||||
|
return Key(k).IntSlice(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 creates a KeyValue with an INT64 Value type.
|
||||||
|
func Int64(k string, v int64) KeyValue {
|
||||||
|
return Key(k).Int64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Slice creates a KeyValue with an INT64SLICE Value type.
|
||||||
|
func Int64Slice(k string, v []int64) KeyValue {
|
||||||
|
return Key(k).Int64Slice(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 creates a KeyValue with a FLOAT64 Value type.
|
||||||
|
func Float64(k string, v float64) KeyValue {
|
||||||
|
return Key(k).Float64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Slice creates a KeyValue with a FLOAT64SLICE Value type.
|
||||||
|
func Float64Slice(k string, v []float64) KeyValue {
|
||||||
|
return Key(k).Float64Slice(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String creates a KeyValue with a STRING Value type.
|
||||||
|
func String(k, v string) KeyValue {
|
||||||
|
return Key(k).String(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice creates a KeyValue with a STRINGSLICE Value type.
|
||||||
|
func StringSlice(k string, v []string) KeyValue {
|
||||||
|
return Key(k).StringSlice(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stringer creates a new key-value pair with a passed name and a string
|
||||||
|
// value generated by the passed Stringer interface.
|
||||||
|
func Stringer(k string, v fmt.Stringer) KeyValue {
|
||||||
|
return Key(k).String(v.String())
|
||||||
|
}
|
|
@ -0,0 +1,435 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Set is the representation for a distinct label set. It
|
||||||
|
// manages an immutable set of labels, with an internal cache
|
||||||
|
// for storing label encodings.
|
||||||
|
//
|
||||||
|
// This type supports the `Equivalent` method of comparison
|
||||||
|
// using values of type `Distinct`.
|
||||||
|
//
|
||||||
|
// This type is used to implement:
|
||||||
|
// 1. Metric labels
|
||||||
|
// 2. Resource sets
|
||||||
|
// 3. Correlation map (TODO)
|
||||||
|
Set struct {
|
||||||
|
equivalent Distinct
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distinct wraps a variable-size array of `KeyValue`,
|
||||||
|
// constructed with keys in sorted order. This can be used as
|
||||||
|
// a map key or for equality checking between Sets.
|
||||||
|
Distinct struct {
|
||||||
|
iface interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter supports removing certain labels from label sets.
|
||||||
|
// When the filter returns true, the label will be kept in
|
||||||
|
// the filtered label set. When the filter returns false, the
|
||||||
|
// label is excluded from the filtered label set, and the
|
||||||
|
// label instead appears in the `removed` list of excluded labels.
|
||||||
|
Filter func(KeyValue) bool
|
||||||
|
|
||||||
|
// Sortable implements `sort.Interface`, used for sorting
|
||||||
|
// `KeyValue`. This is an exported type to support a
|
||||||
|
// memory optimization. A pointer to one of these is needed
|
||||||
|
// for the call to `sort.Stable()`, which the caller may
|
||||||
|
// provide in order to avoid an allocation. See
|
||||||
|
// `NewSetWithSortable()`.
|
||||||
|
Sortable []KeyValue
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// keyValueType is used in `computeDistinctReflect`.
|
||||||
|
keyValueType = reflect.TypeOf(KeyValue{})
|
||||||
|
|
||||||
|
// emptySet is returned for empty label sets.
|
||||||
|
emptySet = &Set{
|
||||||
|
equivalent: Distinct{
|
||||||
|
iface: [0]KeyValue{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmptySet returns a reference to a Set with no elements.
|
||||||
|
//
|
||||||
|
// This is a convenience provided for optimized calling utility.
|
||||||
|
func EmptySet() *Set {
|
||||||
|
return emptySet
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflect abbreviates `reflect.ValueOf`.
|
||||||
|
func (d Distinct) reflect() reflect.Value {
|
||||||
|
return reflect.ValueOf(d.iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns true if this value refers to a valid `*Set`.
|
||||||
|
func (d Distinct) Valid() bool {
|
||||||
|
return d.iface != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of labels in this set.
|
||||||
|
func (l *Set) Len() int {
|
||||||
|
if l == nil || !l.equivalent.Valid() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return l.equivalent.reflect().Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the KeyValue at ordered position `idx` in this set.
|
||||||
|
func (l *Set) Get(idx int) (KeyValue, bool) {
|
||||||
|
if l == nil {
|
||||||
|
return KeyValue{}, false
|
||||||
|
}
|
||||||
|
value := l.equivalent.reflect()
|
||||||
|
|
||||||
|
if idx >= 0 && idx < value.Len() {
|
||||||
|
// Note: The Go compiler successfully avoids an allocation for
|
||||||
|
// the interface{} conversion here:
|
||||||
|
return value.Index(idx).Interface().(KeyValue), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyValue{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the value of a specified key in this set.
|
||||||
|
func (l *Set) Value(k Key) (Value, bool) {
|
||||||
|
if l == nil {
|
||||||
|
return Value{}, false
|
||||||
|
}
|
||||||
|
rValue := l.equivalent.reflect()
|
||||||
|
vlen := rValue.Len()
|
||||||
|
|
||||||
|
idx := sort.Search(vlen, func(idx int) bool {
|
||||||
|
return rValue.Index(idx).Interface().(KeyValue).Key >= k
|
||||||
|
})
|
||||||
|
if idx >= vlen {
|
||||||
|
return Value{}, false
|
||||||
|
}
|
||||||
|
keyValue := rValue.Index(idx).Interface().(KeyValue)
|
||||||
|
if k == keyValue.Key {
|
||||||
|
return keyValue.Value, true
|
||||||
|
}
|
||||||
|
return Value{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasValue tests whether a key is defined in this set.
|
||||||
|
func (l *Set) HasValue(k Key) bool {
|
||||||
|
if l == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, ok := l.Value(k)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iter returns an iterator for visiting the labels in this set.
|
||||||
|
func (l *Set) Iter() Iterator {
|
||||||
|
return Iterator{
|
||||||
|
storage: l,
|
||||||
|
idx: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSlice returns the set of labels belonging to this set, sorted,
|
||||||
|
// where keys appear no more than once.
|
||||||
|
func (l *Set) ToSlice() []KeyValue {
|
||||||
|
iter := l.Iter()
|
||||||
|
return iter.ToSlice()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equivalent returns a value that may be used as a map key. The
|
||||||
|
// Distinct type guarantees that the result will equal the equivalent
|
||||||
|
// Distinct value of any label set with the same elements as this,
|
||||||
|
// where sets are made unique by choosing the last value in the input
|
||||||
|
// for any given key.
|
||||||
|
func (l *Set) Equivalent() Distinct {
|
||||||
|
if l == nil || !l.equivalent.Valid() {
|
||||||
|
return emptySet.equivalent
|
||||||
|
}
|
||||||
|
return l.equivalent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if the argument set is equivalent to this set.
|
||||||
|
func (l *Set) Equals(o *Set) bool {
|
||||||
|
return l.Equivalent() == o.Equivalent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoded returns the encoded form of this set, according to
|
||||||
|
// `encoder`.
|
||||||
|
func (l *Set) Encoded(encoder Encoder) string {
|
||||||
|
if l == nil || encoder == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoder.Encode(l.Iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
func empty() Set {
|
||||||
|
return Set{
|
||||||
|
equivalent: emptySet.equivalent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSet returns a new `Set`. See the documentation for
|
||||||
|
// `NewSetWithSortableFiltered` for more details.
|
||||||
|
//
|
||||||
|
// Except for empty sets, this method adds an additional allocation
|
||||||
|
// compared with calls that include a `*Sortable`.
|
||||||
|
func NewSet(kvs ...KeyValue) Set {
|
||||||
|
// Check for empty set.
|
||||||
|
if len(kvs) == 0 {
|
||||||
|
return empty()
|
||||||
|
}
|
||||||
|
s, _ := NewSetWithSortableFiltered(kvs, new(Sortable), nil)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSetWithSortable returns a new `Set`. See the documentation for
|
||||||
|
// `NewSetWithSortableFiltered` for more details.
|
||||||
|
//
|
||||||
|
// This call includes a `*Sortable` option as a memory optimization.
|
||||||
|
func NewSetWithSortable(kvs []KeyValue, tmp *Sortable) Set {
|
||||||
|
// Check for empty set.
|
||||||
|
if len(kvs) == 0 {
|
||||||
|
return empty()
|
||||||
|
}
|
||||||
|
s, _ := NewSetWithSortableFiltered(kvs, tmp, nil)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSetWithFiltered returns a new `Set`. See the documentation for
|
||||||
|
// `NewSetWithSortableFiltered` for more details.
|
||||||
|
//
|
||||||
|
// This call includes a `Filter` to include/exclude label keys from
|
||||||
|
// the return value. Excluded keys are returned as a slice of label
|
||||||
|
// values.
|
||||||
|
func NewSetWithFiltered(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
|
||||||
|
// Check for empty set.
|
||||||
|
if len(kvs) == 0 {
|
||||||
|
return empty(), nil
|
||||||
|
}
|
||||||
|
return NewSetWithSortableFiltered(kvs, new(Sortable), filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSetWithSortableFiltered returns a new `Set`.
|
||||||
|
//
|
||||||
|
// Duplicate keys are eliminated by taking the last value. This
|
||||||
|
// re-orders the input slice so that unique last-values are contiguous
|
||||||
|
// at the end of the slice.
|
||||||
|
//
|
||||||
|
// This ensures the following:
|
||||||
|
//
|
||||||
|
// - Last-value-wins semantics
|
||||||
|
// - Caller sees the reordering, but doesn't lose values
|
||||||
|
// - Repeated call preserve last-value wins.
|
||||||
|
//
|
||||||
|
// Note that methods are defined on `*Set`, although this returns `Set`.
|
||||||
|
// Callers can avoid memory allocations by:
|
||||||
|
//
|
||||||
|
// - allocating a `Sortable` for use as a temporary in this method
|
||||||
|
// - allocating a `Set` for storing the return value of this
|
||||||
|
// constructor.
|
||||||
|
//
|
||||||
|
// The result maintains a cache of encoded labels, by attribute.EncoderID.
|
||||||
|
// This value should not be copied after its first use.
|
||||||
|
//
|
||||||
|
// The second `[]KeyValue` return value is a list of labels that were
|
||||||
|
// excluded by the Filter (if non-nil).
|
||||||
|
func NewSetWithSortableFiltered(kvs []KeyValue, tmp *Sortable, filter Filter) (Set, []KeyValue) {
|
||||||
|
// Check for empty set.
|
||||||
|
if len(kvs) == 0 {
|
||||||
|
return empty(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
*tmp = kvs
|
||||||
|
|
||||||
|
// Stable sort so the following de-duplication can implement
|
||||||
|
// last-value-wins semantics.
|
||||||
|
sort.Stable(tmp)
|
||||||
|
|
||||||
|
*tmp = nil
|
||||||
|
|
||||||
|
position := len(kvs) - 1
|
||||||
|
offset := position - 1
|
||||||
|
|
||||||
|
// The requirements stated above require that the stable
|
||||||
|
// result be placed in the end of the input slice, while
|
||||||
|
// overwritten values are swapped to the beginning.
|
||||||
|
//
|
||||||
|
// De-duplicate with last-value-wins semantics. Preserve
|
||||||
|
// duplicate values at the beginning of the input slice.
|
||||||
|
for ; offset >= 0; offset-- {
|
||||||
|
if kvs[offset].Key == kvs[position].Key {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
position--
|
||||||
|
kvs[offset], kvs[position] = kvs[position], kvs[offset]
|
||||||
|
}
|
||||||
|
if filter != nil {
|
||||||
|
return filterSet(kvs[position:], filter)
|
||||||
|
}
|
||||||
|
return Set{
|
||||||
|
equivalent: computeDistinct(kvs[position:]),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterSet reorders `kvs` so that included keys are contiguous at
|
||||||
|
// the end of the slice, while excluded keys precede the included keys.
|
||||||
|
func filterSet(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
|
||||||
|
var excluded []KeyValue
|
||||||
|
|
||||||
|
// Move labels that do not match the filter so
|
||||||
|
// they're adjacent before calling computeDistinct().
|
||||||
|
distinctPosition := len(kvs)
|
||||||
|
|
||||||
|
// Swap indistinct keys forward and distinct keys toward the
|
||||||
|
// end of the slice.
|
||||||
|
offset := len(kvs) - 1
|
||||||
|
for ; offset >= 0; offset-- {
|
||||||
|
if filter(kvs[offset]) {
|
||||||
|
distinctPosition--
|
||||||
|
kvs[offset], kvs[distinctPosition] = kvs[distinctPosition], kvs[offset]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
excluded = kvs[:distinctPosition]
|
||||||
|
|
||||||
|
return Set{
|
||||||
|
equivalent: computeDistinct(kvs[distinctPosition:]),
|
||||||
|
}, excluded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter returns a filtered copy of this `Set`. See the
|
||||||
|
// documentation for `NewSetWithSortableFiltered` for more details.
|
||||||
|
func (l *Set) Filter(re Filter) (Set, []KeyValue) {
|
||||||
|
if re == nil {
|
||||||
|
return Set{
|
||||||
|
equivalent: l.equivalent,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This could be refactored to avoid the temporary slice
|
||||||
|
// allocation, if it proves to be expensive.
|
||||||
|
return filterSet(l.ToSlice(), re)
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeDistinct returns a `Distinct` using either the fixed- or
|
||||||
|
// reflect-oriented code path, depending on the size of the input.
|
||||||
|
// The input slice is assumed to already be sorted and de-duplicated.
|
||||||
|
func computeDistinct(kvs []KeyValue) Distinct {
|
||||||
|
iface := computeDistinctFixed(kvs)
|
||||||
|
if iface == nil {
|
||||||
|
iface = computeDistinctReflect(kvs)
|
||||||
|
}
|
||||||
|
return Distinct{
|
||||||
|
iface: iface,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeDistinctFixed computes a `Distinct` for small slices. It
|
||||||
|
// returns nil if the input is too large for this code path.
|
||||||
|
func computeDistinctFixed(kvs []KeyValue) interface{} {
|
||||||
|
switch len(kvs) {
|
||||||
|
case 1:
|
||||||
|
ptr := new([1]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 2:
|
||||||
|
ptr := new([2]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 3:
|
||||||
|
ptr := new([3]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 4:
|
||||||
|
ptr := new([4]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 5:
|
||||||
|
ptr := new([5]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 6:
|
||||||
|
ptr := new([6]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 7:
|
||||||
|
ptr := new([7]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 8:
|
||||||
|
ptr := new([8]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 9:
|
||||||
|
ptr := new([9]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
case 10:
|
||||||
|
ptr := new([10]KeyValue)
|
||||||
|
copy((*ptr)[:], kvs)
|
||||||
|
return *ptr
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeDistinctReflect computes a `Distinct` using reflection,
|
||||||
|
// works for any size input.
|
||||||
|
func computeDistinctReflect(kvs []KeyValue) interface{} {
|
||||||
|
at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem()
|
||||||
|
for i, keyValue := range kvs {
|
||||||
|
*(at.Index(i).Addr().Interface().(*KeyValue)) = keyValue
|
||||||
|
}
|
||||||
|
return at.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of the `*Set`.
|
||||||
|
func (l *Set) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(l.equivalent.iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalLog is the marshaling function used by the logging system to represent this exporter.
|
||||||
|
func (l Set) MarshalLog() interface{} {
|
||||||
|
kvs := make(map[string]string)
|
||||||
|
for _, kv := range l.ToSlice() {
|
||||||
|
kvs[string(kv.Key)] = kv.Value.Emit()
|
||||||
|
}
|
||||||
|
return kvs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements `sort.Interface`.
|
||||||
|
func (l *Sortable) Len() int {
|
||||||
|
return len(*l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap implements `sort.Interface`.
|
||||||
|
func (l *Sortable) Swap(i, j int) {
|
||||||
|
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less implements `sort.Interface`.
|
||||||
|
func (l *Sortable) Less(i, j int) bool {
|
||||||
|
return (*l)[i].Key < (*l)[j].Key
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Code generated by "stringer -type=Type"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package attribute
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[INVALID-0]
|
||||||
|
_ = x[BOOL-1]
|
||||||
|
_ = x[INT64-2]
|
||||||
|
_ = x[FLOAT64-3]
|
||||||
|
_ = x[STRING-4]
|
||||||
|
_ = x[BOOLSLICE-5]
|
||||||
|
_ = x[INT64SLICE-6]
|
||||||
|
_ = x[FLOAT64SLICE-7]
|
||||||
|
_ = x[STRINGSLICE-8]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Type_name = "INVALIDBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE"
|
||||||
|
|
||||||
|
var _Type_index = [...]uint8{0, 7, 11, 16, 23, 29, 38, 48, 60, 71}
|
||||||
|
|
||||||
|
func (i Type) String() string {
|
||||||
|
if i < 0 || i >= Type(len(_Type_index)-1) {
|
||||||
|
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Type_name[_Type_index[i]:_Type_index[i+1]]
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate stringer -type=Type
|
||||||
|
|
||||||
|
// Type describes the type of the data Value holds.
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
// Value represents the value part in key-value pairs.
|
||||||
|
type Value struct {
|
||||||
|
vtype Type
|
||||||
|
numeric uint64
|
||||||
|
stringly string
|
||||||
|
slice interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// INVALID is used for a Value with no value set.
|
||||||
|
INVALID Type = iota
|
||||||
|
// BOOL is a boolean Type Value.
|
||||||
|
BOOL
|
||||||
|
// INT64 is a 64-bit signed integral Type Value.
|
||||||
|
INT64
|
||||||
|
// FLOAT64 is a 64-bit floating point Type Value.
|
||||||
|
FLOAT64
|
||||||
|
// STRING is a string Type Value.
|
||||||
|
STRING
|
||||||
|
// BOOLSLICE is a slice of booleans Type Value.
|
||||||
|
BOOLSLICE
|
||||||
|
// INT64SLICE is a slice of 64-bit signed integral numbers Type Value.
|
||||||
|
INT64SLICE
|
||||||
|
// FLOAT64SLICE is a slice of 64-bit floating point numbers Type Value.
|
||||||
|
FLOAT64SLICE
|
||||||
|
// STRINGSLICE is a slice of strings Type Value.
|
||||||
|
STRINGSLICE
|
||||||
|
)
|
||||||
|
|
||||||
|
// BoolValue creates a BOOL Value.
|
||||||
|
func BoolValue(v bool) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: BOOL,
|
||||||
|
numeric: internal.BoolToRaw(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSliceValue creates a BOOLSLICE Value.
|
||||||
|
func BoolSliceValue(v []bool) Value {
|
||||||
|
cp := make([]bool, len(v))
|
||||||
|
copy(cp, v)
|
||||||
|
return Value{
|
||||||
|
vtype: BOOLSLICE,
|
||||||
|
slice: &cp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntValue creates an INT64 Value.
|
||||||
|
func IntValue(v int) Value {
|
||||||
|
return Int64Value(int64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntSliceValue creates an INTSLICE Value.
|
||||||
|
func IntSliceValue(v []int) Value {
|
||||||
|
cp := make([]int64, 0, len(v))
|
||||||
|
for _, i := range v {
|
||||||
|
cp = append(cp, int64(i))
|
||||||
|
}
|
||||||
|
return Value{
|
||||||
|
vtype: INT64SLICE,
|
||||||
|
slice: &cp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Value creates an INT64 Value.
|
||||||
|
func Int64Value(v int64) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: INT64,
|
||||||
|
numeric: internal.Int64ToRaw(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64SliceValue creates an INT64SLICE Value.
|
||||||
|
func Int64SliceValue(v []int64) Value {
|
||||||
|
cp := make([]int64, len(v))
|
||||||
|
copy(cp, v)
|
||||||
|
return Value{
|
||||||
|
vtype: INT64SLICE,
|
||||||
|
slice: &cp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Value creates a FLOAT64 Value.
|
||||||
|
func Float64Value(v float64) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: FLOAT64,
|
||||||
|
numeric: internal.Float64ToRaw(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64SliceValue creates a FLOAT64SLICE Value.
|
||||||
|
func Float64SliceValue(v []float64) Value {
|
||||||
|
cp := make([]float64, len(v))
|
||||||
|
copy(cp, v)
|
||||||
|
return Value{
|
||||||
|
vtype: FLOAT64SLICE,
|
||||||
|
slice: &cp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringValue creates a STRING Value.
|
||||||
|
func StringValue(v string) Value {
|
||||||
|
return Value{
|
||||||
|
vtype: STRING,
|
||||||
|
stringly: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSliceValue creates a STRINGSLICE Value.
|
||||||
|
func StringSliceValue(v []string) Value {
|
||||||
|
cp := make([]string, len(v))
|
||||||
|
copy(cp, v)
|
||||||
|
return Value{
|
||||||
|
vtype: STRINGSLICE,
|
||||||
|
slice: &cp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns a type of the Value.
|
||||||
|
func (v Value) Type() Type {
|
||||||
|
return v.vtype
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsBool returns the bool value. Make sure that the Value's type is
|
||||||
|
// BOOL.
|
||||||
|
func (v Value) AsBool() bool {
|
||||||
|
return internal.RawToBool(v.numeric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsBoolSlice returns the []bool value. Make sure that the Value's type is
|
||||||
|
// BOOLSLICE.
|
||||||
|
func (v Value) AsBoolSlice() []bool {
|
||||||
|
if s, ok := v.slice.(*[]bool); ok {
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInt64 returns the int64 value. Make sure that the Value's type is
|
||||||
|
// INT64.
|
||||||
|
func (v Value) AsInt64() int64 {
|
||||||
|
return internal.RawToInt64(v.numeric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInt64Slice returns the []int64 value. Make sure that the Value's type is
|
||||||
|
// INT64SLICE.
|
||||||
|
func (v Value) AsInt64Slice() []int64 {
|
||||||
|
if s, ok := v.slice.(*[]int64); ok {
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsFloat64 returns the float64 value. Make sure that the Value's
|
||||||
|
// type is FLOAT64.
|
||||||
|
func (v Value) AsFloat64() float64 {
|
||||||
|
return internal.RawToFloat64(v.numeric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsFloat64Slice returns the []float64 value. Make sure that the Value's type is
|
||||||
|
// FLOAT64SLICE.
|
||||||
|
func (v Value) AsFloat64Slice() []float64 {
|
||||||
|
if s, ok := v.slice.(*[]float64); ok {
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsString returns the string value. Make sure that the Value's type
|
||||||
|
// is STRING.
|
||||||
|
func (v Value) AsString() string {
|
||||||
|
return v.stringly
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsStringSlice returns the []string value. Make sure that the Value's type is
|
||||||
|
// STRINGSLICE.
|
||||||
|
func (v Value) AsStringSlice() []string {
|
||||||
|
if s, ok := v.slice.(*[]string); ok {
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type unknownValueType struct{}
|
||||||
|
|
||||||
|
// AsInterface returns Value's data as interface{}.
|
||||||
|
func (v Value) AsInterface() interface{} {
|
||||||
|
switch v.Type() {
|
||||||
|
case BOOL:
|
||||||
|
return v.AsBool()
|
||||||
|
case BOOLSLICE:
|
||||||
|
return v.AsBoolSlice()
|
||||||
|
case INT64:
|
||||||
|
return v.AsInt64()
|
||||||
|
case INT64SLICE:
|
||||||
|
return v.AsInt64Slice()
|
||||||
|
case FLOAT64:
|
||||||
|
return v.AsFloat64()
|
||||||
|
case FLOAT64SLICE:
|
||||||
|
return v.AsFloat64Slice()
|
||||||
|
case STRING:
|
||||||
|
return v.stringly
|
||||||
|
case STRINGSLICE:
|
||||||
|
return v.AsStringSlice()
|
||||||
|
}
|
||||||
|
return unknownValueType{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit returns a string representation of Value's data.
|
||||||
|
func (v Value) Emit() string {
|
||||||
|
switch v.Type() {
|
||||||
|
case BOOLSLICE:
|
||||||
|
return fmt.Sprint(*(v.slice.(*[]bool)))
|
||||||
|
case BOOL:
|
||||||
|
return strconv.FormatBool(v.AsBool())
|
||||||
|
case INT64SLICE:
|
||||||
|
return fmt.Sprint(*(v.slice.(*[]int64)))
|
||||||
|
case INT64:
|
||||||
|
return strconv.FormatInt(v.AsInt64(), 10)
|
||||||
|
case FLOAT64SLICE:
|
||||||
|
return fmt.Sprint(*(v.slice.(*[]float64)))
|
||||||
|
case FLOAT64:
|
||||||
|
return fmt.Sprint(v.AsFloat64())
|
||||||
|
case STRINGSLICE:
|
||||||
|
return fmt.Sprint(*(v.slice.(*[]string)))
|
||||||
|
case STRING:
|
||||||
|
return v.stringly
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of the Value.
|
||||||
|
func (v Value) MarshalJSON() ([]byte, error) {
|
||||||
|
var jsonVal struct {
|
||||||
|
Type string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
jsonVal.Type = v.Type().String()
|
||||||
|
jsonVal.Value = v.AsInterface()
|
||||||
|
return json.Marshal(jsonVal)
|
||||||
|
}
|
|
@ -0,0 +1,556 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package baggage // import "go.opentelemetry.io/otel/baggage"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/internal/baggage"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxMembers = 180
|
||||||
|
maxBytesPerMembers = 4096
|
||||||
|
maxBytesPerBaggageString = 8192
|
||||||
|
|
||||||
|
listDelimiter = ","
|
||||||
|
keyValueDelimiter = "="
|
||||||
|
propertyDelimiter = ";"
|
||||||
|
|
||||||
|
keyDef = `([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+)`
|
||||||
|
valueDef = `([\x21\x23-\x2b\x2d-\x3a\x3c-\x5B\x5D-\x7e]*)`
|
||||||
|
keyValueDef = `\s*` + keyDef + `\s*` + keyValueDelimiter + `\s*` + valueDef + `\s*`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
keyRe = regexp.MustCompile(`^` + keyDef + `$`)
|
||||||
|
valueRe = regexp.MustCompile(`^` + valueDef + `$`)
|
||||||
|
propertyRe = regexp.MustCompile(`^(?:\s*` + keyDef + `\s*|` + keyValueDef + `)$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidKey = errors.New("invalid key")
|
||||||
|
errInvalidValue = errors.New("invalid value")
|
||||||
|
errInvalidProperty = errors.New("invalid baggage list-member property")
|
||||||
|
errInvalidMember = errors.New("invalid baggage list-member")
|
||||||
|
errMemberNumber = errors.New("too many list-members in baggage-string")
|
||||||
|
errMemberBytes = errors.New("list-member too large")
|
||||||
|
errBaggageBytes = errors.New("baggage-string too large")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Property is an additional metadata entry for a baggage list-member.
|
||||||
|
type Property struct {
|
||||||
|
key, value string
|
||||||
|
|
||||||
|
// hasValue indicates if a zero-value value means the property does not
|
||||||
|
// have a value or if it was the zero-value.
|
||||||
|
hasValue bool
|
||||||
|
|
||||||
|
// hasData indicates whether the created property contains data or not.
|
||||||
|
// Properties that do not contain data are invalid with no other check
|
||||||
|
// required.
|
||||||
|
hasData bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeyProperty(key string) (Property, error) {
|
||||||
|
if !keyRe.MatchString(key) {
|
||||||
|
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Property{key: key, hasData: true}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeyValueProperty(key, value string) (Property, error) {
|
||||||
|
if !keyRe.MatchString(key) {
|
||||||
|
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
||||||
|
}
|
||||||
|
if !valueRe.MatchString(value) {
|
||||||
|
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Property{
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
hasValue: true,
|
||||||
|
hasData: true,
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInvalidProperty() Property {
|
||||||
|
return Property{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseProperty attempts to decode a Property from the passed string. It
|
||||||
|
// returns an error if the input is invalid according to the W3C Baggage
|
||||||
|
// specification.
|
||||||
|
func parseProperty(property string) (Property, error) {
|
||||||
|
if property == "" {
|
||||||
|
return newInvalidProperty(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
match := propertyRe.FindStringSubmatch(property)
|
||||||
|
if len(match) != 4 {
|
||||||
|
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Property{hasData: true}
|
||||||
|
if match[1] != "" {
|
||||||
|
p.key = match[1]
|
||||||
|
} else {
|
||||||
|
p.key = match[2]
|
||||||
|
p.value = match[3]
|
||||||
|
p.hasValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate ensures p conforms to the W3C Baggage specification, returning an
|
||||||
|
// error otherwise.
|
||||||
|
func (p Property) validate() error {
|
||||||
|
errFunc := func(err error) error {
|
||||||
|
return fmt.Errorf("invalid property: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.hasData {
|
||||||
|
return errFunc(fmt.Errorf("%w: %q", errInvalidProperty, p))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !keyRe.MatchString(p.key) {
|
||||||
|
return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
|
||||||
|
}
|
||||||
|
if p.hasValue && !valueRe.MatchString(p.value) {
|
||||||
|
return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
|
||||||
|
}
|
||||||
|
if !p.hasValue && p.value != "" {
|
||||||
|
return errFunc(errors.New("inconsistent value"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the Property key.
|
||||||
|
func (p Property) Key() string {
|
||||||
|
return p.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the Property value. Additionally a boolean value is returned
|
||||||
|
// indicating if the returned value is the empty if the Property has a value
|
||||||
|
// that is empty or if the value is not set.
|
||||||
|
func (p Property) Value() (string, bool) {
|
||||||
|
return p.value, p.hasValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes Property into a string compliant with the W3C Baggage
|
||||||
|
// specification.
|
||||||
|
func (p Property) String() string {
|
||||||
|
if p.hasValue {
|
||||||
|
return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, p.value)
|
||||||
|
}
|
||||||
|
return p.key
|
||||||
|
}
|
||||||
|
|
||||||
|
type properties []Property
|
||||||
|
|
||||||
|
func fromInternalProperties(iProps []baggage.Property) properties {
|
||||||
|
if len(iProps) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
props := make(properties, len(iProps))
|
||||||
|
for i, p := range iProps {
|
||||||
|
props[i] = Property{
|
||||||
|
key: p.Key,
|
||||||
|
value: p.Value,
|
||||||
|
hasValue: p.HasValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p properties) asInternal() []baggage.Property {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iProps := make([]baggage.Property, len(p))
|
||||||
|
for i, prop := range p {
|
||||||
|
iProps[i] = baggage.Property{
|
||||||
|
Key: prop.key,
|
||||||
|
Value: prop.value,
|
||||||
|
HasValue: prop.hasValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iProps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p properties) Copy() properties {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
props := make(properties, len(p))
|
||||||
|
copy(props, p)
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate ensures each Property in p conforms to the W3C Baggage
|
||||||
|
// specification, returning an error otherwise.
|
||||||
|
func (p properties) validate() error {
|
||||||
|
for _, prop := range p {
|
||||||
|
if err := prop.validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes properties into a string compliant with the W3C Baggage
|
||||||
|
// specification.
|
||||||
|
func (p properties) String() string {
|
||||||
|
props := make([]string, len(p))
|
||||||
|
for i, prop := range p {
|
||||||
|
props[i] = prop.String()
|
||||||
|
}
|
||||||
|
return strings.Join(props, propertyDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member is a list-member of a baggage-string as defined by the W3C Baggage
|
||||||
|
// specification.
|
||||||
|
type Member struct {
|
||||||
|
key, value string
|
||||||
|
properties properties
|
||||||
|
|
||||||
|
// hasData indicates whether the created property contains data or not.
|
||||||
|
// Properties that do not contain data are invalid with no other check
|
||||||
|
// required.
|
||||||
|
hasData bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMember returns a new Member from the passed arguments. An error is
|
||||||
|
// returned if the created Member would be invalid according to the W3C
|
||||||
|
// Baggage specification.
|
||||||
|
func NewMember(key, value string, props ...Property) (Member, error) {
|
||||||
|
m := Member{
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
properties: properties(props).Copy(),
|
||||||
|
hasData: true,
|
||||||
|
}
|
||||||
|
if err := m.validate(); err != nil {
|
||||||
|
return newInvalidMember(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInvalidMember() Member {
|
||||||
|
return Member{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMember attempts to decode a Member from the passed string. It returns
|
||||||
|
// an error if the input is invalid according to the W3C Baggage
|
||||||
|
// specification.
|
||||||
|
func parseMember(member string) (Member, error) {
|
||||||
|
if n := len(member); n > maxBytesPerMembers {
|
||||||
|
return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
key, value string
|
||||||
|
props properties
|
||||||
|
)
|
||||||
|
|
||||||
|
parts := strings.SplitN(member, propertyDelimiter, 2)
|
||||||
|
switch len(parts) {
|
||||||
|
case 2:
|
||||||
|
// Parse the member properties.
|
||||||
|
for _, pStr := range strings.Split(parts[1], propertyDelimiter) {
|
||||||
|
p, err := parseProperty(pStr)
|
||||||
|
if err != nil {
|
||||||
|
return newInvalidMember(), err
|
||||||
|
}
|
||||||
|
props = append(props, p)
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
// Parse the member key/value pair.
|
||||||
|
|
||||||
|
// Take into account a value can contain equal signs (=).
|
||||||
|
kv := strings.SplitN(parts[0], keyValueDelimiter, 2)
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member)
|
||||||
|
}
|
||||||
|
// "Leading and trailing whitespaces are allowed but MUST be trimmed
|
||||||
|
// when converting the header into a data structure."
|
||||||
|
key = strings.TrimSpace(kv[0])
|
||||||
|
var err error
|
||||||
|
value, err = url.QueryUnescape(strings.TrimSpace(kv[1]))
|
||||||
|
if err != nil {
|
||||||
|
return newInvalidMember(), fmt.Errorf("%w: %q", err, value)
|
||||||
|
}
|
||||||
|
if !keyRe.MatchString(key) {
|
||||||
|
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
||||||
|
}
|
||||||
|
if !valueRe.MatchString(value) {
|
||||||
|
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// This should never happen unless a developer has changed the string
|
||||||
|
// splitting somehow. Panic instead of failing silently and allowing
|
||||||
|
// the bug to slip past the CI checks.
|
||||||
|
panic("failed to parse baggage member")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Member{key: key, value: value, properties: props, hasData: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate ensures m conforms to the W3C Baggage specification, returning an
|
||||||
|
// error otherwise.
|
||||||
|
func (m Member) validate() error {
|
||||||
|
if !m.hasData {
|
||||||
|
return fmt.Errorf("%w: %q", errInvalidMember, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !keyRe.MatchString(m.key) {
|
||||||
|
return fmt.Errorf("%w: %q", errInvalidKey, m.key)
|
||||||
|
}
|
||||||
|
if !valueRe.MatchString(m.value) {
|
||||||
|
return fmt.Errorf("%w: %q", errInvalidValue, m.value)
|
||||||
|
}
|
||||||
|
return m.properties.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the Member key.
|
||||||
|
func (m Member) Key() string { return m.key }
|
||||||
|
|
||||||
|
// Value returns the Member value.
|
||||||
|
func (m Member) Value() string { return m.value }
|
||||||
|
|
||||||
|
// Properties returns a copy of the Member properties.
|
||||||
|
func (m Member) Properties() []Property { return m.properties.Copy() }
|
||||||
|
|
||||||
|
// String encodes Member into a string compliant with the W3C Baggage
|
||||||
|
// specification.
|
||||||
|
func (m Member) String() string {
|
||||||
|
// A key is just an ASCII string, but a value is URL encoded UTF-8.
|
||||||
|
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, url.QueryEscape(m.value))
|
||||||
|
if len(m.properties) > 0 {
|
||||||
|
s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baggage is a list of baggage members representing the baggage-string as
|
||||||
|
// defined by the W3C Baggage specification.
|
||||||
|
type Baggage struct { //nolint:golint
|
||||||
|
list baggage.List
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new valid Baggage. It returns an error if it results in a
|
||||||
|
// Baggage exceeding limits set in that specification.
|
||||||
|
//
|
||||||
|
// It expects all the provided members to have already been validated.
|
||||||
|
func New(members ...Member) (Baggage, error) {
|
||||||
|
if len(members) == 0 {
|
||||||
|
return Baggage{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make(baggage.List)
|
||||||
|
for _, m := range members {
|
||||||
|
if !m.hasData {
|
||||||
|
return Baggage{}, errInvalidMember
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||||
|
b[m.key] = baggage.Item{
|
||||||
|
Value: m.value,
|
||||||
|
Properties: m.properties.asInternal(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check member numbers after deduplicating.
|
||||||
|
if len(b) > maxMembers {
|
||||||
|
return Baggage{}, errMemberNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
bag := Baggage{b}
|
||||||
|
if n := len(bag.String()); n > maxBytesPerBaggageString {
|
||||||
|
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse attempts to decode a baggage-string from the passed string. It
|
||||||
|
// returns an error if the input is invalid according to the W3C Baggage
|
||||||
|
// specification.
|
||||||
|
//
|
||||||
|
// If there are duplicate list-members contained in baggage, the last one
|
||||||
|
// defined (reading left-to-right) will be the only one kept. This diverges
|
||||||
|
// from the W3C Baggage specification which allows duplicate list-members, but
|
||||||
|
// conforms to the OpenTelemetry Baggage specification.
|
||||||
|
func Parse(bStr string) (Baggage, error) {
|
||||||
|
if bStr == "" {
|
||||||
|
return Baggage{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(bStr); n > maxBytesPerBaggageString {
|
||||||
|
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make(baggage.List)
|
||||||
|
for _, memberStr := range strings.Split(bStr, listDelimiter) {
|
||||||
|
m, err := parseMember(memberStr)
|
||||||
|
if err != nil {
|
||||||
|
return Baggage{}, err
|
||||||
|
}
|
||||||
|
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||||
|
b[m.key] = baggage.Item{
|
||||||
|
Value: m.value,
|
||||||
|
Properties: m.properties.asInternal(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenTelemetry does not allow for duplicate list-members, but the W3C
|
||||||
|
// specification does. Now that we have deduplicated, ensure the baggage
|
||||||
|
// does not exceed list-member limits.
|
||||||
|
if len(b) > maxMembers {
|
||||||
|
return Baggage{}, errMemberNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
return Baggage{b}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member returns the baggage list-member identified by key.
|
||||||
|
//
|
||||||
|
// If there is no list-member matching the passed key the returned Member will
|
||||||
|
// be a zero-value Member.
|
||||||
|
// The returned member is not validated, as we assume the validation happened
|
||||||
|
// when it was added to the Baggage.
|
||||||
|
func (b Baggage) Member(key string) Member {
|
||||||
|
v, ok := b.list[key]
|
||||||
|
if !ok {
|
||||||
|
// We do not need to worry about distiguising between the situation
|
||||||
|
// where a zero-valued Member is included in the Baggage because a
|
||||||
|
// zero-valued Member is invalid according to the W3C Baggage
|
||||||
|
// specification (it has an empty key).
|
||||||
|
return newInvalidMember()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Member{
|
||||||
|
key: key,
|
||||||
|
value: v.Value,
|
||||||
|
properties: fromInternalProperties(v.Properties),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Members returns all the baggage list-members.
|
||||||
|
// The order of the returned list-members does not have significance.
|
||||||
|
//
|
||||||
|
// The returned members are not validated, as we assume the validation happened
|
||||||
|
// when they were added to the Baggage.
|
||||||
|
func (b Baggage) Members() []Member {
|
||||||
|
if len(b.list) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
members := make([]Member, 0, len(b.list))
|
||||||
|
for k, v := range b.list {
|
||||||
|
members = append(members, Member{
|
||||||
|
key: k,
|
||||||
|
value: v.Value,
|
||||||
|
properties: fromInternalProperties(v.Properties),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMember returns a copy the Baggage with the member included. If the
|
||||||
|
// baggage contains a Member with the same key the existing Member is
|
||||||
|
// replaced.
|
||||||
|
//
|
||||||
|
// If member is invalid according to the W3C Baggage specification, an error
|
||||||
|
// is returned with the original Baggage.
|
||||||
|
func (b Baggage) SetMember(member Member) (Baggage, error) {
|
||||||
|
if !member.hasData {
|
||||||
|
return b, errInvalidMember
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(b.list)
|
||||||
|
if _, ok := b.list[member.key]; !ok {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
list := make(baggage.List, n)
|
||||||
|
|
||||||
|
for k, v := range b.list {
|
||||||
|
// Do not copy if we are just going to overwrite.
|
||||||
|
if k == member.key {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
list[member.key] = baggage.Item{
|
||||||
|
Value: member.value,
|
||||||
|
Properties: member.properties.asInternal(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return Baggage{list: list}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMember returns a copy of the Baggage with the list-member identified
|
||||||
|
// by key removed.
|
||||||
|
func (b Baggage) DeleteMember(key string) Baggage {
|
||||||
|
n := len(b.list)
|
||||||
|
if _, ok := b.list[key]; ok {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
list := make(baggage.List, n)
|
||||||
|
|
||||||
|
for k, v := range b.list {
|
||||||
|
if k == key {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return Baggage{list: list}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of list-members in the Baggage.
|
||||||
|
func (b Baggage) Len() int {
|
||||||
|
return len(b.list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes Baggage into a string compliant with the W3C Baggage
|
||||||
|
// specification. The returned string will be invalid if the Baggage contains
|
||||||
|
// any invalid list-members.
|
||||||
|
func (b Baggage) String() string {
|
||||||
|
members := make([]string, 0, len(b.list))
|
||||||
|
for k, v := range b.list {
|
||||||
|
members = append(members, Member{
|
||||||
|
key: k,
|
||||||
|
value: v.Value,
|
||||||
|
properties: fromInternalProperties(v.Properties),
|
||||||
|
}.String())
|
||||||
|
}
|
||||||
|
return strings.Join(members, listDelimiter)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package baggage // import "go.opentelemetry.io/otel/baggage"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/internal/baggage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContextWithBaggage returns a copy of parent with baggage.
|
||||||
|
func ContextWithBaggage(parent context.Context, b Baggage) context.Context {
|
||||||
|
// Delegate so any hooks for the OpenTracing bridge are handled.
|
||||||
|
return baggage.ContextWithList(parent, b.list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextWithoutBaggage returns a copy of parent with no baggage.
|
||||||
|
func ContextWithoutBaggage(parent context.Context) context.Context {
|
||||||
|
// Delegate so any hooks for the OpenTracing bridge are handled.
|
||||||
|
return baggage.ContextWithList(parent, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromContext returns the baggage contained in ctx.
|
||||||
|
func FromContext(ctx context.Context) Baggage {
|
||||||
|
// Delegate so any hooks for the OpenTracing bridge are handled.
|
||||||
|
return Baggage{list: baggage.ListFromContext(ctx)}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package baggage provides functionality for storing and retrieving
|
||||||
|
baggage items in Go context. For propagating the baggage, see the
|
||||||
|
go.opentelemetry.io/otel/propagation package.
|
||||||
|
*/
|
||||||
|
package baggage // import "go.opentelemetry.io/otel/baggage"
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package codes // import "go.opentelemetry.io/otel/codes"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Unset is the default status code.
|
||||||
|
Unset Code = 0
|
||||||
|
// Error indicates the operation contains an error.
|
||||||
|
Error Code = 1
|
||||||
|
// Ok indicates operation has been validated by an Application developers
|
||||||
|
// or Operator to have completed successfully, or contain no error.
|
||||||
|
Ok Code = 2
|
||||||
|
|
||||||
|
maxCode = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Code is an 32-bit representation of a status state.
|
||||||
|
type Code uint32
|
||||||
|
|
||||||
|
var codeToStr = map[Code]string{
|
||||||
|
Unset: "Unset",
|
||||||
|
Error: "Error",
|
||||||
|
Ok: "Ok",
|
||||||
|
}
|
||||||
|
|
||||||
|
var strToCode = map[string]Code{
|
||||||
|
`"Unset"`: Unset,
|
||||||
|
`"Error"`: Error,
|
||||||
|
`"Ok"`: Ok,
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the Code as a string.
|
||||||
|
func (c Code) String() string {
|
||||||
|
return codeToStr[c]
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals b into the Code.
|
||||||
|
//
|
||||||
|
// This is based on the functionality in the gRPC codes package:
|
||||||
|
// https://github.com/grpc/grpc-go/blob/bb64fee312b46ebee26be43364a7a966033521b1/codes/codes.go#L218-L244
|
||||||
|
func (c *Code) UnmarshalJSON(b []byte) error {
|
||||||
|
// From json.Unmarshaler: By convention, to approximate the behavior of
|
||||||
|
// Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as
|
||||||
|
// a no-op.
|
||||||
|
if string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c == nil {
|
||||||
|
return fmt.Errorf("nil receiver passed to UnmarshalJSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
var x interface{}
|
||||||
|
if err := json.Unmarshal(b, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch x.(type) {
|
||||||
|
case string:
|
||||||
|
if jc, ok := strToCode[string(b)]; ok {
|
||||||
|
*c = jc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid code: %q", string(b))
|
||||||
|
case float64:
|
||||||
|
if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil {
|
||||||
|
if ci >= maxCode {
|
||||||
|
return fmt.Errorf("invalid code: %q", ci)
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = Code(ci)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid code: %q", string(b))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid code: %q", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns c as the JSON encoding of c.
|
||||||
|
func (c *Code) MarshalJSON() ([]byte, error) {
|
||||||
|
if c == nil {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
str, ok := codeToStr[*c]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid code: %d", *c)
|
||||||
|
}
|
||||||
|
return []byte(fmt.Sprintf("%q", str)), nil
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package codes defines the canonical error codes used by OpenTelemetry.
|
||||||
|
|
||||||
|
It conforms to [the OpenTelemetry
|
||||||
|
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#statuscanonicalcode).
|
||||||
|
*/
|
||||||
|
package codes // import "go.opentelemetry.io/otel/codes"
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package otel provides global access to the OpenTelemetry API. The subpackages of
|
||||||
|
the otel package provide an implementation of the OpenTelemetry API.
|
||||||
|
|
||||||
|
The provided API is used to instrument code and measure data about that code's
|
||||||
|
performance and operation. The measured data, by default, is not processed or
|
||||||
|
transmitted anywhere. An implementation of the OpenTelemetry SDK, like the
|
||||||
|
default SDK implementation (go.opentelemetry.io/otel/sdk), and associated
|
||||||
|
exporters are used to process and transport this data.
|
||||||
|
|
||||||
|
To read the getting started guide, see https://opentelemetry.io/docs/go/getting-started/.
|
||||||
|
|
||||||
|
To read more about tracing, see go.opentelemetry.io/otel/trace.
|
||||||
|
|
||||||
|
To read more about metrics, see go.opentelemetry.io/otel/metric.
|
||||||
|
|
||||||
|
To read more about propagation, see go.opentelemetry.io/otel/propagation and
|
||||||
|
go.opentelemetry.io/otel/baggage.
|
||||||
|
*/
|
||||||
|
package otel // import "go.opentelemetry.io/otel"
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otel // import "go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
// ErrorHandler handles irremediable events.
|
||||||
|
type ErrorHandler interface {
|
||||||
|
// DO NOT CHANGE: any modification will not be backwards compatible and
|
||||||
|
// must never be done outside of a new major release.
|
||||||
|
|
||||||
|
// Handle handles any error deemed irremediable by an OpenTelemetry
|
||||||
|
// component.
|
||||||
|
Handle(error)
|
||||||
|
// DO NOT CHANGE: any modification will not be backwards compatible and
|
||||||
|
// must never be done outside of a new major release.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorHandlerFunc is a convenience adapter to allow the use of a function
|
||||||
|
// as an ErrorHandler.
|
||||||
|
type ErrorHandlerFunc func(error)
|
||||||
|
|
||||||
|
var _ ErrorHandler = ErrorHandlerFunc(nil)
|
||||||
|
|
||||||
|
// Handle handles the irremediable error by calling the ErrorHandlerFunc itself.
|
||||||
|
func (f ErrorHandlerFunc) Handle(err error) {
|
||||||
|
f(err)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright The OpenTelemetry Authors
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
top_dir='.'
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
top_dir="${1}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
p=$(pwd)
|
||||||
|
mod_dirs=()
|
||||||
|
|
||||||
|
# Note `mapfile` does not exist in older bash versions:
|
||||||
|
# https://stackoverflow.com/questions/41475261/need-alternative-to-readarray-mapfile-for-script-on-older-version-of-bash
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
mod_dirs+=("$line")
|
||||||
|
done < <(find "${top_dir}" -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||||
|
|
||||||
|
for mod_dir in "${mod_dirs[@]}"; do
|
||||||
|
cd "${mod_dir}"
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
echo ".${line#${p}}"
|
||||||
|
done < <(go list --find -f '{{.Name}}|{{.Dir}}' ./... | grep '^main|' | cut -f 2- -d '|')
|
||||||
|
cd "${p}"
|
||||||
|
done
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otel // import "go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// globalErrorHandler provides an ErrorHandler that can be used
|
||||||
|
// throughout an OpenTelemetry instrumented project. When a user
|
||||||
|
// specified ErrorHandler is registered (`SetErrorHandler`) all calls to
|
||||||
|
// `Handle` and will be delegated to the registered ErrorHandler.
|
||||||
|
globalErrorHandler = defaultErrorHandler()
|
||||||
|
|
||||||
|
// Compile-time check that delegator implements ErrorHandler.
|
||||||
|
_ ErrorHandler = (*delegator)(nil)
|
||||||
|
// Compile-time check that errLogger implements ErrorHandler.
|
||||||
|
_ ErrorHandler = (*errLogger)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type delegator struct {
|
||||||
|
lock *sync.RWMutex
|
||||||
|
eh ErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *delegator) Handle(err error) {
|
||||||
|
d.lock.RLock()
|
||||||
|
defer d.lock.RUnlock()
|
||||||
|
d.eh.Handle(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDelegate sets the ErrorHandler delegate.
|
||||||
|
func (d *delegator) setDelegate(eh ErrorHandler) {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
d.eh = eh
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultErrorHandler() *delegator {
|
||||||
|
return &delegator{
|
||||||
|
lock: &sync.RWMutex{},
|
||||||
|
eh: &errLogger{l: log.New(os.Stderr, "", log.LstdFlags)},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// errLogger logs errors if no delegate is set, otherwise they are delegated.
|
||||||
|
type errLogger struct {
|
||||||
|
l *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle logs err if no delegate is set, otherwise it is delegated.
|
||||||
|
func (h *errLogger) Handle(err error) {
|
||||||
|
h.l.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorHandler returns the global ErrorHandler instance.
|
||||||
|
//
|
||||||
|
// The default ErrorHandler instance returned will log all errors to STDERR
|
||||||
|
// until an override ErrorHandler is set with SetErrorHandler. All
|
||||||
|
// ErrorHandler returned prior to this will automatically forward errors to
|
||||||
|
// the set instance instead of logging.
|
||||||
|
//
|
||||||
|
// Subsequent calls to SetErrorHandler after the first will not forward errors
|
||||||
|
// to the new ErrorHandler for prior returned instances.
|
||||||
|
func GetErrorHandler() ErrorHandler {
|
||||||
|
return globalErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrorHandler sets the global ErrorHandler to h.
|
||||||
|
//
|
||||||
|
// The first time this is called all ErrorHandler previously returned from
|
||||||
|
// GetErrorHandler will send errors to h instead of the default logging
|
||||||
|
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
|
||||||
|
// delegate errors to h.
|
||||||
|
func SetErrorHandler(h ErrorHandler) {
|
||||||
|
globalErrorHandler.setDelegate(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is a convenience function for ErrorHandler().Handle(err)
|
||||||
|
func Handle(err error) {
|
||||||
|
GetErrorHandler().Handle(err)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package baggage provides base types and functionality to store and retrieve
|
||||||
|
baggage in Go context. This package exists because the OpenTracing bridge to
|
||||||
|
OpenTelemetry needs to synchronize state whenever baggage for a context is
|
||||||
|
modified and that context contains an OpenTracing span. If it were not for
|
||||||
|
this need this package would not need to exist and the
|
||||||
|
`go.opentelemetry.io/otel/baggage` package would be the singular place where
|
||||||
|
W3C baggage is handled.
|
||||||
|
*/
|
||||||
|
package baggage // import "go.opentelemetry.io/otel/internal/baggage"
|
||||||
|
|
||||||
|
// List is the collection of baggage members. The W3C allows for duplicates,
|
||||||
|
// but OpenTelemetry does not, therefore, this is represented as a map.
|
||||||
|
type List map[string]Item
|
||||||
|
|
||||||
|
// Item is the value and metadata properties part of a list-member.
|
||||||
|
type Item struct {
|
||||||
|
Value string
|
||||||
|
Properties []Property
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property is a metadata entry for a list-member.
|
||||||
|
type Property struct {
|
||||||
|
Key, Value string
|
||||||
|
|
||||||
|
// HasValue indicates if a zero-value value means the property does not
|
||||||
|
// have a value or if it was the zero-value.
|
||||||
|
HasValue bool
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package baggage // import "go.opentelemetry.io/otel/internal/baggage"
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type baggageContextKeyType int
|
||||||
|
|
||||||
|
const baggageKey baggageContextKeyType = iota
|
||||||
|
|
||||||
|
// SetHookFunc is a callback called when storing baggage in the context.
|
||||||
|
type SetHookFunc func(context.Context, List) context.Context
|
||||||
|
|
||||||
|
// GetHookFunc is a callback called when getting baggage from the context.
|
||||||
|
type GetHookFunc func(context.Context, List) List
|
||||||
|
|
||||||
|
type baggageState struct {
|
||||||
|
list List
|
||||||
|
|
||||||
|
setHook SetHookFunc
|
||||||
|
getHook GetHookFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextWithSetHook returns a copy of parent with hook configured to be
|
||||||
|
// invoked every time ContextWithBaggage is called.
|
||||||
|
//
|
||||||
|
// Passing nil SetHookFunc creates a context with no set hook to call.
|
||||||
|
func ContextWithSetHook(parent context.Context, hook SetHookFunc) context.Context {
|
||||||
|
var s baggageState
|
||||||
|
switch v := parent.Value(baggageKey).(type) {
|
||||||
|
case baggageState:
|
||||||
|
s = v
|
||||||
|
}
|
||||||
|
|
||||||
|
s.setHook = hook
|
||||||
|
return context.WithValue(parent, baggageKey, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextWithGetHook returns a copy of parent with hook configured to be
|
||||||
|
// invoked every time FromContext is called.
|
||||||
|
//
|
||||||
|
// Passing nil GetHookFunc creates a context with no get hook to call.
|
||||||
|
func ContextWithGetHook(parent context.Context, hook GetHookFunc) context.Context {
|
||||||
|
var s baggageState
|
||||||
|
switch v := parent.Value(baggageKey).(type) {
|
||||||
|
case baggageState:
|
||||||
|
s = v
|
||||||
|
}
|
||||||
|
|
||||||
|
s.getHook = hook
|
||||||
|
return context.WithValue(parent, baggageKey, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextWithList returns a copy of parent with baggage. Passing nil list
|
||||||
|
// returns a context without any baggage.
|
||||||
|
func ContextWithList(parent context.Context, list List) context.Context {
|
||||||
|
var s baggageState
|
||||||
|
switch v := parent.Value(baggageKey).(type) {
|
||||||
|
case baggageState:
|
||||||
|
s = v
|
||||||
|
}
|
||||||
|
|
||||||
|
s.list = list
|
||||||
|
ctx := context.WithValue(parent, baggageKey, s)
|
||||||
|
if s.setHook != nil {
|
||||||
|
ctx = s.setHook(ctx, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFromContext returns the baggage contained in ctx.
|
||||||
|
func ListFromContext(ctx context.Context) List {
|
||||||
|
switch v := ctx.Value(baggageKey).(type) {
|
||||||
|
case baggageState:
|
||||||
|
if v.getHook != nil {
|
||||||
|
return v.getHook(ctx, v.list)
|
||||||
|
}
|
||||||
|
return v.list
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
63
vendor/go.opentelemetry.io/otel/internal/global/internal_logging.go
generated
vendored
Normal file
63
vendor/go.opentelemetry.io/otel/internal/global/internal_logging.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package global // import "go.opentelemetry.io/otel/internal/global"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
|
"github.com/go-logr/stdr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// globalLogger is the logging interface used within the otel api and sdk provide deatails of the internals.
|
||||||
|
//
|
||||||
|
// The default logger uses stdr which is backed by the standard `log.Logger`
|
||||||
|
// interface. This logger will only show messages at the Error Level.
|
||||||
|
var globalLogger logr.Logger = stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile))
|
||||||
|
var globalLoggerLock = &sync.RWMutex{}
|
||||||
|
|
||||||
|
// SetLogger overrides the globalLogger with l.
|
||||||
|
//
|
||||||
|
// To see Info messages use a logger with `l.V(1).Enabled() == true`
|
||||||
|
// To see Debug messages use a logger with `l.V(5).Enabled() == true`
|
||||||
|
func SetLogger(l logr.Logger) {
|
||||||
|
globalLoggerLock.Lock()
|
||||||
|
defer globalLoggerLock.Unlock()
|
||||||
|
globalLogger = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info prints messages about the general state of the API or SDK.
|
||||||
|
// This should usually be less then 5 messages a minute
|
||||||
|
func Info(msg string, keysAndValues ...interface{}) {
|
||||||
|
globalLoggerLock.RLock()
|
||||||
|
defer globalLoggerLock.RUnlock()
|
||||||
|
globalLogger.V(1).Info(msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error prints messages about exceptional states of the API or SDK.
|
||||||
|
func Error(err error, msg string, keysAndValues ...interface{}) {
|
||||||
|
globalLoggerLock.RLock()
|
||||||
|
defer globalLoggerLock.RUnlock()
|
||||||
|
globalLogger.Error(err, msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug prints messages about all internal changes in the API or SDK.
|
||||||
|
func Debug(msg string, keysAndValues ...interface{}) {
|
||||||
|
globalLoggerLock.RLock()
|
||||||
|
defer globalLoggerLock.RUnlock()
|
||||||
|
globalLogger.V(5).Info(msg, keysAndValues...)
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package global // import "go.opentelemetry.io/otel/internal/global"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// textMapPropagator is a default TextMapPropagator that delegates calls to a
|
||||||
|
// registered delegate if one is set, otherwise it defaults to delegating the
|
||||||
|
// calls to a the default no-op propagation.TextMapPropagator.
|
||||||
|
type textMapPropagator struct {
|
||||||
|
mtx sync.Mutex
|
||||||
|
once sync.Once
|
||||||
|
delegate propagation.TextMapPropagator
|
||||||
|
noop propagation.TextMapPropagator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time guarantee that textMapPropagator implements the
|
||||||
|
// propagation.TextMapPropagator interface.
|
||||||
|
var _ propagation.TextMapPropagator = (*textMapPropagator)(nil)
|
||||||
|
|
||||||
|
func newTextMapPropagator() *textMapPropagator {
|
||||||
|
return &textMapPropagator{
|
||||||
|
noop: propagation.NewCompositeTextMapPropagator(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDelegate sets a delegate propagation.TextMapPropagator that all calls are
|
||||||
|
// forwarded to. Delegation can only be performed once, all subsequent calls
|
||||||
|
// perform no delegation.
|
||||||
|
func (p *textMapPropagator) SetDelegate(delegate propagation.TextMapPropagator) {
|
||||||
|
if delegate == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mtx.Lock()
|
||||||
|
p.once.Do(func() { p.delegate = delegate })
|
||||||
|
p.mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// effectiveDelegate returns the current delegate of p if one is set,
|
||||||
|
// otherwise the default noop TextMapPropagator is returned. This method
|
||||||
|
// can be called concurrently.
|
||||||
|
func (p *textMapPropagator) effectiveDelegate() propagation.TextMapPropagator {
|
||||||
|
p.mtx.Lock()
|
||||||
|
defer p.mtx.Unlock()
|
||||||
|
if p.delegate != nil {
|
||||||
|
return p.delegate
|
||||||
|
}
|
||||||
|
return p.noop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject set cross-cutting concerns from the Context into the carrier.
|
||||||
|
func (p *textMapPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
|
||||||
|
p.effectiveDelegate().Inject(ctx, carrier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract reads cross-cutting concerns from the carrier into a Context.
|
||||||
|
func (p *textMapPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
|
||||||
|
return p.effectiveDelegate().Extract(ctx, carrier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields returns the keys whose values are set with Inject.
|
||||||
|
func (p *textMapPropagator) Fields() []string {
|
||||||
|
return p.effectiveDelegate().Fields()
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package global // import "go.opentelemetry.io/otel/internal/global"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
tracerProviderHolder struct {
|
||||||
|
tp trace.TracerProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
propagatorsHolder struct {
|
||||||
|
tm propagation.TextMapPropagator
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalTracer = defaultTracerValue()
|
||||||
|
globalPropagators = defaultPropagatorsValue()
|
||||||
|
|
||||||
|
delegateTraceOnce sync.Once
|
||||||
|
delegateTextMapPropagatorOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracerProvider is the internal implementation for global.TracerProvider.
|
||||||
|
func TracerProvider() trace.TracerProvider {
|
||||||
|
return globalTracer.Load().(tracerProviderHolder).tp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTracerProvider is the internal implementation for global.SetTracerProvider.
|
||||||
|
func SetTracerProvider(tp trace.TracerProvider) {
|
||||||
|
delegateTraceOnce.Do(func() {
|
||||||
|
current := TracerProvider()
|
||||||
|
if current == tp {
|
||||||
|
// Setting the provider to the prior default is nonsense, panic.
|
||||||
|
// Panic is acceptable because we are likely still early in the
|
||||||
|
// process lifetime.
|
||||||
|
panic("invalid TracerProvider, the global instance cannot be reinstalled")
|
||||||
|
} else if def, ok := current.(*tracerProvider); ok {
|
||||||
|
def.setDelegate(tp)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
globalTracer.Store(tracerProviderHolder{tp: tp})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextMapPropagator is the internal implementation for global.TextMapPropagator.
|
||||||
|
func TextMapPropagator() propagation.TextMapPropagator {
|
||||||
|
return globalPropagators.Load().(propagatorsHolder).tm
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTextMapPropagator is the internal implementation for global.SetTextMapPropagator.
|
||||||
|
func SetTextMapPropagator(p propagation.TextMapPropagator) {
|
||||||
|
// For the textMapPropagator already returned by TextMapPropagator
|
||||||
|
// delegate to p.
|
||||||
|
delegateTextMapPropagatorOnce.Do(func() {
|
||||||
|
if current := TextMapPropagator(); current == p {
|
||||||
|
// Setting the provider to the prior default is nonsense, panic.
|
||||||
|
// Panic is acceptable because we are likely still early in the
|
||||||
|
// process lifetime.
|
||||||
|
panic("invalid TextMapPropagator, the global instance cannot be reinstalled")
|
||||||
|
} else if def, ok := current.(*textMapPropagator); ok {
|
||||||
|
def.SetDelegate(p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Return p when subsequent calls to TextMapPropagator are made.
|
||||||
|
globalPropagators.Store(propagatorsHolder{tm: p})
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultTracerValue() *atomic.Value {
|
||||||
|
v := &atomic.Value{}
|
||||||
|
v.Store(tracerProviderHolder{tp: &tracerProvider{}})
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultPropagatorsValue() *atomic.Value {
|
||||||
|
v := &atomic.Value{}
|
||||||
|
v.Store(propagatorsHolder{tm: newTextMapPropagator()})
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetForTest restores the initial global state, for testing purposes.
|
||||||
|
func ResetForTest() {
|
||||||
|
globalTracer = defaultTracerValue()
|
||||||
|
globalPropagators = defaultPropagatorsValue()
|
||||||
|
delegateTraceOnce = sync.Once{}
|
||||||
|
delegateTextMapPropagatorOnce = sync.Once{}
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package global // import "go.opentelemetry.io/otel/internal/global"
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file contains the forwarding implementation of the TracerProvider used as
|
||||||
|
the default global instance. Prior to initialization of an SDK, Tracers
|
||||||
|
returned by the global TracerProvider will provide no-op functionality. This
|
||||||
|
means that all Span created prior to initialization are no-op Spans.
|
||||||
|
|
||||||
|
Once an SDK has been initialized, all provided no-op Tracers are swapped for
|
||||||
|
Tracers provided by the SDK defined TracerProvider. However, any Span started
|
||||||
|
prior to this initialization does not change its behavior. Meaning, the Span
|
||||||
|
remains a no-op Span.
|
||||||
|
|
||||||
|
The implementation to track and swap Tracers locks all new Tracer creation
|
||||||
|
until the swap is complete. This assumes that this operation is not
|
||||||
|
performance-critical. If that assumption is incorrect, be sure to configure an
|
||||||
|
SDK prior to any Tracer creation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tracerProvider is a placeholder for a configured SDK TracerProvider.
|
||||||
|
//
|
||||||
|
// All TracerProvider functionality is forwarded to a delegate once
|
||||||
|
// configured.
|
||||||
|
type tracerProvider struct {
|
||||||
|
mtx sync.Mutex
|
||||||
|
tracers map[il]*tracer
|
||||||
|
delegate trace.TracerProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time guarantee that tracerProvider implements the TracerProvider
|
||||||
|
// interface.
|
||||||
|
var _ trace.TracerProvider = &tracerProvider{}
|
||||||
|
|
||||||
|
// setDelegate configures p to delegate all TracerProvider functionality to
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// All Tracers provided prior to this function call are switched out to be
|
||||||
|
// Tracers provided by provider.
|
||||||
|
//
|
||||||
|
// It is guaranteed by the caller that this happens only once.
|
||||||
|
func (p *tracerProvider) setDelegate(provider trace.TracerProvider) {
|
||||||
|
p.mtx.Lock()
|
||||||
|
defer p.mtx.Unlock()
|
||||||
|
|
||||||
|
p.delegate = provider
|
||||||
|
|
||||||
|
if len(p.tracers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range p.tracers {
|
||||||
|
t.setDelegate(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.tracers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracer implements TracerProvider.
|
||||||
|
func (p *tracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
|
||||||
|
p.mtx.Lock()
|
||||||
|
defer p.mtx.Unlock()
|
||||||
|
|
||||||
|
if p.delegate != nil {
|
||||||
|
return p.delegate.Tracer(name, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this moment it is guaranteed that no sdk is installed, save the tracer in the tracers map.
|
||||||
|
|
||||||
|
c := trace.NewTracerConfig(opts...)
|
||||||
|
key := il{
|
||||||
|
name: name,
|
||||||
|
version: c.InstrumentationVersion(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.tracers == nil {
|
||||||
|
p.tracers = make(map[il]*tracer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := p.tracers[key]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &tracer{name: name, opts: opts, provider: p}
|
||||||
|
p.tracers[key] = t
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
type il struct {
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracer is a placeholder for a trace.Tracer.
|
||||||
|
//
|
||||||
|
// All Tracer functionality is forwarded to a delegate once configured.
|
||||||
|
// Otherwise, all functionality is forwarded to a NoopTracer.
|
||||||
|
type tracer struct {
|
||||||
|
name string
|
||||||
|
opts []trace.TracerOption
|
||||||
|
provider *tracerProvider
|
||||||
|
|
||||||
|
delegate atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time guarantee that tracer implements the trace.Tracer interface.
|
||||||
|
var _ trace.Tracer = &tracer{}
|
||||||
|
|
||||||
|
// setDelegate configures t to delegate all Tracer functionality to Tracers
|
||||||
|
// created by provider.
|
||||||
|
//
|
||||||
|
// All subsequent calls to the Tracer methods will be passed to the delegate.
|
||||||
|
//
|
||||||
|
// It is guaranteed by the caller that this happens only once.
|
||||||
|
func (t *tracer) setDelegate(provider trace.TracerProvider) {
|
||||||
|
t.delegate.Store(provider.Tracer(t.name, t.opts...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start implements trace.Tracer by forwarding the call to t.delegate if
|
||||||
|
// set, otherwise it forwards the call to a NoopTracer.
|
||||||
|
func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
delegate := t.delegate.Load()
|
||||||
|
if delegate != nil {
|
||||||
|
return delegate.(trace.Tracer).Start(ctx, name, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := nonRecordingSpan{sc: trace.SpanContextFromContext(ctx), tracer: t}
|
||||||
|
ctx = trace.ContextWithSpan(ctx, s)
|
||||||
|
return ctx, s
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonRecordingSpan is a minimal implementation of a Span that wraps a
|
||||||
|
// SpanContext. It performs no operations other than to return the wrapped
|
||||||
|
// SpanContext.
|
||||||
|
type nonRecordingSpan struct {
|
||||||
|
sc trace.SpanContext
|
||||||
|
tracer *tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ trace.Span = nonRecordingSpan{}
|
||||||
|
|
||||||
|
// SpanContext returns the wrapped SpanContext.
|
||||||
|
func (s nonRecordingSpan) SpanContext() trace.SpanContext { return s.sc }
|
||||||
|
|
||||||
|
// IsRecording always returns false.
|
||||||
|
func (nonRecordingSpan) IsRecording() bool { return false }
|
||||||
|
|
||||||
|
// SetStatus does nothing.
|
||||||
|
func (nonRecordingSpan) SetStatus(codes.Code, string) {}
|
||||||
|
|
||||||
|
// SetError does nothing.
|
||||||
|
func (nonRecordingSpan) SetError(bool) {}
|
||||||
|
|
||||||
|
// SetAttributes does nothing.
|
||||||
|
func (nonRecordingSpan) SetAttributes(...attribute.KeyValue) {}
|
||||||
|
|
||||||
|
// End does nothing.
|
||||||
|
func (nonRecordingSpan) End(...trace.SpanEndOption) {}
|
||||||
|
|
||||||
|
// RecordError does nothing.
|
||||||
|
func (nonRecordingSpan) RecordError(error, ...trace.EventOption) {}
|
||||||
|
|
||||||
|
// AddEvent does nothing.
|
||||||
|
func (nonRecordingSpan) AddEvent(string, ...trace.EventOption) {}
|
||||||
|
|
||||||
|
// SetName does nothing.
|
||||||
|
func (nonRecordingSpan) SetName(string) {}
|
||||||
|
|
||||||
|
func (s nonRecordingSpan) TracerProvider() trace.TracerProvider { return s.tracer.provider }
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,287 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package global // import "go.opentelemetry.io/otel/internal/metric/global"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/internal/metric/registry"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"go.opentelemetry.io/otel/metric/number"
|
||||||
|
"go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file contains the forwarding implementation of MeterProvider used as
|
||||||
|
// the default global instance. Metric events using instruments provided by
|
||||||
|
// this implementation are no-ops until the first Meter implementation is set
|
||||||
|
// as the global provider.
|
||||||
|
//
|
||||||
|
// The implementation here uses Mutexes to maintain a list of active Meters in
|
||||||
|
// the MeterProvider and Instruments in each Meter, under the assumption that
|
||||||
|
// these interfaces are not performance-critical.
|
||||||
|
//
|
||||||
|
// We have the invariant that setDelegate() will be called before a new
|
||||||
|
// MeterProvider implementation is registered as the global provider. Mutexes
|
||||||
|
// in the MeterProvider and Meters ensure that each instrument has a delegate
|
||||||
|
// before the global provider is set.
|
||||||
|
//
|
||||||
|
// Metric uniqueness checking is implemented by calling the exported
|
||||||
|
// methods of the api/metric/registry package.
|
||||||
|
|
||||||
|
type meterKey struct {
|
||||||
|
InstrumentationName string
|
||||||
|
InstrumentationVersion string
|
||||||
|
SchemaURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type meterProvider struct {
|
||||||
|
delegate metric.MeterProvider
|
||||||
|
|
||||||
|
// lock protects `delegate` and `meters`.
|
||||||
|
lock sync.Mutex
|
||||||
|
|
||||||
|
// meters maintains a unique entry for every named Meter
|
||||||
|
// that has been registered through the global instance.
|
||||||
|
meters map[meterKey]*meterEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type meterImpl struct {
|
||||||
|
delegate unsafe.Pointer // (*metric.MeterImpl)
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
syncInsts []*syncImpl
|
||||||
|
asyncInsts []*asyncImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
type meterEntry struct {
|
||||||
|
unique sdkapi.MeterImpl
|
||||||
|
impl meterImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
type instrument struct {
|
||||||
|
descriptor sdkapi.Descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
type syncImpl struct {
|
||||||
|
delegate unsafe.Pointer // (*sdkapi.SyncImpl)
|
||||||
|
|
||||||
|
instrument
|
||||||
|
}
|
||||||
|
|
||||||
|
type asyncImpl struct {
|
||||||
|
delegate unsafe.Pointer // (*sdkapi.AsyncImpl)
|
||||||
|
|
||||||
|
instrument
|
||||||
|
|
||||||
|
runner sdkapi.AsyncRunner
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ metric.MeterProvider = &meterProvider{}
|
||||||
|
var _ sdkapi.MeterImpl = &meterImpl{}
|
||||||
|
var _ sdkapi.InstrumentImpl = &syncImpl{}
|
||||||
|
var _ sdkapi.AsyncImpl = &asyncImpl{}
|
||||||
|
|
||||||
|
func (inst *instrument) Descriptor() sdkapi.Descriptor {
|
||||||
|
return inst.descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeterProvider interface and delegation
|
||||||
|
|
||||||
|
func newMeterProvider() *meterProvider {
|
||||||
|
return &meterProvider{
|
||||||
|
meters: map[meterKey]*meterEntry{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *meterProvider) setDelegate(provider metric.MeterProvider) {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
p.delegate = provider
|
||||||
|
for key, entry := range p.meters {
|
||||||
|
entry.impl.setDelegate(key, provider)
|
||||||
|
}
|
||||||
|
p.meters = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *meterProvider) Meter(instrumentationName string, opts ...metric.MeterOption) metric.Meter {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
if p.delegate != nil {
|
||||||
|
return p.delegate.Meter(instrumentationName, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := metric.NewMeterConfig(opts...)
|
||||||
|
key := meterKey{
|
||||||
|
InstrumentationName: instrumentationName,
|
||||||
|
InstrumentationVersion: cfg.InstrumentationVersion(),
|
||||||
|
SchemaURL: cfg.SchemaURL(),
|
||||||
|
}
|
||||||
|
entry, ok := p.meters[key]
|
||||||
|
if !ok {
|
||||||
|
entry = &meterEntry{}
|
||||||
|
// Note: This code implements its own MeterProvider
|
||||||
|
// name-uniqueness logic because there is
|
||||||
|
// synchronization required at the moment of
|
||||||
|
// delegation. We use the same instrument-uniqueness
|
||||||
|
// checking the real SDK uses here:
|
||||||
|
entry.unique = registry.NewUniqueInstrumentMeterImpl(&entry.impl)
|
||||||
|
p.meters[key] = entry
|
||||||
|
}
|
||||||
|
return metric.WrapMeterImpl(entry.unique)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meter interface and delegation
|
||||||
|
|
||||||
|
func (m *meterImpl) setDelegate(key meterKey, provider metric.MeterProvider) {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
d := new(sdkapi.MeterImpl)
|
||||||
|
*d = provider.Meter(
|
||||||
|
key.InstrumentationName,
|
||||||
|
metric.WithInstrumentationVersion(key.InstrumentationVersion),
|
||||||
|
metric.WithSchemaURL(key.SchemaURL),
|
||||||
|
).MeterImpl()
|
||||||
|
m.delegate = unsafe.Pointer(d)
|
||||||
|
|
||||||
|
for _, inst := range m.syncInsts {
|
||||||
|
inst.setDelegate(*d)
|
||||||
|
}
|
||||||
|
m.syncInsts = nil
|
||||||
|
for _, obs := range m.asyncInsts {
|
||||||
|
obs.setDelegate(*d)
|
||||||
|
}
|
||||||
|
m.asyncInsts = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *meterImpl) NewSyncInstrument(desc sdkapi.Descriptor) (sdkapi.SyncImpl, error) {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
if meterPtr := (*sdkapi.MeterImpl)(atomic.LoadPointer(&m.delegate)); meterPtr != nil {
|
||||||
|
return (*meterPtr).NewSyncInstrument(desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
inst := &syncImpl{
|
||||||
|
instrument: instrument{
|
||||||
|
descriptor: desc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
m.syncInsts = append(m.syncInsts, inst)
|
||||||
|
return inst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous delegation
|
||||||
|
|
||||||
|
func (inst *syncImpl) setDelegate(d sdkapi.MeterImpl) {
|
||||||
|
implPtr := new(sdkapi.SyncImpl)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
*implPtr, err = d.NewSyncInstrument(inst.descriptor)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// TODO: There is no standard way to deliver this error to the user.
|
||||||
|
// See https://github.com/open-telemetry/opentelemetry-go/issues/514
|
||||||
|
// Note that the default SDK will not generate any errors yet, this is
|
||||||
|
// only for added safety.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StorePointer(&inst.delegate, unsafe.Pointer(implPtr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inst *syncImpl) Implementation() interface{} {
|
||||||
|
if implPtr := (*sdkapi.SyncImpl)(atomic.LoadPointer(&inst.delegate)); implPtr != nil {
|
||||||
|
return (*implPtr).Implementation()
|
||||||
|
}
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async delegation
|
||||||
|
|
||||||
|
func (m *meterImpl) NewAsyncInstrument(
|
||||||
|
desc sdkapi.Descriptor,
|
||||||
|
runner sdkapi.AsyncRunner,
|
||||||
|
) (sdkapi.AsyncImpl, error) {
|
||||||
|
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
if meterPtr := (*sdkapi.MeterImpl)(atomic.LoadPointer(&m.delegate)); meterPtr != nil {
|
||||||
|
return (*meterPtr).NewAsyncInstrument(desc, runner)
|
||||||
|
}
|
||||||
|
|
||||||
|
inst := &asyncImpl{
|
||||||
|
instrument: instrument{
|
||||||
|
descriptor: desc,
|
||||||
|
},
|
||||||
|
runner: runner,
|
||||||
|
}
|
||||||
|
m.asyncInsts = append(m.asyncInsts, inst)
|
||||||
|
return inst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obs *asyncImpl) Implementation() interface{} {
|
||||||
|
if implPtr := (*sdkapi.AsyncImpl)(atomic.LoadPointer(&obs.delegate)); implPtr != nil {
|
||||||
|
return (*implPtr).Implementation()
|
||||||
|
}
|
||||||
|
return obs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obs *asyncImpl) setDelegate(d sdkapi.MeterImpl) {
|
||||||
|
implPtr := new(sdkapi.AsyncImpl)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
*implPtr, err = d.NewAsyncInstrument(obs.descriptor, obs.runner)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// TODO: There is no standard way to deliver this error to the user.
|
||||||
|
// See https://github.com/open-telemetry/opentelemetry-go/issues/514
|
||||||
|
// Note that the default SDK will not generate any errors yet, this is
|
||||||
|
// only for added safety.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StorePointer(&obs.delegate, unsafe.Pointer(implPtr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metric updates
|
||||||
|
|
||||||
|
func (m *meterImpl) RecordBatch(ctx context.Context, labels []attribute.KeyValue, measurements ...sdkapi.Measurement) {
|
||||||
|
if delegatePtr := (*sdkapi.MeterImpl)(atomic.LoadPointer(&m.delegate)); delegatePtr != nil {
|
||||||
|
(*delegatePtr).RecordBatch(ctx, labels, measurements...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inst *syncImpl) RecordOne(ctx context.Context, number number.Number, labels []attribute.KeyValue) {
|
||||||
|
if instPtr := (*sdkapi.SyncImpl)(atomic.LoadPointer(&inst.delegate)); instPtr != nil {
|
||||||
|
(*instPtr).RecordOne(ctx, number, labels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AtomicFieldOffsets() map[string]uintptr {
|
||||||
|
return map[string]uintptr{
|
||||||
|
"meterProvider.delegate": unsafe.Offsetof(meterProvider{}.delegate),
|
||||||
|
"meterImpl.delegate": unsafe.Offsetof(meterImpl{}.delegate),
|
||||||
|
"syncImpl.delegate": unsafe.Offsetof(syncImpl{}.delegate),
|
||||||
|
"asyncImpl.delegate": unsafe.Offsetof(asyncImpl{}.delegate),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package global // import "go.opentelemetry.io/otel/internal/metric/global"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
)
|
||||||
|
|
||||||
|
type meterProviderHolder struct {
|
||||||
|
mp metric.MeterProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalMeter = defaultMeterValue()
|
||||||
|
|
||||||
|
delegateMeterOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// MeterProvider is the internal implementation for global.MeterProvider.
|
||||||
|
func MeterProvider() metric.MeterProvider {
|
||||||
|
return globalMeter.Load().(meterProviderHolder).mp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMeterProvider is the internal implementation for global.SetMeterProvider.
|
||||||
|
func SetMeterProvider(mp metric.MeterProvider) {
|
||||||
|
delegateMeterOnce.Do(func() {
|
||||||
|
current := MeterProvider()
|
||||||
|
|
||||||
|
if current == mp {
|
||||||
|
// Setting the provider to the prior default is nonsense, panic.
|
||||||
|
// Panic is acceptable because we are likely still early in the
|
||||||
|
// process lifetime.
|
||||||
|
panic("invalid MeterProvider, the global instance cannot be reinstalled")
|
||||||
|
} else if def, ok := current.(*meterProvider); ok {
|
||||||
|
def.setDelegate(mp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
globalMeter.Store(meterProviderHolder{mp: mp})
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultMeterValue() *atomic.Value {
|
||||||
|
v := &atomic.Value{}
|
||||||
|
v.Store(meterProviderHolder{mp: newMeterProvider()})
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetForTest restores the initial global state, for testing purposes.
|
||||||
|
func ResetForTest() {
|
||||||
|
globalMeter = defaultMeterValue()
|
||||||
|
delegateMeterOnce = sync.Once{}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package registry provides a non-standalone implementation of
|
||||||
|
MeterProvider that adds uniqueness checking for instrument descriptors
|
||||||
|
on top of other MeterProvider it wraps.
|
||||||
|
|
||||||
|
This package is currently in a pre-GA phase. Backwards incompatible changes
|
||||||
|
may be introduced in subsequent minor version releases as we work to track the
|
||||||
|
evolving OpenTelemetry specification and user feedback.
|
||||||
|
*/
|
||||||
|
package registry // import "go.opentelemetry.io/otel/internal/metric/registry"
|
139
vendor/go.opentelemetry.io/otel/internal/metric/registry/registry.go
generated
vendored
Normal file
139
vendor/go.opentelemetry.io/otel/internal/metric/registry/registry.go
generated
vendored
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package registry // import "go.opentelemetry.io/otel/internal/metric/registry"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UniqueInstrumentMeterImpl implements the metric.MeterImpl interface, adding
|
||||||
|
// uniqueness checking for instrument descriptors.
|
||||||
|
type UniqueInstrumentMeterImpl struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
impl sdkapi.MeterImpl
|
||||||
|
state map[string]sdkapi.InstrumentImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ sdkapi.MeterImpl = (*UniqueInstrumentMeterImpl)(nil)
|
||||||
|
|
||||||
|
// ErrMetricKindMismatch is the standard error for mismatched metric
|
||||||
|
// instrument definitions.
|
||||||
|
var ErrMetricKindMismatch = fmt.Errorf(
|
||||||
|
"a metric was already registered by this name with another kind or number type")
|
||||||
|
|
||||||
|
// NewUniqueInstrumentMeterImpl returns a wrapped metric.MeterImpl
|
||||||
|
// with the addition of instrument name uniqueness checking.
|
||||||
|
func NewUniqueInstrumentMeterImpl(impl sdkapi.MeterImpl) *UniqueInstrumentMeterImpl {
|
||||||
|
return &UniqueInstrumentMeterImpl{
|
||||||
|
impl: impl,
|
||||||
|
state: map[string]sdkapi.InstrumentImpl{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeterImpl gives the caller access to the underlying MeterImpl
|
||||||
|
// used by this UniqueInstrumentMeterImpl.
|
||||||
|
func (u *UniqueInstrumentMeterImpl) MeterImpl() sdkapi.MeterImpl {
|
||||||
|
return u.impl
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordBatch implements sdkapi.MeterImpl.
|
||||||
|
func (u *UniqueInstrumentMeterImpl) RecordBatch(ctx context.Context, labels []attribute.KeyValue, ms ...sdkapi.Measurement) {
|
||||||
|
u.impl.RecordBatch(ctx, labels, ms...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetricKindMismatchError formats an error that describes a
|
||||||
|
// mismatched metric instrument definition.
|
||||||
|
func NewMetricKindMismatchError(desc sdkapi.Descriptor) error {
|
||||||
|
return fmt.Errorf("metric %s registered as %s %s: %w",
|
||||||
|
desc.Name(),
|
||||||
|
desc.NumberKind(),
|
||||||
|
desc.InstrumentKind(),
|
||||||
|
ErrMetricKindMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compatible determines whether two sdkapi.Descriptors are considered
|
||||||
|
// the same for the purpose of uniqueness checking.
|
||||||
|
func Compatible(candidate, existing sdkapi.Descriptor) bool {
|
||||||
|
return candidate.InstrumentKind() == existing.InstrumentKind() &&
|
||||||
|
candidate.NumberKind() == existing.NumberKind()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkUniqueness returns an ErrMetricKindMismatch error if there is
|
||||||
|
// a conflict between a descriptor that was already registered and the
|
||||||
|
// `descriptor` argument. If there is an existing compatible
|
||||||
|
// registration, this returns the already-registered instrument. If
|
||||||
|
// there is no conflict and no prior registration, returns (nil, nil).
|
||||||
|
func (u *UniqueInstrumentMeterImpl) checkUniqueness(descriptor sdkapi.Descriptor) (sdkapi.InstrumentImpl, error) {
|
||||||
|
impl, ok := u.state[descriptor.Name()]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Compatible(descriptor, impl.Descriptor()) {
|
||||||
|
return nil, NewMetricKindMismatchError(impl.Descriptor())
|
||||||
|
}
|
||||||
|
|
||||||
|
return impl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSyncInstrument implements sdkapi.MeterImpl.
|
||||||
|
func (u *UniqueInstrumentMeterImpl) NewSyncInstrument(descriptor sdkapi.Descriptor) (sdkapi.SyncImpl, error) {
|
||||||
|
u.lock.Lock()
|
||||||
|
defer u.lock.Unlock()
|
||||||
|
|
||||||
|
impl, err := u.checkUniqueness(descriptor)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if impl != nil {
|
||||||
|
return impl.(sdkapi.SyncImpl), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syncInst, err := u.impl.NewSyncInstrument(descriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.state[descriptor.Name()] = syncInst
|
||||||
|
return syncInst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncInstrument implements sdkapi.MeterImpl.
|
||||||
|
func (u *UniqueInstrumentMeterImpl) NewAsyncInstrument(
|
||||||
|
descriptor sdkapi.Descriptor,
|
||||||
|
runner sdkapi.AsyncRunner,
|
||||||
|
) (sdkapi.AsyncImpl, error) {
|
||||||
|
u.lock.Lock()
|
||||||
|
defer u.lock.Unlock()
|
||||||
|
|
||||||
|
impl, err := u.checkUniqueness(descriptor)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if impl != nil {
|
||||||
|
return impl.(sdkapi.AsyncImpl), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncInst, err := u.impl.NewAsyncInstrument(descriptor, runner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.state[descriptor.Name()] = asyncInst
|
||||||
|
return asyncInst, nil
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package internal // import "go.opentelemetry.io/otel/internal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BoolToRaw(b bool) uint64 {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func RawToBool(r uint64) bool {
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int64ToRaw(i int64) uint64 {
|
||||||
|
return uint64(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RawToInt64(r uint64) int64 {
|
||||||
|
return int64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Float64ToRaw(f float64) uint64 {
|
||||||
|
return math.Float64bits(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RawToFloat64(r uint64) float64 {
|
||||||
|
return math.Float64frombits(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RawPtrToFloat64Ptr(r *uint64) *float64 {
|
||||||
|
return (*float64)(unsafe.Pointer(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RawPtrToInt64Ptr(r *uint64) *int64 {
|
||||||
|
return (*int64)(unsafe.Pointer(r))
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package otel // import "go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-logr/logr"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/internal/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetLogger configures the logger used internally to opentelemetry.
|
||||||
|
func SetLogger(logger logr.Logger) {
|
||||||
|
global.SetLogger(logger)
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package metric // import "go.opentelemetry.io/otel/metric"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel/metric/unit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstrumentConfig contains options for metric instrument descriptors.
|
||||||
|
type InstrumentConfig struct {
|
||||||
|
description string
|
||||||
|
unit unit.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description describes the instrument in human-readable terms.
|
||||||
|
func (cfg *InstrumentConfig) Description() string {
|
||||||
|
return cfg.description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit describes the measurement unit for a instrument.
|
||||||
|
func (cfg *InstrumentConfig) Unit() unit.Unit {
|
||||||
|
return cfg.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentOption is an interface for applying metric instrument options.
|
||||||
|
type InstrumentOption interface {
|
||||||
|
// ApplyMeter is used to set a InstrumentOption value of a
|
||||||
|
// InstrumentConfig.
|
||||||
|
applyInstrument(InstrumentConfig) InstrumentConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInstrumentConfig creates a new InstrumentConfig
|
||||||
|
// and applies all the given options.
|
||||||
|
func NewInstrumentConfig(opts ...InstrumentOption) InstrumentConfig {
|
||||||
|
var config InstrumentConfig
|
||||||
|
for _, o := range opts {
|
||||||
|
config = o.applyInstrument(config)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type instrumentOptionFunc func(InstrumentConfig) InstrumentConfig
|
||||||
|
|
||||||
|
func (fn instrumentOptionFunc) applyInstrument(cfg InstrumentConfig) InstrumentConfig {
|
||||||
|
return fn(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDescription applies provided description.
|
||||||
|
func WithDescription(desc string) InstrumentOption {
|
||||||
|
return instrumentOptionFunc(func(cfg InstrumentConfig) InstrumentConfig {
|
||||||
|
cfg.description = desc
|
||||||
|
return cfg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUnit applies provided unit.
|
||||||
|
func WithUnit(unit unit.Unit) InstrumentOption {
|
||||||
|
return instrumentOptionFunc(func(cfg InstrumentConfig) InstrumentConfig {
|
||||||
|
cfg.unit = unit
|
||||||
|
return cfg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeterConfig contains options for Meters.
|
||||||
|
type MeterConfig struct {
|
||||||
|
instrumentationVersion string
|
||||||
|
schemaURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentationVersion is the version of the library providing instrumentation.
|
||||||
|
func (cfg *MeterConfig) InstrumentationVersion() string {
|
||||||
|
return cfg.instrumentationVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchemaURL is the schema_url of the library providing instrumentation.
|
||||||
|
func (cfg *MeterConfig) SchemaURL() string {
|
||||||
|
return cfg.schemaURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeterOption is an interface for applying Meter options.
|
||||||
|
type MeterOption interface {
|
||||||
|
// ApplyMeter is used to set a MeterOption value of a MeterConfig.
|
||||||
|
applyMeter(MeterConfig) MeterConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMeterConfig creates a new MeterConfig and applies
|
||||||
|
// all the given options.
|
||||||
|
func NewMeterConfig(opts ...MeterOption) MeterConfig {
|
||||||
|
var config MeterConfig
|
||||||
|
for _, o := range opts {
|
||||||
|
config = o.applyMeter(config)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type meterOptionFunc func(MeterConfig) MeterConfig
|
||||||
|
|
||||||
|
func (fn meterOptionFunc) applyMeter(cfg MeterConfig) MeterConfig {
|
||||||
|
return fn(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInstrumentationVersion sets the instrumentation version.
|
||||||
|
func WithInstrumentationVersion(version string) MeterOption {
|
||||||
|
return meterOptionFunc(func(config MeterConfig) MeterConfig {
|
||||||
|
config.instrumentationVersion = version
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSchemaURL sets the schema URL.
|
||||||
|
func WithSchemaURL(schemaURL string) MeterOption {
|
||||||
|
return meterOptionFunc(func(config MeterConfig) MeterConfig {
|
||||||
|
config.schemaURL = schemaURL
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package metric provides an implementation of the metrics part of the
|
||||||
|
OpenTelemetry API.
|
||||||
|
|
||||||
|
This package is currently in a pre-GA phase. Backwards incompatible changes
|
||||||
|
may be introduced in subsequent minor version releases as we work to track the
|
||||||
|
evolving OpenTelemetry specification and user feedback.
|
||||||
|
|
||||||
|
Measurements can be made about an operation being performed or the state of a
|
||||||
|
system in general. These measurements can be crucial to the reliable operation
|
||||||
|
of code and provide valuable insights about the inner workings of a system.
|
||||||
|
|
||||||
|
Measurements are made using instruments provided by this package. The type of
|
||||||
|
instrument used will depend on the type of measurement being made and of what
|
||||||
|
part of a system is being measured.
|
||||||
|
|
||||||
|
Instruments are categorized as Synchronous or Asynchronous and independently
|
||||||
|
as Adding or Grouping. Synchronous instruments are called by the user with a
|
||||||
|
Context. Asynchronous instruments are called by the SDK during collection.
|
||||||
|
Adding instruments are semantically intended for capturing a sum. Grouping
|
||||||
|
instruments are intended for capturing a distribution.
|
||||||
|
|
||||||
|
Adding instruments may be monotonic, in which case they are non-decreasing
|
||||||
|
and naturally define a rate.
|
||||||
|
|
||||||
|
The synchronous instrument names are:
|
||||||
|
|
||||||
|
Counter: adding, monotonic
|
||||||
|
UpDownCounter: adding
|
||||||
|
Histogram: grouping
|
||||||
|
|
||||||
|
and the asynchronous instruments are:
|
||||||
|
|
||||||
|
CounterObserver: adding, monotonic
|
||||||
|
UpDownCounterObserver: adding
|
||||||
|
GaugeObserver: grouping
|
||||||
|
|
||||||
|
All instruments are provided with support for either float64 or int64 input
|
||||||
|
values.
|
||||||
|
|
||||||
|
An instrument is created using a Meter. Additionally, a Meter is used to
|
||||||
|
record batches of synchronous measurements or asynchronous observations. A
|
||||||
|
Meter is obtained using a MeterProvider. A Meter, like a Tracer, is unique to
|
||||||
|
the instrumentation it instruments and must be named and versioned when
|
||||||
|
created with a MeterProvider with the name and version of the instrumentation
|
||||||
|
library.
|
||||||
|
|
||||||
|
Instrumentation should be designed to accept a MeterProvider from which it can
|
||||||
|
create its own unique Meter. Alternatively, the registered global
|
||||||
|
MeterProvider from the go.opentelemetry.io/otel package can be used as a
|
||||||
|
default.
|
||||||
|
*/
|
||||||
|
package metric // import "go.opentelemetry.io/otel/metric"
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package global // import "go.opentelemetry.io/otel/metric/global"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel/internal/metric/global"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Meter creates an implementation of the Meter interface from the global
|
||||||
|
// MeterProvider. The instrumentationName must be the name of the library
|
||||||
|
// providing instrumentation. This name may be the same as the instrumented
|
||||||
|
// code only if that code provides built-in instrumentation. If the
|
||||||
|
// instrumentationName is empty, then a implementation defined default name
|
||||||
|
// will be used instead.
|
||||||
|
//
|
||||||
|
// This is short for MeterProvider().Meter(name)
|
||||||
|
func Meter(instrumentationName string, opts ...metric.MeterOption) metric.Meter {
|
||||||
|
return GetMeterProvider().Meter(instrumentationName, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMeterProvider returns the registered global meter provider. If
|
||||||
|
// none is registered then a default meter provider is returned that
|
||||||
|
// forwards the Meter interface to the first registered Meter.
|
||||||
|
//
|
||||||
|
// Use the meter provider to create a named meter. E.g.
|
||||||
|
// meter := global.MeterProvider().Meter("example.com/foo")
|
||||||
|
// or
|
||||||
|
// meter := global.Meter("example.com/foo")
|
||||||
|
func GetMeterProvider() metric.MeterProvider {
|
||||||
|
return global.MeterProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMeterProvider registers `mp` as the global meter provider.
|
||||||
|
func SetMeterProvider(mp metric.MeterProvider) {
|
||||||
|
global.SetMeterProvider(mp)
|
||||||
|
}
|
|
@ -0,0 +1,538 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package metric // import "go.opentelemetry.io/otel/metric"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric/number"
|
||||||
|
"go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MeterProvider supports named Meter instances.
|
||||||
|
type MeterProvider interface {
|
||||||
|
// Meter creates an implementation of the Meter interface.
|
||||||
|
// The instrumentationName must be the name of the library providing
|
||||||
|
// instrumentation. This name may be the same as the instrumented code
|
||||||
|
// only if that code provides built-in instrumentation. If the
|
||||||
|
// instrumentationName is empty, then a implementation defined default
|
||||||
|
// name will be used instead.
|
||||||
|
Meter(instrumentationName string, opts ...MeterOption) Meter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meter is the creator of metric instruments.
|
||||||
|
//
|
||||||
|
// An uninitialized Meter is a no-op implementation.
|
||||||
|
type Meter struct {
|
||||||
|
impl sdkapi.MeterImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapMeterImpl constructs a `Meter` implementation from a
|
||||||
|
// `MeterImpl` implementation.
|
||||||
|
func WrapMeterImpl(impl sdkapi.MeterImpl) Meter {
|
||||||
|
return Meter{
|
||||||
|
impl: impl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement is used for reporting a synchronous batch of metric
|
||||||
|
// values. Instances of this type should be created by synchronous
|
||||||
|
// instruments (e.g., Int64Counter.Measurement()).
|
||||||
|
//
|
||||||
|
// Note: This is an alias because it is a first-class member of the
|
||||||
|
// API but is also part of the lower-level sdkapi interface.
|
||||||
|
type Measurement = sdkapi.Measurement
|
||||||
|
|
||||||
|
// Observation is used for reporting an asynchronous batch of metric
|
||||||
|
// values. Instances of this type should be created by asynchronous
|
||||||
|
// instruments (e.g., Int64GaugeObserver.Observation()).
|
||||||
|
//
|
||||||
|
// Note: This is an alias because it is a first-class member of the
|
||||||
|
// API but is also part of the lower-level sdkapi interface.
|
||||||
|
type Observation = sdkapi.Observation
|
||||||
|
|
||||||
|
// RecordBatch atomically records a batch of measurements.
|
||||||
|
func (m Meter) RecordBatch(ctx context.Context, ls []attribute.KeyValue, ms ...Measurement) {
|
||||||
|
if m.impl == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.impl.RecordBatch(ctx, ls, ms...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatchObserver creates a new BatchObserver that supports
|
||||||
|
// making batches of observations for multiple instruments.
|
||||||
|
func (m Meter) NewBatchObserver(callback BatchObserverFunc) BatchObserver {
|
||||||
|
return BatchObserver{
|
||||||
|
meter: m,
|
||||||
|
runner: newBatchAsyncRunner(callback),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Counter creates a new integer Counter instrument with the
|
||||||
|
// given name, customized with options. May return an error if the
|
||||||
|
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||||
|
// duplicate registration).
|
||||||
|
func (m Meter) NewInt64Counter(name string, options ...InstrumentOption) (Int64Counter, error) {
|
||||||
|
return wrapInt64CounterInstrument(
|
||||||
|
m.newSync(name, sdkapi.CounterInstrumentKind, number.Int64Kind, options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Counter creates a new floating point Counter with the
|
||||||
|
// given name, customized with options. May return an error if the
|
||||||
|
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||||
|
// duplicate registration).
|
||||||
|
func (m Meter) NewFloat64Counter(name string, options ...InstrumentOption) (Float64Counter, error) {
|
||||||
|
return wrapFloat64CounterInstrument(
|
||||||
|
m.newSync(name, sdkapi.CounterInstrumentKind, number.Float64Kind, options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64UpDownCounter creates a new integer UpDownCounter instrument with the
|
||||||
|
// given name, customized with options. May return an error if the
|
||||||
|
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||||
|
// duplicate registration).
|
||||||
|
func (m Meter) NewInt64UpDownCounter(name string, options ...InstrumentOption) (Int64UpDownCounter, error) {
|
||||||
|
return wrapInt64UpDownCounterInstrument(
|
||||||
|
m.newSync(name, sdkapi.UpDownCounterInstrumentKind, number.Int64Kind, options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64UpDownCounter creates a new floating point UpDownCounter with the
|
||||||
|
// given name, customized with options. May return an error if the
|
||||||
|
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||||
|
// duplicate registration).
|
||||||
|
func (m Meter) NewFloat64UpDownCounter(name string, options ...InstrumentOption) (Float64UpDownCounter, error) {
|
||||||
|
return wrapFloat64UpDownCounterInstrument(
|
||||||
|
m.newSync(name, sdkapi.UpDownCounterInstrumentKind, number.Float64Kind, options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Histogram creates a new integer Histogram instrument with the
|
||||||
|
// given name, customized with options. May return an error if the
|
||||||
|
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||||
|
// duplicate registration).
|
||||||
|
func (m Meter) NewInt64Histogram(name string, opts ...InstrumentOption) (Int64Histogram, error) {
|
||||||
|
return wrapInt64HistogramInstrument(
|
||||||
|
m.newSync(name, sdkapi.HistogramInstrumentKind, number.Int64Kind, opts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Histogram creates a new floating point Histogram with the
|
||||||
|
// given name, customized with options. May return an error if the
|
||||||
|
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||||
|
// duplicate registration).
|
||||||
|
func (m Meter) NewFloat64Histogram(name string, opts ...InstrumentOption) (Float64Histogram, error) {
|
||||||
|
return wrapFloat64HistogramInstrument(
|
||||||
|
m.newSync(name, sdkapi.HistogramInstrumentKind, number.Float64Kind, opts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64GaugeObserver creates a new integer GaugeObserver instrument
|
||||||
|
// with the given name, running a given callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (m Meter) NewInt64GaugeObserver(name string, callback Int64ObserverFunc, opts ...InstrumentOption) (Int64GaugeObserver, error) {
|
||||||
|
if callback == nil {
|
||||||
|
return wrapInt64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapInt64GaugeObserverInstrument(
|
||||||
|
m.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Int64Kind, opts,
|
||||||
|
newInt64AsyncRunner(callback)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64GaugeObserver creates a new floating point GaugeObserver with
|
||||||
|
// the given name, running a given callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (m Meter) NewFloat64GaugeObserver(name string, callback Float64ObserverFunc, opts ...InstrumentOption) (Float64GaugeObserver, error) {
|
||||||
|
if callback == nil {
|
||||||
|
return wrapFloat64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapFloat64GaugeObserverInstrument(
|
||||||
|
m.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Float64Kind, opts,
|
||||||
|
newFloat64AsyncRunner(callback)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64CounterObserver creates a new integer CounterObserver instrument
|
||||||
|
// with the given name, running a given callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (m Meter) NewInt64CounterObserver(name string, callback Int64ObserverFunc, opts ...InstrumentOption) (Int64CounterObserver, error) {
|
||||||
|
if callback == nil {
|
||||||
|
return wrapInt64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapInt64CounterObserverInstrument(
|
||||||
|
m.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Int64Kind, opts,
|
||||||
|
newInt64AsyncRunner(callback)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64CounterObserver creates a new floating point CounterObserver with
|
||||||
|
// the given name, running a given callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (m Meter) NewFloat64CounterObserver(name string, callback Float64ObserverFunc, opts ...InstrumentOption) (Float64CounterObserver, error) {
|
||||||
|
if callback == nil {
|
||||||
|
return wrapFloat64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapFloat64CounterObserverInstrument(
|
||||||
|
m.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Float64Kind, opts,
|
||||||
|
newFloat64AsyncRunner(callback)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64UpDownCounterObserver creates a new integer UpDownCounterObserver instrument
|
||||||
|
// with the given name, running a given callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (m Meter) NewInt64UpDownCounterObserver(name string, callback Int64ObserverFunc, opts ...InstrumentOption) (Int64UpDownCounterObserver, error) {
|
||||||
|
if callback == nil {
|
||||||
|
return wrapInt64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapInt64UpDownCounterObserverInstrument(
|
||||||
|
m.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Int64Kind, opts,
|
||||||
|
newInt64AsyncRunner(callback)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64UpDownCounterObserver creates a new floating point UpDownCounterObserver with
|
||||||
|
// the given name, running a given callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (m Meter) NewFloat64UpDownCounterObserver(name string, callback Float64ObserverFunc, opts ...InstrumentOption) (Float64UpDownCounterObserver, error) {
|
||||||
|
if callback == nil {
|
||||||
|
return wrapFloat64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapFloat64UpDownCounterObserverInstrument(
|
||||||
|
m.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Float64Kind, opts,
|
||||||
|
newFloat64AsyncRunner(callback)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64GaugeObserver creates a new integer GaugeObserver instrument
|
||||||
|
// with the given name, running in a batch callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (b BatchObserver) NewInt64GaugeObserver(name string, opts ...InstrumentOption) (Int64GaugeObserver, error) {
|
||||||
|
if b.runner == nil {
|
||||||
|
return wrapInt64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapInt64GaugeObserverInstrument(
|
||||||
|
b.meter.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Int64Kind, opts, b.runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64GaugeObserver creates a new floating point GaugeObserver with
|
||||||
|
// the given name, running in a batch callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (b BatchObserver) NewFloat64GaugeObserver(name string, opts ...InstrumentOption) (Float64GaugeObserver, error) {
|
||||||
|
if b.runner == nil {
|
||||||
|
return wrapFloat64GaugeObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapFloat64GaugeObserverInstrument(
|
||||||
|
b.meter.newAsync(name, sdkapi.GaugeObserverInstrumentKind, number.Float64Kind, opts,
|
||||||
|
b.runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64CounterObserver creates a new integer CounterObserver instrument
|
||||||
|
// with the given name, running in a batch callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (b BatchObserver) NewInt64CounterObserver(name string, opts ...InstrumentOption) (Int64CounterObserver, error) {
|
||||||
|
if b.runner == nil {
|
||||||
|
return wrapInt64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapInt64CounterObserverInstrument(
|
||||||
|
b.meter.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Int64Kind, opts, b.runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64CounterObserver creates a new floating point CounterObserver with
|
||||||
|
// the given name, running in a batch callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (b BatchObserver) NewFloat64CounterObserver(name string, opts ...InstrumentOption) (Float64CounterObserver, error) {
|
||||||
|
if b.runner == nil {
|
||||||
|
return wrapFloat64CounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapFloat64CounterObserverInstrument(
|
||||||
|
b.meter.newAsync(name, sdkapi.CounterObserverInstrumentKind, number.Float64Kind, opts,
|
||||||
|
b.runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64UpDownCounterObserver creates a new integer UpDownCounterObserver instrument
|
||||||
|
// with the given name, running in a batch callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (b BatchObserver) NewInt64UpDownCounterObserver(name string, opts ...InstrumentOption) (Int64UpDownCounterObserver, error) {
|
||||||
|
if b.runner == nil {
|
||||||
|
return wrapInt64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapInt64UpDownCounterObserverInstrument(
|
||||||
|
b.meter.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Int64Kind, opts, b.runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64UpDownCounterObserver creates a new floating point UpDownCounterObserver with
|
||||||
|
// the given name, running in a batch callback, and customized with
|
||||||
|
// options. May return an error if the name is invalid (e.g., empty)
|
||||||
|
// or improperly registered (e.g., duplicate registration).
|
||||||
|
func (b BatchObserver) NewFloat64UpDownCounterObserver(name string, opts ...InstrumentOption) (Float64UpDownCounterObserver, error) {
|
||||||
|
if b.runner == nil {
|
||||||
|
return wrapFloat64UpDownCounterObserverInstrument(sdkapi.NewNoopAsyncInstrument(), nil)
|
||||||
|
}
|
||||||
|
return wrapFloat64UpDownCounterObserverInstrument(
|
||||||
|
b.meter.newAsync(name, sdkapi.UpDownCounterObserverInstrumentKind, number.Float64Kind, opts,
|
||||||
|
b.runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeterImpl returns the underlying MeterImpl of this Meter.
|
||||||
|
func (m Meter) MeterImpl() sdkapi.MeterImpl {
|
||||||
|
return m.impl
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAsync constructs one new asynchronous instrument.
|
||||||
|
func (m Meter) newAsync(
|
||||||
|
name string,
|
||||||
|
mkind sdkapi.InstrumentKind,
|
||||||
|
nkind number.Kind,
|
||||||
|
opts []InstrumentOption,
|
||||||
|
runner sdkapi.AsyncRunner,
|
||||||
|
) (
|
||||||
|
sdkapi.AsyncImpl,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
if m.impl == nil {
|
||||||
|
return sdkapi.NewNoopAsyncInstrument(), nil
|
||||||
|
}
|
||||||
|
cfg := NewInstrumentConfig(opts...)
|
||||||
|
desc := sdkapi.NewDescriptor(name, mkind, nkind, cfg.description, cfg.unit)
|
||||||
|
return m.impl.NewAsyncInstrument(desc, runner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSync constructs one new synchronous instrument.
|
||||||
|
func (m Meter) newSync(
|
||||||
|
name string,
|
||||||
|
metricKind sdkapi.InstrumentKind,
|
||||||
|
numberKind number.Kind,
|
||||||
|
opts []InstrumentOption,
|
||||||
|
) (
|
||||||
|
sdkapi.SyncImpl,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
if m.impl == nil {
|
||||||
|
return sdkapi.NewNoopSyncInstrument(), nil
|
||||||
|
}
|
||||||
|
cfg := NewInstrumentConfig(opts...)
|
||||||
|
desc := sdkapi.NewDescriptor(name, metricKind, numberKind, cfg.description, cfg.unit)
|
||||||
|
return m.impl.NewSyncInstrument(desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeterMust is a wrapper for Meter interfaces that panics when any
|
||||||
|
// instrument constructor encounters an error.
|
||||||
|
type MeterMust struct {
|
||||||
|
meter Meter
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchObserverMust is a wrapper for BatchObserver that panics when
|
||||||
|
// any instrument constructor encounters an error.
|
||||||
|
type BatchObserverMust struct {
|
||||||
|
batch BatchObserver
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must constructs a MeterMust implementation from a Meter, allowing
|
||||||
|
// the application to panic when any instrument constructor yields an
|
||||||
|
// error.
|
||||||
|
func Must(meter Meter) MeterMust {
|
||||||
|
return MeterMust{meter: meter}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Counter calls `Meter.NewInt64Counter` and returns the
|
||||||
|
// instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewInt64Counter(name string, cos ...InstrumentOption) Int64Counter {
|
||||||
|
if inst, err := mm.meter.NewInt64Counter(name, cos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Counter calls `Meter.NewFloat64Counter` and returns the
|
||||||
|
// instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewFloat64Counter(name string, cos ...InstrumentOption) Float64Counter {
|
||||||
|
if inst, err := mm.meter.NewFloat64Counter(name, cos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64UpDownCounter calls `Meter.NewInt64UpDownCounter` and returns the
|
||||||
|
// instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewInt64UpDownCounter(name string, cos ...InstrumentOption) Int64UpDownCounter {
|
||||||
|
if inst, err := mm.meter.NewInt64UpDownCounter(name, cos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64UpDownCounter calls `Meter.NewFloat64UpDownCounter` and returns the
|
||||||
|
// instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewFloat64UpDownCounter(name string, cos ...InstrumentOption) Float64UpDownCounter {
|
||||||
|
if inst, err := mm.meter.NewFloat64UpDownCounter(name, cos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Histogram calls `Meter.NewInt64Histogram` and returns the
|
||||||
|
// instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewInt64Histogram(name string, mos ...InstrumentOption) Int64Histogram {
|
||||||
|
if inst, err := mm.meter.NewInt64Histogram(name, mos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Histogram calls `Meter.NewFloat64Histogram` and returns the
|
||||||
|
// instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewFloat64Histogram(name string, mos ...InstrumentOption) Float64Histogram {
|
||||||
|
if inst, err := mm.meter.NewFloat64Histogram(name, mos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64GaugeObserver calls `Meter.NewInt64GaugeObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewInt64GaugeObserver(name string, callback Int64ObserverFunc, oos ...InstrumentOption) Int64GaugeObserver {
|
||||||
|
if inst, err := mm.meter.NewInt64GaugeObserver(name, callback, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64GaugeObserver calls `Meter.NewFloat64GaugeObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewFloat64GaugeObserver(name string, callback Float64ObserverFunc, oos ...InstrumentOption) Float64GaugeObserver {
|
||||||
|
if inst, err := mm.meter.NewFloat64GaugeObserver(name, callback, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64CounterObserver calls `Meter.NewInt64CounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewInt64CounterObserver(name string, callback Int64ObserverFunc, oos ...InstrumentOption) Int64CounterObserver {
|
||||||
|
if inst, err := mm.meter.NewInt64CounterObserver(name, callback, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64CounterObserver calls `Meter.NewFloat64CounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewFloat64CounterObserver(name string, callback Float64ObserverFunc, oos ...InstrumentOption) Float64CounterObserver {
|
||||||
|
if inst, err := mm.meter.NewFloat64CounterObserver(name, callback, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64UpDownCounterObserver calls `Meter.NewInt64UpDownCounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewInt64UpDownCounterObserver(name string, callback Int64ObserverFunc, oos ...InstrumentOption) Int64UpDownCounterObserver {
|
||||||
|
if inst, err := mm.meter.NewInt64UpDownCounterObserver(name, callback, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64UpDownCounterObserver calls `Meter.NewFloat64UpDownCounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (mm MeterMust) NewFloat64UpDownCounterObserver(name string, callback Float64ObserverFunc, oos ...InstrumentOption) Float64UpDownCounterObserver {
|
||||||
|
if inst, err := mm.meter.NewFloat64UpDownCounterObserver(name, callback, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBatchObserver returns a wrapper around BatchObserver that panics
|
||||||
|
// when any instrument constructor returns an error.
|
||||||
|
func (mm MeterMust) NewBatchObserver(callback BatchObserverFunc) BatchObserverMust {
|
||||||
|
return BatchObserverMust{
|
||||||
|
batch: mm.meter.NewBatchObserver(callback),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64GaugeObserver calls `BatchObserver.NewInt64GaugeObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (bm BatchObserverMust) NewInt64GaugeObserver(name string, oos ...InstrumentOption) Int64GaugeObserver {
|
||||||
|
if inst, err := bm.batch.NewInt64GaugeObserver(name, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64GaugeObserver calls `BatchObserver.NewFloat64GaugeObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (bm BatchObserverMust) NewFloat64GaugeObserver(name string, oos ...InstrumentOption) Float64GaugeObserver {
|
||||||
|
if inst, err := bm.batch.NewFloat64GaugeObserver(name, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64CounterObserver calls `BatchObserver.NewInt64CounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (bm BatchObserverMust) NewInt64CounterObserver(name string, oos ...InstrumentOption) Int64CounterObserver {
|
||||||
|
if inst, err := bm.batch.NewInt64CounterObserver(name, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64CounterObserver calls `BatchObserver.NewFloat64CounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (bm BatchObserverMust) NewFloat64CounterObserver(name string, oos ...InstrumentOption) Float64CounterObserver {
|
||||||
|
if inst, err := bm.batch.NewFloat64CounterObserver(name, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64UpDownCounterObserver calls `BatchObserver.NewInt64UpDownCounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (bm BatchObserverMust) NewInt64UpDownCounterObserver(name string, oos ...InstrumentOption) Int64UpDownCounterObserver {
|
||||||
|
if inst, err := bm.batch.NewInt64UpDownCounterObserver(name, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64UpDownCounterObserver calls `BatchObserver.NewFloat64UpDownCounterObserver` and
|
||||||
|
// returns the instrument, panicking if it encounters an error.
|
||||||
|
func (bm BatchObserverMust) NewFloat64UpDownCounterObserver(name string, oos ...InstrumentOption) Float64UpDownCounterObserver {
|
||||||
|
if inst, err := bm.batch.NewFloat64UpDownCounterObserver(name, oos...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return inst
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,464 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package metric // import "go.opentelemetry.io/otel/metric"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric/number"
|
||||||
|
"go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrSDKReturnedNilImpl is returned when a new `MeterImpl` returns nil.
|
||||||
|
var ErrSDKReturnedNilImpl = errors.New("SDK returned a nil implementation")
|
||||||
|
|
||||||
|
// Int64ObserverFunc is a type of callback that integral
|
||||||
|
// observers run.
|
||||||
|
type Int64ObserverFunc func(context.Context, Int64ObserverResult)
|
||||||
|
|
||||||
|
// Float64ObserverFunc is a type of callback that floating point
|
||||||
|
// observers run.
|
||||||
|
type Float64ObserverFunc func(context.Context, Float64ObserverResult)
|
||||||
|
|
||||||
|
// BatchObserverFunc is a callback argument for use with any
|
||||||
|
// Observer instrument that will be reported as a batch of
|
||||||
|
// observations.
|
||||||
|
type BatchObserverFunc func(context.Context, BatchObserverResult)
|
||||||
|
|
||||||
|
// Int64ObserverResult is passed to an observer callback to capture
|
||||||
|
// observations for one asynchronous integer metric instrument.
|
||||||
|
type Int64ObserverResult struct {
|
||||||
|
instrument sdkapi.AsyncImpl
|
||||||
|
function func([]attribute.KeyValue, ...Observation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64ObserverResult is passed to an observer callback to capture
|
||||||
|
// observations for one asynchronous floating point metric instrument.
|
||||||
|
type Float64ObserverResult struct {
|
||||||
|
instrument sdkapi.AsyncImpl
|
||||||
|
function func([]attribute.KeyValue, ...Observation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchObserverResult is passed to a batch observer callback to
|
||||||
|
// capture observations for multiple asynchronous instruments.
|
||||||
|
type BatchObserverResult struct {
|
||||||
|
function func([]attribute.KeyValue, ...Observation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe captures a single integer value from the associated
|
||||||
|
// instrument callback, with the given labels.
|
||||||
|
func (ir Int64ObserverResult) Observe(value int64, labels ...attribute.KeyValue) {
|
||||||
|
ir.function(labels, sdkapi.NewObservation(ir.instrument, number.NewInt64Number(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe captures a single floating point value from the associated
|
||||||
|
// instrument callback, with the given labels.
|
||||||
|
func (fr Float64ObserverResult) Observe(value float64, labels ...attribute.KeyValue) {
|
||||||
|
fr.function(labels, sdkapi.NewObservation(fr.instrument, number.NewFloat64Number(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe captures a multiple observations from the associated batch
|
||||||
|
// instrument callback, with the given labels.
|
||||||
|
func (br BatchObserverResult) Observe(labels []attribute.KeyValue, obs ...Observation) {
|
||||||
|
br.function(labels, obs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ sdkapi.AsyncSingleRunner = (*Int64ObserverFunc)(nil)
|
||||||
|
var _ sdkapi.AsyncSingleRunner = (*Float64ObserverFunc)(nil)
|
||||||
|
var _ sdkapi.AsyncBatchRunner = (*BatchObserverFunc)(nil)
|
||||||
|
|
||||||
|
// newInt64AsyncRunner returns a single-observer callback for integer Observer instruments.
|
||||||
|
func newInt64AsyncRunner(c Int64ObserverFunc) sdkapi.AsyncSingleRunner {
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFloat64AsyncRunner returns a single-observer callback for floating point Observer instruments.
|
||||||
|
func newFloat64AsyncRunner(c Float64ObserverFunc) sdkapi.AsyncSingleRunner {
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBatchAsyncRunner returns a batch-observer callback use with multiple Observer instruments.
|
||||||
|
func newBatchAsyncRunner(c BatchObserverFunc) sdkapi.AsyncBatchRunner {
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyRunner implements AsyncRunner.
|
||||||
|
func (*Int64ObserverFunc) AnyRunner() {}
|
||||||
|
|
||||||
|
// AnyRunner implements AsyncRunner.
|
||||||
|
func (*Float64ObserverFunc) AnyRunner() {}
|
||||||
|
|
||||||
|
// AnyRunner implements AsyncRunner.
|
||||||
|
func (*BatchObserverFunc) AnyRunner() {}
|
||||||
|
|
||||||
|
// Run implements AsyncSingleRunner.
|
||||||
|
func (i *Int64ObserverFunc) Run(ctx context.Context, impl sdkapi.AsyncImpl, function func([]attribute.KeyValue, ...Observation)) {
|
||||||
|
(*i)(ctx, Int64ObserverResult{
|
||||||
|
instrument: impl,
|
||||||
|
function: function,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements AsyncSingleRunner.
|
||||||
|
func (f *Float64ObserverFunc) Run(ctx context.Context, impl sdkapi.AsyncImpl, function func([]attribute.KeyValue, ...Observation)) {
|
||||||
|
(*f)(ctx, Float64ObserverResult{
|
||||||
|
instrument: impl,
|
||||||
|
function: function,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run implements AsyncBatchRunner.
|
||||||
|
func (b *BatchObserverFunc) Run(ctx context.Context, function func([]attribute.KeyValue, ...Observation)) {
|
||||||
|
(*b)(ctx, BatchObserverResult{
|
||||||
|
function: function,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapInt64GaugeObserverInstrument converts an AsyncImpl into Int64GaugeObserver.
|
||||||
|
func wrapInt64GaugeObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Int64GaugeObserver, error) {
|
||||||
|
common, err := checkNewAsync(asyncInst, err)
|
||||||
|
return Int64GaugeObserver{asyncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapFloat64GaugeObserverInstrument converts an AsyncImpl into Float64GaugeObserver.
|
||||||
|
func wrapFloat64GaugeObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Float64GaugeObserver, error) {
|
||||||
|
common, err := checkNewAsync(asyncInst, err)
|
||||||
|
return Float64GaugeObserver{asyncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapInt64CounterObserverInstrument converts an AsyncImpl into Int64CounterObserver.
|
||||||
|
func wrapInt64CounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Int64CounterObserver, error) {
|
||||||
|
common, err := checkNewAsync(asyncInst, err)
|
||||||
|
return Int64CounterObserver{asyncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapFloat64CounterObserverInstrument converts an AsyncImpl into Float64CounterObserver.
|
||||||
|
func wrapFloat64CounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Float64CounterObserver, error) {
|
||||||
|
common, err := checkNewAsync(asyncInst, err)
|
||||||
|
return Float64CounterObserver{asyncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapInt64UpDownCounterObserverInstrument converts an AsyncImpl into Int64UpDownCounterObserver.
|
||||||
|
func wrapInt64UpDownCounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Int64UpDownCounterObserver, error) {
|
||||||
|
common, err := checkNewAsync(asyncInst, err)
|
||||||
|
return Int64UpDownCounterObserver{asyncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapFloat64UpDownCounterObserverInstrument converts an AsyncImpl into Float64UpDownCounterObserver.
|
||||||
|
func wrapFloat64UpDownCounterObserverInstrument(asyncInst sdkapi.AsyncImpl, err error) (Float64UpDownCounterObserver, error) {
|
||||||
|
common, err := checkNewAsync(asyncInst, err)
|
||||||
|
return Float64UpDownCounterObserver{asyncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchObserver represents an Observer callback that can report
|
||||||
|
// observations for multiple instruments.
|
||||||
|
type BatchObserver struct {
|
||||||
|
meter Meter
|
||||||
|
runner sdkapi.AsyncBatchRunner
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64GaugeObserver is a metric that captures a set of int64 values at a
|
||||||
|
// point in time.
|
||||||
|
type Int64GaugeObserver struct {
|
||||||
|
asyncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64GaugeObserver is a metric that captures a set of float64 values
|
||||||
|
// at a point in time.
|
||||||
|
type Float64GaugeObserver struct {
|
||||||
|
asyncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64CounterObserver is a metric that captures a precomputed sum of
|
||||||
|
// int64 values at a point in time.
|
||||||
|
type Int64CounterObserver struct {
|
||||||
|
asyncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64CounterObserver is a metric that captures a precomputed sum of
|
||||||
|
// float64 values at a point in time.
|
||||||
|
type Float64CounterObserver struct {
|
||||||
|
asyncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64UpDownCounterObserver is a metric that captures a precomputed sum of
|
||||||
|
// int64 values at a point in time.
|
||||||
|
type Int64UpDownCounterObserver struct {
|
||||||
|
asyncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64UpDownCounterObserver is a metric that captures a precomputed sum of
|
||||||
|
// float64 values at a point in time.
|
||||||
|
type Float64UpDownCounterObserver struct {
|
||||||
|
asyncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observation returns an Observation, a BatchObserverFunc
|
||||||
|
// argument, for an asynchronous integer instrument.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (i Int64GaugeObserver) Observation(v int64) Observation {
|
||||||
|
return sdkapi.NewObservation(i.instrument, number.NewInt64Number(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observation returns an Observation, a BatchObserverFunc
|
||||||
|
// argument, for an asynchronous integer instrument.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (f Float64GaugeObserver) Observation(v float64) Observation {
|
||||||
|
return sdkapi.NewObservation(f.instrument, number.NewFloat64Number(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observation returns an Observation, a BatchObserverFunc
|
||||||
|
// argument, for an asynchronous integer instrument.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (i Int64CounterObserver) Observation(v int64) Observation {
|
||||||
|
return sdkapi.NewObservation(i.instrument, number.NewInt64Number(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observation returns an Observation, a BatchObserverFunc
|
||||||
|
// argument, for an asynchronous integer instrument.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (f Float64CounterObserver) Observation(v float64) Observation {
|
||||||
|
return sdkapi.NewObservation(f.instrument, number.NewFloat64Number(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observation returns an Observation, a BatchObserverFunc
|
||||||
|
// argument, for an asynchronous integer instrument.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (i Int64UpDownCounterObserver) Observation(v int64) Observation {
|
||||||
|
return sdkapi.NewObservation(i.instrument, number.NewInt64Number(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observation returns an Observation, a BatchObserverFunc
|
||||||
|
// argument, for an asynchronous integer instrument.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (f Float64UpDownCounterObserver) Observation(v float64) Observation {
|
||||||
|
return sdkapi.NewObservation(f.instrument, number.NewFloat64Number(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncInstrument contains a SyncImpl.
|
||||||
|
type syncInstrument struct {
|
||||||
|
instrument sdkapi.SyncImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncInstrument contains a AsyncImpl.
|
||||||
|
type asyncInstrument struct {
|
||||||
|
instrument sdkapi.AsyncImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncImpl implements AsyncImpl.
|
||||||
|
func (a asyncInstrument) AsyncImpl() sdkapi.AsyncImpl {
|
||||||
|
return a.instrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncImpl returns the implementation object for synchronous instruments.
|
||||||
|
func (s syncInstrument) SyncImpl() sdkapi.SyncImpl {
|
||||||
|
return s.instrument
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s syncInstrument) float64Measurement(value float64) Measurement {
|
||||||
|
return sdkapi.NewMeasurement(s.instrument, number.NewFloat64Number(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s syncInstrument) int64Measurement(value int64) Measurement {
|
||||||
|
return sdkapi.NewMeasurement(s.instrument, number.NewInt64Number(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s syncInstrument) directRecord(ctx context.Context, number number.Number, labels []attribute.KeyValue) {
|
||||||
|
s.instrument.RecordOne(ctx, number, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNewAsync receives an AsyncImpl and potential
|
||||||
|
// error, and returns the same types, checking for and ensuring that
|
||||||
|
// the returned interface is not nil.
|
||||||
|
func checkNewAsync(instrument sdkapi.AsyncImpl, err error) (asyncInstrument, error) {
|
||||||
|
if instrument == nil {
|
||||||
|
if err == nil {
|
||||||
|
err = ErrSDKReturnedNilImpl
|
||||||
|
}
|
||||||
|
instrument = sdkapi.NewNoopAsyncInstrument()
|
||||||
|
}
|
||||||
|
return asyncInstrument{
|
||||||
|
instrument: instrument,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNewSync receives an SyncImpl and potential
|
||||||
|
// error, and returns the same types, checking for and ensuring that
|
||||||
|
// the returned interface is not nil.
|
||||||
|
func checkNewSync(instrument sdkapi.SyncImpl, err error) (syncInstrument, error) {
|
||||||
|
if instrument == nil {
|
||||||
|
if err == nil {
|
||||||
|
err = ErrSDKReturnedNilImpl
|
||||||
|
}
|
||||||
|
// Note: an alternate behavior would be to synthesize a new name
|
||||||
|
// or group all duplicately-named instruments of a certain type
|
||||||
|
// together and use a tag for the original name, e.g.,
|
||||||
|
// name = 'invalid.counter.int64'
|
||||||
|
// label = 'original-name=duplicate-counter-name'
|
||||||
|
instrument = sdkapi.NewNoopSyncInstrument()
|
||||||
|
}
|
||||||
|
return syncInstrument{
|
||||||
|
instrument: instrument,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapInt64CounterInstrument converts a SyncImpl into Int64Counter.
|
||||||
|
func wrapInt64CounterInstrument(syncInst sdkapi.SyncImpl, err error) (Int64Counter, error) {
|
||||||
|
common, err := checkNewSync(syncInst, err)
|
||||||
|
return Int64Counter{syncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapFloat64CounterInstrument converts a SyncImpl into Float64Counter.
|
||||||
|
func wrapFloat64CounterInstrument(syncInst sdkapi.SyncImpl, err error) (Float64Counter, error) {
|
||||||
|
common, err := checkNewSync(syncInst, err)
|
||||||
|
return Float64Counter{syncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapInt64UpDownCounterInstrument converts a SyncImpl into Int64UpDownCounter.
|
||||||
|
func wrapInt64UpDownCounterInstrument(syncInst sdkapi.SyncImpl, err error) (Int64UpDownCounter, error) {
|
||||||
|
common, err := checkNewSync(syncInst, err)
|
||||||
|
return Int64UpDownCounter{syncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapFloat64UpDownCounterInstrument converts a SyncImpl into Float64UpDownCounter.
|
||||||
|
func wrapFloat64UpDownCounterInstrument(syncInst sdkapi.SyncImpl, err error) (Float64UpDownCounter, error) {
|
||||||
|
common, err := checkNewSync(syncInst, err)
|
||||||
|
return Float64UpDownCounter{syncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapInt64HistogramInstrument converts a SyncImpl into Int64Histogram.
|
||||||
|
func wrapInt64HistogramInstrument(syncInst sdkapi.SyncImpl, err error) (Int64Histogram, error) {
|
||||||
|
common, err := checkNewSync(syncInst, err)
|
||||||
|
return Int64Histogram{syncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapFloat64HistogramInstrument converts a SyncImpl into Float64Histogram.
|
||||||
|
func wrapFloat64HistogramInstrument(syncInst sdkapi.SyncImpl, err error) (Float64Histogram, error) {
|
||||||
|
common, err := checkNewSync(syncInst, err)
|
||||||
|
return Float64Histogram{syncInstrument: common}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Counter is a metric that accumulates float64 values.
|
||||||
|
type Float64Counter struct {
|
||||||
|
syncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Counter is a metric that accumulates int64 values.
|
||||||
|
type Int64Counter struct {
|
||||||
|
syncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c Float64Counter) Measurement(value float64) Measurement {
|
||||||
|
return c.float64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c Int64Counter) Measurement(value int64) Measurement {
|
||||||
|
return c.int64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum. The labels should contain
|
||||||
|
// the keys and values to be associated with this value.
|
||||||
|
func (c Float64Counter) Add(ctx context.Context, value float64, labels ...attribute.KeyValue) {
|
||||||
|
c.directRecord(ctx, number.NewFloat64Number(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum. The labels should contain
|
||||||
|
// the keys and values to be associated with this value.
|
||||||
|
func (c Int64Counter) Add(ctx context.Context, value int64, labels ...attribute.KeyValue) {
|
||||||
|
c.directRecord(ctx, number.NewInt64Number(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64UpDownCounter is a metric instrument that sums floating
|
||||||
|
// point values.
|
||||||
|
type Float64UpDownCounter struct {
|
||||||
|
syncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64UpDownCounter is a metric instrument that sums integer values.
|
||||||
|
type Int64UpDownCounter struct {
|
||||||
|
syncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c Float64UpDownCounter) Measurement(value float64) Measurement {
|
||||||
|
return c.float64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c Int64UpDownCounter) Measurement(value int64) Measurement {
|
||||||
|
return c.int64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum. The labels should contain
|
||||||
|
// the keys and values to be associated with this value.
|
||||||
|
func (c Float64UpDownCounter) Add(ctx context.Context, value float64, labels ...attribute.KeyValue) {
|
||||||
|
c.directRecord(ctx, number.NewFloat64Number(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum. The labels should contain
|
||||||
|
// the keys and values to be associated with this value.
|
||||||
|
func (c Int64UpDownCounter) Add(ctx context.Context, value int64, labels ...attribute.KeyValue) {
|
||||||
|
c.directRecord(ctx, number.NewInt64Number(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Histogram is a metric that records float64 values.
|
||||||
|
type Float64Histogram struct {
|
||||||
|
syncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Histogram is a metric that records int64 values.
|
||||||
|
type Int64Histogram struct {
|
||||||
|
syncInstrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c Float64Histogram) Measurement(value float64) Measurement {
|
||||||
|
return c.float64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c Int64Histogram) Measurement(value int64) Measurement {
|
||||||
|
return c.int64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record adds a new value to the list of Histogram's records. The
|
||||||
|
// labels should contain the keys and values to be associated with
|
||||||
|
// this value.
|
||||||
|
func (c Float64Histogram) Record(ctx context.Context, value float64, labels ...attribute.KeyValue) {
|
||||||
|
c.directRecord(ctx, number.NewFloat64Number(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record adds a new value to the Histogram's distribution. The
|
||||||
|
// labels should contain the keys and values to be associated with
|
||||||
|
// this value.
|
||||||
|
func (c Int64Histogram) Record(ctx context.Context, value int64, labels ...attribute.KeyValue) {
|
||||||
|
c.directRecord(ctx, number.NewInt64Number(value), labels)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package metric // import "go.opentelemetry.io/otel/metric"
|
||||||
|
|
||||||
|
type noopMeterProvider struct{}
|
||||||
|
|
||||||
|
// NewNoopMeterProvider returns an implementation of MeterProvider that
|
||||||
|
// performs no operations. The Meter and Instrument created from the returned
|
||||||
|
// MeterProvider also perform no operations.
|
||||||
|
func NewNoopMeterProvider() MeterProvider {
|
||||||
|
return noopMeterProvider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ MeterProvider = noopMeterProvider{}
|
||||||
|
|
||||||
|
func (noopMeterProvider) Meter(instrumentationName string, opts ...MeterOption) Meter {
|
||||||
|
return Meter{}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package number provides a number abstraction for instruments that
|
||||||
|
either support int64 or float64 input values.
|
||||||
|
|
||||||
|
This package is currently in a pre-GA phase. Backwards incompatible changes
|
||||||
|
may be introduced in subsequent minor version releases as we work to track the
|
||||||
|
evolving OpenTelemetry specification and user feedback.
|
||||||
|
*/
|
||||||
|
package number // import "go.opentelemetry.io/otel/metric/number"
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Code generated by "stringer -type=Kind"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package number
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Int64Kind-0]
|
||||||
|
_ = x[Float64Kind-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Kind_name = "Int64KindFloat64Kind"
|
||||||
|
|
||||||
|
var _Kind_index = [...]uint8{0, 9, 20}
|
||||||
|
|
||||||
|
func (i Kind) String() string {
|
||||||
|
if i < 0 || i >= Kind(len(_Kind_index)-1) {
|
||||||
|
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
|
||||||
|
}
|
|
@ -0,0 +1,538 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package number // import "go.opentelemetry.io/otel/metric/number"
|
||||||
|
|
||||||
|
//go:generate stringer -type=Kind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Kind describes the data type of the Number.
|
||||||
|
type Kind int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Int64Kind means that the Number stores int64.
|
||||||
|
Int64Kind Kind = iota
|
||||||
|
// Float64Kind means that the Number stores float64.
|
||||||
|
Float64Kind
|
||||||
|
)
|
||||||
|
|
||||||
|
// Zero returns a zero value for a given Kind
|
||||||
|
func (k Kind) Zero() Number {
|
||||||
|
switch k {
|
||||||
|
case Int64Kind:
|
||||||
|
return NewInt64Number(0)
|
||||||
|
case Float64Kind:
|
||||||
|
return NewFloat64Number(0.)
|
||||||
|
default:
|
||||||
|
return Number(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimum returns the minimum representable value
|
||||||
|
// for a given Kind
|
||||||
|
func (k Kind) Minimum() Number {
|
||||||
|
switch k {
|
||||||
|
case Int64Kind:
|
||||||
|
return NewInt64Number(math.MinInt64)
|
||||||
|
case Float64Kind:
|
||||||
|
return NewFloat64Number(-1. * math.MaxFloat64)
|
||||||
|
default:
|
||||||
|
return Number(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum returns the maximum representable value
|
||||||
|
// for a given Kind
|
||||||
|
func (k Kind) Maximum() Number {
|
||||||
|
switch k {
|
||||||
|
case Int64Kind:
|
||||||
|
return NewInt64Number(math.MaxInt64)
|
||||||
|
case Float64Kind:
|
||||||
|
return NewFloat64Number(math.MaxFloat64)
|
||||||
|
default:
|
||||||
|
return Number(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number represents either an integral or a floating point value. It
|
||||||
|
// needs to be accompanied with a source of Kind that describes
|
||||||
|
// the actual type of the value stored within Number.
|
||||||
|
type Number uint64
|
||||||
|
|
||||||
|
// - constructors
|
||||||
|
|
||||||
|
// NewNumberFromRaw creates a new Number from a raw value.
|
||||||
|
func NewNumberFromRaw(r uint64) Number {
|
||||||
|
return Number(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Number creates an integral Number.
|
||||||
|
func NewInt64Number(i int64) Number {
|
||||||
|
return NewNumberFromRaw(internal.Int64ToRaw(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Number creates a floating point Number.
|
||||||
|
func NewFloat64Number(f float64) Number {
|
||||||
|
return NewNumberFromRaw(internal.Float64ToRaw(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNumberSignChange returns a number with the same magnitude and
|
||||||
|
// the opposite sign. `kind` must describe the kind of number in `nn`.
|
||||||
|
func NewNumberSignChange(kind Kind, nn Number) Number {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
return NewInt64Number(-nn.AsInt64())
|
||||||
|
case Float64Kind:
|
||||||
|
return NewFloat64Number(-nn.AsFloat64())
|
||||||
|
}
|
||||||
|
return nn
|
||||||
|
}
|
||||||
|
|
||||||
|
// - as x
|
||||||
|
|
||||||
|
// AsNumber gets the Number.
|
||||||
|
func (n *Number) AsNumber() Number {
|
||||||
|
return *n
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsRaw gets the uninterpreted raw value. Might be useful for some
|
||||||
|
// atomic operations.
|
||||||
|
func (n *Number) AsRaw() uint64 {
|
||||||
|
return uint64(*n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInt64 assumes that the value contains an int64 and returns it as
|
||||||
|
// such.
|
||||||
|
func (n *Number) AsInt64() int64 {
|
||||||
|
return internal.RawToInt64(n.AsRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsFloat64 assumes that the measurement value contains a float64 and
|
||||||
|
// returns it as such.
|
||||||
|
func (n *Number) AsFloat64() float64 {
|
||||||
|
return internal.RawToFloat64(n.AsRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
// - as x atomic
|
||||||
|
|
||||||
|
// AsNumberAtomic gets the Number atomically.
|
||||||
|
func (n *Number) AsNumberAtomic() Number {
|
||||||
|
return NewNumberFromRaw(n.AsRawAtomic())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsRawAtomic gets the uninterpreted raw value atomically. Might be
|
||||||
|
// useful for some atomic operations.
|
||||||
|
func (n *Number) AsRawAtomic() uint64 {
|
||||||
|
return atomic.LoadUint64(n.AsRawPtr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInt64Atomic assumes that the number contains an int64 and returns
|
||||||
|
// it as such atomically.
|
||||||
|
func (n *Number) AsInt64Atomic() int64 {
|
||||||
|
return atomic.LoadInt64(n.AsInt64Ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsFloat64Atomic assumes that the measurement value contains a
|
||||||
|
// float64 and returns it as such atomically.
|
||||||
|
func (n *Number) AsFloat64Atomic() float64 {
|
||||||
|
return internal.RawToFloat64(n.AsRawAtomic())
|
||||||
|
}
|
||||||
|
|
||||||
|
// - as x ptr
|
||||||
|
|
||||||
|
// AsRawPtr gets the pointer to the raw, uninterpreted raw
|
||||||
|
// value. Might be useful for some atomic operations.
|
||||||
|
func (n *Number) AsRawPtr() *uint64 {
|
||||||
|
return (*uint64)(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInt64Ptr assumes that the number contains an int64 and returns a
|
||||||
|
// pointer to it.
|
||||||
|
func (n *Number) AsInt64Ptr() *int64 {
|
||||||
|
return internal.RawPtrToInt64Ptr(n.AsRawPtr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsFloat64Ptr assumes that the number contains a float64 and returns a
|
||||||
|
// pointer to it.
|
||||||
|
func (n *Number) AsFloat64Ptr() *float64 {
|
||||||
|
return internal.RawPtrToFloat64Ptr(n.AsRawPtr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// - coerce
|
||||||
|
|
||||||
|
// CoerceToInt64 casts the number to int64. May result in
|
||||||
|
// data/precision loss.
|
||||||
|
func (n *Number) CoerceToInt64(kind Kind) int64 {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
return n.AsInt64()
|
||||||
|
case Float64Kind:
|
||||||
|
return int64(n.AsFloat64())
|
||||||
|
default:
|
||||||
|
// you get what you deserve
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoerceToFloat64 casts the number to float64. May result in
|
||||||
|
// data/precision loss.
|
||||||
|
func (n *Number) CoerceToFloat64(kind Kind) float64 {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
return float64(n.AsInt64())
|
||||||
|
case Float64Kind:
|
||||||
|
return n.AsFloat64()
|
||||||
|
default:
|
||||||
|
// you get what you deserve
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - set
|
||||||
|
|
||||||
|
// SetNumber sets the number to the passed number. Both should be of
|
||||||
|
// the same kind.
|
||||||
|
func (n *Number) SetNumber(nn Number) {
|
||||||
|
*n.AsRawPtr() = nn.AsRaw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRaw sets the number to the passed raw value. Both number and the
|
||||||
|
// raw number should represent the same kind.
|
||||||
|
func (n *Number) SetRaw(r uint64) {
|
||||||
|
*n.AsRawPtr() = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInt64 assumes that the number contains an int64 and sets it to
|
||||||
|
// the passed value.
|
||||||
|
func (n *Number) SetInt64(i int64) {
|
||||||
|
*n.AsInt64Ptr() = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFloat64 assumes that the number contains a float64 and sets it
|
||||||
|
// to the passed value.
|
||||||
|
func (n *Number) SetFloat64(f float64) {
|
||||||
|
*n.AsFloat64Ptr() = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// - set atomic
|
||||||
|
|
||||||
|
// SetNumberAtomic sets the number to the passed number
|
||||||
|
// atomically. Both should be of the same kind.
|
||||||
|
func (n *Number) SetNumberAtomic(nn Number) {
|
||||||
|
atomic.StoreUint64(n.AsRawPtr(), nn.AsRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawAtomic sets the number to the passed raw value
|
||||||
|
// atomically. Both number and the raw number should represent the
|
||||||
|
// same kind.
|
||||||
|
func (n *Number) SetRawAtomic(r uint64) {
|
||||||
|
atomic.StoreUint64(n.AsRawPtr(), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInt64Atomic assumes that the number contains an int64 and sets
|
||||||
|
// it to the passed value atomically.
|
||||||
|
func (n *Number) SetInt64Atomic(i int64) {
|
||||||
|
atomic.StoreInt64(n.AsInt64Ptr(), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFloat64Atomic assumes that the number contains a float64 and
|
||||||
|
// sets it to the passed value atomically.
|
||||||
|
func (n *Number) SetFloat64Atomic(f float64) {
|
||||||
|
atomic.StoreUint64(n.AsRawPtr(), internal.Float64ToRaw(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// - swap
|
||||||
|
|
||||||
|
// SwapNumber sets the number to the passed number and returns the old
|
||||||
|
// number. Both this number and the passed number should be of the
|
||||||
|
// same kind.
|
||||||
|
func (n *Number) SwapNumber(nn Number) Number {
|
||||||
|
old := *n
|
||||||
|
n.SetNumber(nn)
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapRaw sets the number to the passed raw value and returns the old
|
||||||
|
// raw value. Both number and the raw number should represent the same
|
||||||
|
// kind.
|
||||||
|
func (n *Number) SwapRaw(r uint64) uint64 {
|
||||||
|
old := n.AsRaw()
|
||||||
|
n.SetRaw(r)
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapInt64 assumes that the number contains an int64, sets it to the
|
||||||
|
// passed value and returns the old int64 value.
|
||||||
|
func (n *Number) SwapInt64(i int64) int64 {
|
||||||
|
old := n.AsInt64()
|
||||||
|
n.SetInt64(i)
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapFloat64 assumes that the number contains an float64, sets it to
|
||||||
|
// the passed value and returns the old float64 value.
|
||||||
|
func (n *Number) SwapFloat64(f float64) float64 {
|
||||||
|
old := n.AsFloat64()
|
||||||
|
n.SetFloat64(f)
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// - swap atomic
|
||||||
|
|
||||||
|
// SwapNumberAtomic sets the number to the passed number and returns
|
||||||
|
// the old number atomically. Both this number and the passed number
|
||||||
|
// should be of the same kind.
|
||||||
|
func (n *Number) SwapNumberAtomic(nn Number) Number {
|
||||||
|
return NewNumberFromRaw(atomic.SwapUint64(n.AsRawPtr(), nn.AsRaw()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapRawAtomic sets the number to the passed raw value and returns
|
||||||
|
// the old raw value atomically. Both number and the raw number should
|
||||||
|
// represent the same kind.
|
||||||
|
func (n *Number) SwapRawAtomic(r uint64) uint64 {
|
||||||
|
return atomic.SwapUint64(n.AsRawPtr(), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapInt64Atomic assumes that the number contains an int64, sets it
|
||||||
|
// to the passed value and returns the old int64 value atomically.
|
||||||
|
func (n *Number) SwapInt64Atomic(i int64) int64 {
|
||||||
|
return atomic.SwapInt64(n.AsInt64Ptr(), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapFloat64Atomic assumes that the number contains an float64, sets
|
||||||
|
// it to the passed value and returns the old float64 value
|
||||||
|
// atomically.
|
||||||
|
func (n *Number) SwapFloat64Atomic(f float64) float64 {
|
||||||
|
return internal.RawToFloat64(atomic.SwapUint64(n.AsRawPtr(), internal.Float64ToRaw(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// - add
|
||||||
|
|
||||||
|
// AddNumber assumes that this and the passed number are of the passed
|
||||||
|
// kind and adds the passed number to this number.
|
||||||
|
func (n *Number) AddNumber(kind Kind, nn Number) {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
n.AddInt64(nn.AsInt64())
|
||||||
|
case Float64Kind:
|
||||||
|
n.AddFloat64(nn.AsFloat64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRaw assumes that this number and the passed raw value are of the
|
||||||
|
// passed kind and adds the passed raw value to this number.
|
||||||
|
func (n *Number) AddRaw(kind Kind, r uint64) {
|
||||||
|
n.AddNumber(kind, NewNumberFromRaw(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInt64 assumes that the number contains an int64 and adds the
|
||||||
|
// passed int64 to it.
|
||||||
|
func (n *Number) AddInt64(i int64) {
|
||||||
|
*n.AsInt64Ptr() += i
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFloat64 assumes that the number contains a float64 and adds the
|
||||||
|
// passed float64 to it.
|
||||||
|
func (n *Number) AddFloat64(f float64) {
|
||||||
|
*n.AsFloat64Ptr() += f
|
||||||
|
}
|
||||||
|
|
||||||
|
// - add atomic
|
||||||
|
|
||||||
|
// AddNumberAtomic assumes that this and the passed number are of the
|
||||||
|
// passed kind and adds the passed number to this number atomically.
|
||||||
|
func (n *Number) AddNumberAtomic(kind Kind, nn Number) {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
n.AddInt64Atomic(nn.AsInt64())
|
||||||
|
case Float64Kind:
|
||||||
|
n.AddFloat64Atomic(nn.AsFloat64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRawAtomic assumes that this number and the passed raw value are
|
||||||
|
// of the passed kind and adds the passed raw value to this number
|
||||||
|
// atomically.
|
||||||
|
func (n *Number) AddRawAtomic(kind Kind, r uint64) {
|
||||||
|
n.AddNumberAtomic(kind, NewNumberFromRaw(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInt64Atomic assumes that the number contains an int64 and adds
|
||||||
|
// the passed int64 to it atomically.
|
||||||
|
func (n *Number) AddInt64Atomic(i int64) {
|
||||||
|
atomic.AddInt64(n.AsInt64Ptr(), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFloat64Atomic assumes that the number contains a float64 and
|
||||||
|
// adds the passed float64 to it atomically.
|
||||||
|
func (n *Number) AddFloat64Atomic(f float64) {
|
||||||
|
for {
|
||||||
|
o := n.AsFloat64Atomic()
|
||||||
|
if n.CompareAndSwapFloat64(o, o+f) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - compare and swap (atomic only)
|
||||||
|
|
||||||
|
// CompareAndSwapNumber does the atomic CAS operation on this
|
||||||
|
// number. This number and passed old and new numbers should be of the
|
||||||
|
// same kind.
|
||||||
|
func (n *Number) CompareAndSwapNumber(on, nn Number) bool {
|
||||||
|
return atomic.CompareAndSwapUint64(n.AsRawPtr(), on.AsRaw(), nn.AsRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareAndSwapRaw does the atomic CAS operation on this
|
||||||
|
// number. This number and passed old and new raw values should be of
|
||||||
|
// the same kind.
|
||||||
|
func (n *Number) CompareAndSwapRaw(or, nr uint64) bool {
|
||||||
|
return atomic.CompareAndSwapUint64(n.AsRawPtr(), or, nr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareAndSwapInt64 assumes that this number contains an int64 and
|
||||||
|
// does the atomic CAS operation on it.
|
||||||
|
func (n *Number) CompareAndSwapInt64(oi, ni int64) bool {
|
||||||
|
return atomic.CompareAndSwapInt64(n.AsInt64Ptr(), oi, ni)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareAndSwapFloat64 assumes that this number contains a float64 and
|
||||||
|
// does the atomic CAS operation on it.
|
||||||
|
func (n *Number) CompareAndSwapFloat64(of, nf float64) bool {
|
||||||
|
return atomic.CompareAndSwapUint64(n.AsRawPtr(), internal.Float64ToRaw(of), internal.Float64ToRaw(nf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// - compare
|
||||||
|
|
||||||
|
// CompareNumber compares two Numbers given their kind. Both numbers
|
||||||
|
// should have the same kind. This returns:
|
||||||
|
// 0 if the numbers are equal
|
||||||
|
// -1 if the subject `n` is less than the argument `nn`
|
||||||
|
// +1 if the subject `n` is greater than the argument `nn`
|
||||||
|
func (n *Number) CompareNumber(kind Kind, nn Number) int {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
return n.CompareInt64(nn.AsInt64())
|
||||||
|
case Float64Kind:
|
||||||
|
return n.CompareFloat64(nn.AsFloat64())
|
||||||
|
default:
|
||||||
|
// you get what you deserve
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareRaw compares two numbers, where one is input as a raw
|
||||||
|
// uint64, interpreting both values as a `kind` of number.
|
||||||
|
func (n *Number) CompareRaw(kind Kind, r uint64) int {
|
||||||
|
return n.CompareNumber(kind, NewNumberFromRaw(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareInt64 assumes that the Number contains an int64 and performs
|
||||||
|
// a comparison between the value and the other value. It returns the
|
||||||
|
// typical result of the compare function: -1 if the value is less
|
||||||
|
// than the other, 0 if both are equal, 1 if the value is greater than
|
||||||
|
// the other.
|
||||||
|
func (n *Number) CompareInt64(i int64) int {
|
||||||
|
this := n.AsInt64()
|
||||||
|
if this < i {
|
||||||
|
return -1
|
||||||
|
} else if this > i {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareFloat64 assumes that the Number contains a float64 and
|
||||||
|
// performs a comparison between the value and the other value. It
|
||||||
|
// returns the typical result of the compare function: -1 if the value
|
||||||
|
// is less than the other, 0 if both are equal, 1 if the value is
|
||||||
|
// greater than the other.
|
||||||
|
//
|
||||||
|
// Do not compare NaN values.
|
||||||
|
func (n *Number) CompareFloat64(f float64) int {
|
||||||
|
this := n.AsFloat64()
|
||||||
|
if this < f {
|
||||||
|
return -1
|
||||||
|
} else if this > f {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// - relations to zero
|
||||||
|
|
||||||
|
// IsPositive returns true if the actual value is greater than zero.
|
||||||
|
func (n *Number) IsPositive(kind Kind) bool {
|
||||||
|
return n.compareWithZero(kind) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNegative returns true if the actual value is less than zero.
|
||||||
|
func (n *Number) IsNegative(kind Kind) bool {
|
||||||
|
return n.compareWithZero(kind) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the actual value is equal to zero.
|
||||||
|
func (n *Number) IsZero(kind Kind) bool {
|
||||||
|
return n.compareWithZero(kind) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// - misc
|
||||||
|
|
||||||
|
// Emit returns a string representation of the raw value of the
|
||||||
|
// Number. A %d is used for integral values, %f for floating point
|
||||||
|
// values.
|
||||||
|
func (n *Number) Emit(kind Kind) string {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
return fmt.Sprintf("%d", n.AsInt64())
|
||||||
|
case Float64Kind:
|
||||||
|
return fmt.Sprintf("%f", n.AsFloat64())
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInterface returns the number as an interface{}, typically used
|
||||||
|
// for Kind-correct JSON conversion.
|
||||||
|
func (n *Number) AsInterface(kind Kind) interface{} {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
return n.AsInt64()
|
||||||
|
case Float64Kind:
|
||||||
|
return n.AsFloat64()
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - private stuff
|
||||||
|
|
||||||
|
func (n *Number) compareWithZero(kind Kind) int {
|
||||||
|
switch kind {
|
||||||
|
case Int64Kind:
|
||||||
|
return n.CompareInt64(0)
|
||||||
|
case Float64Kind:
|
||||||
|
return n.CompareFloat64(0.)
|
||||||
|
default:
|
||||||
|
// you get what you deserve
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.opentelemetry.io/otel/metric/number"
|
||||||
|
"go.opentelemetry.io/otel/metric/unit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Descriptor contains all the settings that describe an instrument,
|
||||||
|
// including its name, metric kind, number kind, and the configurable
|
||||||
|
// options.
|
||||||
|
type Descriptor struct {
|
||||||
|
name string
|
||||||
|
instrumentKind InstrumentKind
|
||||||
|
numberKind number.Kind
|
||||||
|
description string
|
||||||
|
unit unit.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDescriptor returns a Descriptor with the given contents.
|
||||||
|
func NewDescriptor(name string, ikind InstrumentKind, nkind number.Kind, description string, unit unit.Unit) Descriptor {
|
||||||
|
return Descriptor{
|
||||||
|
name: name,
|
||||||
|
instrumentKind: ikind,
|
||||||
|
numberKind: nkind,
|
||||||
|
description: description,
|
||||||
|
unit: unit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the metric instrument's name.
|
||||||
|
func (d Descriptor) Name() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentKind returns the specific kind of instrument.
|
||||||
|
func (d Descriptor) InstrumentKind() InstrumentKind {
|
||||||
|
return d.instrumentKind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description provides a human-readable description of the metric
|
||||||
|
// instrument.
|
||||||
|
func (d Descriptor) Description() string {
|
||||||
|
return d.description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit describes the units of the metric instrument. Unitless
|
||||||
|
// metrics return the empty string.
|
||||||
|
func (d Descriptor) Unit() unit.Unit {
|
||||||
|
return d.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberKind returns whether this instrument is declared over int64,
|
||||||
|
// float64, or uint64 values.
|
||||||
|
func (d Descriptor) NumberKind() number.Kind {
|
||||||
|
return d.numberKind
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:generate stringer -type=InstrumentKind
|
||||||
|
|
||||||
|
package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
|
||||||
|
// InstrumentKind describes the kind of instrument.
|
||||||
|
type InstrumentKind int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HistogramInstrumentKind indicates a Histogram instrument.
|
||||||
|
HistogramInstrumentKind InstrumentKind = iota
|
||||||
|
// GaugeObserverInstrumentKind indicates an GaugeObserver instrument.
|
||||||
|
GaugeObserverInstrumentKind
|
||||||
|
|
||||||
|
// CounterInstrumentKind indicates a Counter instrument.
|
||||||
|
CounterInstrumentKind
|
||||||
|
// UpDownCounterInstrumentKind indicates a UpDownCounter instrument.
|
||||||
|
UpDownCounterInstrumentKind
|
||||||
|
|
||||||
|
// CounterObserverInstrumentKind indicates a CounterObserver instrument.
|
||||||
|
CounterObserverInstrumentKind
|
||||||
|
// UpDownCounterObserverInstrumentKind indicates a UpDownCounterObserver
|
||||||
|
// instrument.
|
||||||
|
UpDownCounterObserverInstrumentKind
|
||||||
|
)
|
||||||
|
|
||||||
|
// Synchronous returns whether this is a synchronous kind of instrument.
|
||||||
|
func (k InstrumentKind) Synchronous() bool {
|
||||||
|
switch k {
|
||||||
|
case CounterInstrumentKind, UpDownCounterInstrumentKind, HistogramInstrumentKind:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronous returns whether this is an asynchronous kind of instrument.
|
||||||
|
func (k InstrumentKind) Asynchronous() bool {
|
||||||
|
return !k.Synchronous()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding returns whether this kind of instrument adds its inputs (as opposed to Grouping).
|
||||||
|
func (k InstrumentKind) Adding() bool {
|
||||||
|
switch k {
|
||||||
|
case CounterInstrumentKind, UpDownCounterInstrumentKind, CounterObserverInstrumentKind, UpDownCounterObserverInstrumentKind:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grouping returns whether this kind of instrument groups its inputs (as opposed to Adding).
|
||||||
|
func (k InstrumentKind) Grouping() bool {
|
||||||
|
return !k.Adding()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monotonic returns whether this kind of instrument exposes a non-decreasing sum.
|
||||||
|
func (k InstrumentKind) Monotonic() bool {
|
||||||
|
switch k {
|
||||||
|
case CounterInstrumentKind, CounterObserverInstrumentKind:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrecomputedSum returns whether this kind of instrument receives precomputed sums.
|
||||||
|
func (k InstrumentKind) PrecomputedSum() bool {
|
||||||
|
return k.Adding() && k.Asynchronous()
|
||||||
|
}
|
28
vendor/go.opentelemetry.io/otel/metric/sdkapi/instrumentkind_string.go
generated
vendored
Normal file
28
vendor/go.opentelemetry.io/otel/metric/sdkapi/instrumentkind_string.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by "stringer -type=InstrumentKind"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package sdkapi
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[HistogramInstrumentKind-0]
|
||||||
|
_ = x[GaugeObserverInstrumentKind-1]
|
||||||
|
_ = x[CounterInstrumentKind-2]
|
||||||
|
_ = x[UpDownCounterInstrumentKind-3]
|
||||||
|
_ = x[CounterObserverInstrumentKind-4]
|
||||||
|
_ = x[UpDownCounterObserverInstrumentKind-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _InstrumentKind_name = "HistogramInstrumentKindGaugeObserverInstrumentKindCounterInstrumentKindUpDownCounterInstrumentKindCounterObserverInstrumentKindUpDownCounterObserverInstrumentKind"
|
||||||
|
|
||||||
|
var _InstrumentKind_index = [...]uint8{0, 23, 50, 71, 98, 127, 162}
|
||||||
|
|
||||||
|
func (i InstrumentKind) String() string {
|
||||||
|
if i < 0 || i >= InstrumentKind(len(_InstrumentKind_index)-1) {
|
||||||
|
return "InstrumentKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _InstrumentKind_name[_InstrumentKind_index[i]:_InstrumentKind_index[i+1]]
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric/number"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noopInstrument struct {
|
||||||
|
descriptor Descriptor
|
||||||
|
}
|
||||||
|
type noopSyncInstrument struct{ noopInstrument }
|
||||||
|
type noopAsyncInstrument struct{ noopInstrument }
|
||||||
|
|
||||||
|
var _ SyncImpl = noopSyncInstrument{}
|
||||||
|
var _ AsyncImpl = noopAsyncInstrument{}
|
||||||
|
|
||||||
|
// NewNoopSyncInstrument returns a No-op implementation of the
|
||||||
|
// synchronous instrument interface.
|
||||||
|
func NewNoopSyncInstrument() SyncImpl {
|
||||||
|
return noopSyncInstrument{
|
||||||
|
noopInstrument{
|
||||||
|
descriptor: Descriptor{
|
||||||
|
instrumentKind: CounterInstrumentKind,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNoopAsyncInstrument returns a No-op implementation of the
|
||||||
|
// asynchronous instrument interface.
|
||||||
|
func NewNoopAsyncInstrument() AsyncImpl {
|
||||||
|
return noopAsyncInstrument{
|
||||||
|
noopInstrument{
|
||||||
|
descriptor: Descriptor{
|
||||||
|
instrumentKind: CounterObserverInstrumentKind,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopInstrument) Implementation() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n noopInstrument) Descriptor() Descriptor {
|
||||||
|
return n.descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopSyncInstrument) RecordOne(context.Context, number.Number, []attribute.KeyValue) {
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package sdkapi // import "go.opentelemetry.io/otel/metric/sdkapi"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric/number"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MeterImpl is the interface an SDK must implement to supply a Meter
|
||||||
|
// implementation.
|
||||||
|
type MeterImpl interface {
|
||||||
|
// RecordBatch atomically records a batch of measurements.
|
||||||
|
RecordBatch(ctx context.Context, labels []attribute.KeyValue, measurement ...Measurement)
|
||||||
|
|
||||||
|
// NewSyncInstrument returns a newly constructed
|
||||||
|
// synchronous instrument implementation or an error, should
|
||||||
|
// one occur.
|
||||||
|
NewSyncInstrument(descriptor Descriptor) (SyncImpl, error)
|
||||||
|
|
||||||
|
// NewAsyncInstrument returns a newly constructed
|
||||||
|
// asynchronous instrument implementation or an error, should
|
||||||
|
// one occur.
|
||||||
|
NewAsyncInstrument(
|
||||||
|
descriptor Descriptor,
|
||||||
|
runner AsyncRunner,
|
||||||
|
) (AsyncImpl, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentImpl is a common interface for synchronous and
|
||||||
|
// asynchronous instruments.
|
||||||
|
type InstrumentImpl interface {
|
||||||
|
// Implementation returns the underlying implementation of the
|
||||||
|
// instrument, which allows the implementation to gain access
|
||||||
|
// to its own representation especially from a `Measurement`.
|
||||||
|
Implementation() interface{}
|
||||||
|
|
||||||
|
// Descriptor returns a copy of the instrument's Descriptor.
|
||||||
|
Descriptor() Descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncImpl is the implementation-level interface to a generic
|
||||||
|
// synchronous instrument (e.g., Histogram and Counter instruments).
|
||||||
|
type SyncImpl interface {
|
||||||
|
InstrumentImpl
|
||||||
|
|
||||||
|
// RecordOne captures a single synchronous metric event.
|
||||||
|
RecordOne(ctx context.Context, number number.Number, labels []attribute.KeyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncImpl is an implementation-level interface to an
|
||||||
|
// asynchronous instrument (e.g., Observer instruments).
|
||||||
|
type AsyncImpl interface {
|
||||||
|
InstrumentImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncRunner is expected to convert into an AsyncSingleRunner or an
|
||||||
|
// AsyncBatchRunner. SDKs will encounter an error if the AsyncRunner
|
||||||
|
// does not satisfy one of these interfaces.
|
||||||
|
type AsyncRunner interface {
|
||||||
|
// AnyRunner is a non-exported method with no functional use
|
||||||
|
// other than to make this a non-empty interface.
|
||||||
|
AnyRunner()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncSingleRunner is an interface implemented by single-observer
|
||||||
|
// callbacks.
|
||||||
|
type AsyncSingleRunner interface {
|
||||||
|
// Run accepts a single instrument and function for capturing
|
||||||
|
// observations of that instrument. Each call to the function
|
||||||
|
// receives one captured observation. (The function accepts
|
||||||
|
// multiple observations so the same implementation can be
|
||||||
|
// used for batch runners.)
|
||||||
|
Run(ctx context.Context, single AsyncImpl, capture func([]attribute.KeyValue, ...Observation))
|
||||||
|
|
||||||
|
AsyncRunner
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncBatchRunner is an interface implemented by batch-observer
|
||||||
|
// callbacks.
|
||||||
|
type AsyncBatchRunner interface {
|
||||||
|
// Run accepts a function for capturing observations of
|
||||||
|
// multiple instruments.
|
||||||
|
Run(ctx context.Context, capture func([]attribute.KeyValue, ...Observation))
|
||||||
|
|
||||||
|
AsyncRunner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMeasurement constructs a single observation, a binding between
|
||||||
|
// an asynchronous instrument and a number.
|
||||||
|
func NewMeasurement(instrument SyncImpl, number number.Number) Measurement {
|
||||||
|
return Measurement{
|
||||||
|
instrument: instrument,
|
||||||
|
number: number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement is a low-level type used with synchronous instruments
|
||||||
|
// as a direct interface to the SDK via `RecordBatch`.
|
||||||
|
type Measurement struct {
|
||||||
|
// number needs to be aligned for 64-bit atomic operations.
|
||||||
|
number number.Number
|
||||||
|
instrument SyncImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncImpl returns the instrument that created this measurement.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (m Measurement) SyncImpl() SyncImpl {
|
||||||
|
return m.instrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number returns a number recorded in this measurement.
|
||||||
|
func (m Measurement) Number() number.Number {
|
||||||
|
return m.number
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewObservation constructs a single observation, a binding between
|
||||||
|
// an asynchronous instrument and a number.
|
||||||
|
func NewObservation(instrument AsyncImpl, number number.Number) Observation {
|
||||||
|
return Observation{
|
||||||
|
instrument: instrument,
|
||||||
|
number: number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observation is a low-level type used with asynchronous instruments
|
||||||
|
// as a direct interface to the SDK via `BatchObserver`.
|
||||||
|
type Observation struct {
|
||||||
|
// number needs to be aligned for 64-bit atomic operations.
|
||||||
|
number number.Number
|
||||||
|
instrument AsyncImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncImpl returns the instrument that created this observation.
|
||||||
|
// This returns an implementation-level object for use by the SDK,
|
||||||
|
// users should not refer to this.
|
||||||
|
func (m Observation) AsyncImpl() AsyncImpl {
|
||||||
|
return m.instrument
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number returns a number recorded in this observation.
|
||||||
|
func (m Observation) Number() number.Number {
|
||||||
|
return m.number
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package unit provides units.
|
||||||
|
//
|
||||||
|
// This package is currently in a pre-GA phase. Backwards incompatible changes
|
||||||
|
// may be introduced in subsequent minor version releases as we work to track
|
||||||
|
// the evolving OpenTelemetry specification and user feedback.
|
||||||
|
package unit // import "go.opentelemetry.io/otel/metric/unit"
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package unit // import "go.opentelemetry.io/otel/metric/unit"
|
||||||
|
|
||||||
|
type Unit string
|
||||||
|
|
||||||
|
// Units defined by OpenTelemetry.
|
||||||
|
const (
|
||||||
|
Dimensionless Unit = "1"
|
||||||
|
Bytes Unit = "By"
|
||||||
|
Milliseconds Unit = "ms"
|
||||||
|
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue