chore: Bump dskit dependency (#20518)
This commit is contained in:
@@ -53,7 +53,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2
|
||||
github.com/grafana/dskit v0.0.0-20251218155159-03c38747d0d9
|
||||
github.com/grafana/dskit v0.0.0-20260120145137-7f862deba99d
|
||||
github.com/grafana/go-gelf/v2 v2.0.1
|
||||
github.com/grafana/gomemcache v0.0.0-20251127154401-74f93547077b
|
||||
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853
|
||||
@@ -238,7 +238,7 @@ require (
|
||||
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
|
||||
github.com/jaegertracing/jaeger-idl v0.6.0 // indirect
|
||||
github.com/kamstrup/intmap v0.5.2 // indirect
|
||||
github.com/klauspost/asmfmt v1.3.2 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
@@ -300,25 +300,25 @@ require (
|
||||
go.opentelemetry.io/collector/featuregate v1.49.0 // indirect
|
||||
go.opentelemetry.io/collector/pipeline v1.48.0 // indirect
|
||||
go.opentelemetry.io/collector/processor v1.48.0 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.14.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.15.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.15.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
@@ -455,7 +455,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.15.0 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.15.1 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
@@ -470,9 +470,9 @@ require (
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.6 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.6 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.7 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.7 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.6 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/collector/semconv v0.128.0 // indirect
|
||||
|
||||
@@ -648,8 +648,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4Vp68H0tp/0iN17DM2ehRo1rLEdOFe/gB8I=
|
||||
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw=
|
||||
github.com/grafana/dskit v0.0.0-20251218155159-03c38747d0d9 h1:fU+fiBDzF7fag+HAqlznVftLTguUD1ptxLRwCd2HWUI=
|
||||
github.com/grafana/dskit v0.0.0-20251218155159-03c38747d0d9/go.mod h1:01qUloWNYQRbj71VJBTYd0AMoa8wBk3mm3t/vK5bdSo=
|
||||
github.com/grafana/dskit v0.0.0-20260120145137-7f862deba99d h1:V+19BJI3h0yqqQ94lmLgorGy3J+xmme8f4w3GFnYbew=
|
||||
github.com/grafana/dskit v0.0.0-20260120145137-7f862deba99d/go.mod h1:iHZFIKfU//1Uto0gmgmGBB1oVUBcTiepUFslatTkrAs=
|
||||
github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak=
|
||||
github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90=
|
||||
github.com/grafana/gomemcache v0.0.0-20251127154401-74f93547077b h1:5qp8/5YPt/Z2RW5QHsxvwE05+LWQYIXydP2MwOkMfb8=
|
||||
@@ -736,8 +736,8 @@ github.com/influxdata/telegraf v1.37.0 h1:IIUwAB+Al2F0sC+8ppi5Ib2t9Bh/SdQNqvkSg6
|
||||
github.com/influxdata/telegraf v1.37.0/go.mod h1:Il11p+a3xXUvn0x/gK5EsAvsHWv4rf3fJmn17/bYUgY=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.3.5 h1:6fHArdV1lf50iRhCkCP7wkvGwWzVwi+l9w1t5mwkOa8=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.3.5/go.mod h1:nUGHP4kZHAZngCVr4v6C8nuargFrtvt7GrzH/hqn7c4=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE=
|
||||
github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k=
|
||||
github.com/jaegertracing/jaeger-idl v0.6.0 h1:LOVQfVby9ywdMPI9n3hMwKbyLVV3BL1XH2QqsP5KTMk=
|
||||
github.com/jaegertracing/jaeger-idl v0.6.0/go.mod h1:mpW0lZfG907/+o5w5OlnNnig7nHJGT3SfKmRqC42HGQ=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||
@@ -1011,8 +1011,8 @@ github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTU
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
|
||||
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
|
||||
github.com/prometheus/exporter-toolkit v0.15.0 h1:Pcle5sSViwR1x0gdPd0wtYrPQENBieQAM7TmT0qtb2U=
|
||||
github.com/prometheus/exporter-toolkit v0.15.0/go.mod h1:OyRWd2iTo6Xge9Kedvv0IhCrJSBu36JCfJ2yVniRIYk=
|
||||
github.com/prometheus/exporter-toolkit v0.15.1 h1:XrGGr/qWl8Gd+pqJqTkNLww9eG8vR/CoRk0FubOKfLE=
|
||||
github.com/prometheus/exporter-toolkit v0.15.1/go.mod h1:P/NR9qFRGbCFgpklyhix9F6v6fFr/VQB/CVsrMDGKo4=
|
||||
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
|
||||
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
@@ -1176,12 +1176,12 @@ go.einride.tech/aip v0.73.0 h1:bPo4oqBo2ZQeBKo4ZzLb1kxYXTY1ysJhpvQyfuGzvps=
|
||||
go.einride.tech/aip v0.73.0/go.mod h1:Mj7rFbmXEgw0dq1dqJ7JGMvYCZZVxmGOR3S4ZcV5LvQ=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ=
|
||||
go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8=
|
||||
go.etcd.io/etcd/api/v3 v3.6.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0=
|
||||
go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q=
|
||||
go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE=
|
||||
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
|
||||
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -1229,59 +1229,59 @@ go.opentelemetry.io/collector/processor/xprocessor v0.142.0 h1:7a1Crxrd5iBMVnebT
|
||||
go.opentelemetry.io/collector/processor/xprocessor v0.142.0/go.mod h1:LY/GS2DiJILJKS3ynU3eOLLWSP8CmN1FtdpAMsVV8AU=
|
||||
go.opentelemetry.io/collector/semconv v0.128.0 h1:MzYOz7Vgb3Kf5D7b49pqqgeUhEmOCuT10bIXb/Cc+k4=
|
||||
go.opentelemetry.io/collector/semconv v0.128.0/go.mod h1:OPXer4l43X23cnjLXIZnRj/qQOjSuq4TgBLI76P9hns=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 h1:/Rij/t18Y7rUayNg7Id6rPrEnHgorxYabm2E6wUdPP4=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.63.0/go.mod h1:AdyDPn6pkbkt2w01n3BubRVk7xAsCRq1Yg1mpfyA/0E=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.64.0 h1:7TYhBCu6Xz6vDJGNtEslWZLuuX2IJ/aH50hBY4MVeUg=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.64.0/go.mod h1:tHQctZfAe7e4PBPGyt3kae6mQFXNpj+iiDJa3ithM50=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 h1:NLnZybb9KkfMXPwZhd5diBYJoVxiO9Qa06dacEA7ySY=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.63.0/go.mod h1:OvRg7gm5WRSCtxzGSsrFHbDLToYlStHNZQ+iPNIyD6g=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.64.0 h1:9pzPj3RFyKOxBAMkM2w84LpT+rdHam1XoFA+QhARiRw=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.64.0/go.mod h1:hlVZx1btWH0XTfXpuGX9dsquB50s+tc3fYFOO5elo2M=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0 h1:OXSUzgmIFkcC4An+mv+lqqZSndTffXpjAyoR+1f8k/A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0/go.mod h1:1A4GVLFIm54HFqVdOpWmukap7rgb0frrE3zWXohLPdM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0 h1:RcFp4UxGTE2VQQ0M7s24YRUShEJ5D5JDnd5g2EaTh6E=
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0/go.mod h1:y6oMwgsv+yWYCLRigU6Pp07/x4KZUEh8LIPTSUnQKbQ=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0/go.mod h1:nWFP7C+T8TygkTjJ7mAyEaFaE7wNfms3nV/vexZ6qt0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE=
|
||||
go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
|
||||
go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.61.0 h1:cCyZS4dr67d30uDyh8etKM2QyDsQ4zC9ds3bdbrVoD0=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.61.0/go.mod h1:iivMuj3xpR2DkUrUya3TPS/Z9h3dz7h01GxU+fQBRNg=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 h1:0BSddrtQqLEylcErkeFrJBmwFzcqfQq9+/uxfTZq+HE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0/go.mod h1:87sjYuAPzaRCtdd09GU5gM1U9wQLrrcYrm77mh5EBoc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g=
|
||||
go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY=
|
||||
go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
|
||||
go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE=
|
||||
go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ingester
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@@ -13,8 +14,6 @@ import (
|
||||
|
||||
"go.uber.org/atomic"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
"github.com/grafana/dskit/backoff"
|
||||
@@ -1728,6 +1727,10 @@ func (r *readRingMock) ZonesCount() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (r *readRingMock) Zones() []string {
|
||||
return []string{"zone1"}
|
||||
}
|
||||
|
||||
func (r *readRingMock) HealthyInstancesInZoneCount() int {
|
||||
return len(r.replicationSet.Instances)
|
||||
}
|
||||
|
||||
+9
-2
@@ -2260,7 +2260,10 @@ func (t *Loki) initPartitionRing() (services.Service, error) {
|
||||
return nil, fmt.Errorf("creating KV store for partitions ring watcher: %w", err)
|
||||
}
|
||||
|
||||
t.PartitionRingWatcher = ring.NewPartitionRingWatcher(ingester.PartitionRingName, ingester.PartitionRingKey, kvClient, util_log.Logger, prometheus.WrapRegistererWithPrefix("loki_", prometheus.DefaultRegisterer))
|
||||
ringOptions := ring.DefaultPartitionRingOptions()
|
||||
ringOptions.ShuffleShardCacheSize = 1000
|
||||
|
||||
t.PartitionRingWatcher = ring.NewPartitionRingWatcherWithOptions(ingester.PartitionRingName, ingester.PartitionRingKey, kvClient, ringOptions, util_log.Logger, prometheus.WrapRegistererWithPrefix("loki_", prometheus.DefaultRegisterer))
|
||||
t.partitionRing = ring.NewPartitionInstanceRing(t.PartitionRingWatcher, t.ring, t.Cfg.Ingester.LifecyclerConfig.RingConfig.HeartbeatTimeout)
|
||||
|
||||
// Expose a web page to view the partitions ring state.
|
||||
@@ -2388,10 +2391,14 @@ func (t *Loki) initDataObjConsumerPartitionRing() (services.Service, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create KV store for dataobj ring watcher: %w", err)
|
||||
}
|
||||
t.DataObjConsumerPartitionRingWatcher = ring.NewPartitionRingWatcher(
|
||||
ringOptions := ring.DefaultPartitionRingOptions()
|
||||
ringOptions.ShuffleShardCacheSize = 1000
|
||||
|
||||
t.DataObjConsumerPartitionRingWatcher = ring.NewPartitionRingWatcherWithOptions(
|
||||
consumer.PartitionRingName,
|
||||
consumer.PartitionRingKey,
|
||||
kvClient,
|
||||
ringOptions,
|
||||
util_log.Logger,
|
||||
prometheus.WrapRegistererWithPrefix("loki_", prometheus.DefaultRegisterer),
|
||||
)
|
||||
|
||||
@@ -196,6 +196,11 @@ func (f *fakeRing) WritableInstancesWithTokensInZoneCount(zone string) int {
|
||||
return args.Int(0)
|
||||
}
|
||||
|
||||
// WritableInstancesWithTokensInZoneCount returns the number of writable instances in the ring that are registered in given zone and have tokens.
|
||||
func (f *fakeRing) Zones() []string {
|
||||
return []string{"zone1"}
|
||||
}
|
||||
|
||||
func (f *fakeRing) Get(
|
||||
key uint32,
|
||||
op ring.Operation,
|
||||
|
||||
@@ -538,6 +538,10 @@ func (r *readRingMock) GetSubringForOperationStates(_ ring.Operation) ring.ReadR
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *readRingMock) Zones() []string {
|
||||
return []string{"zone1"}
|
||||
}
|
||||
|
||||
func mockReadRingWithOneActiveIngester() *readRingMock {
|
||||
return newReadRingMock([]ring.InstanceDesc{
|
||||
{Addr: "test", Timestamp: time.Now().UnixNano(), State: ring.ACTIVE, Tokens: []uint32{1, 2, 3}},
|
||||
|
||||
@@ -151,6 +151,10 @@ func (r *readRingMock) GetSubringForOperationStates(_ ring.Operation) ring.ReadR
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *readRingMock) Zones() []string {
|
||||
return []string{"zone1"}
|
||||
}
|
||||
|
||||
type readLifecyclerMock struct {
|
||||
mock.Mock
|
||||
addr string
|
||||
|
||||
+17
-1
@@ -360,6 +360,13 @@ func (l *BasicLifecycler) registerInstance(ctx context.Context) error {
|
||||
l.currInstanceDesc = &instanceDesc
|
||||
l.currState.Unlock()
|
||||
|
||||
// Initialize the read-only metric to reflect the current state after registration.
|
||||
if instanceDesc.ReadOnly {
|
||||
l.metrics.readOnly.Set(1)
|
||||
} else {
|
||||
l.metrics.readOnly.Set(0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -454,6 +461,7 @@ func (l *BasicLifecycler) unregisterInstance(ctx context.Context) error {
|
||||
|
||||
l.metrics.tokensToOwn.Set(0)
|
||||
l.metrics.tokensOwned.Set(0)
|
||||
l.metrics.readOnly.Set(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -558,9 +566,17 @@ func (l *BasicLifecycler) changeReadOnlyState(ctx context.Context, readOnly bool
|
||||
if err != nil {
|
||||
from, _ := l.GetReadOnlyState()
|
||||
level.Warn(l.logger).Log("msg", "failed to change instance read-only state in the ring", "from", from, "to", readOnly, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
// Update the metric to reflect the current state.
|
||||
if readOnly {
|
||||
l.metrics.readOnly.Set(1)
|
||||
} else {
|
||||
l.metrics.readOnly.Set(0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// run a function within the lifecycler service goroutine.
|
||||
|
||||
+6
@@ -9,6 +9,7 @@ type BasicLifecyclerMetrics struct {
|
||||
heartbeats prometheus.Counter
|
||||
tokensOwned prometheus.Gauge
|
||||
tokensToOwn prometheus.Gauge
|
||||
readOnly prometheus.Gauge
|
||||
}
|
||||
|
||||
func NewBasicLifecyclerMetrics(ringName string, reg prometheus.Registerer) *BasicLifecyclerMetrics {
|
||||
@@ -28,5 +29,10 @@ func NewBasicLifecyclerMetrics(ringName string, reg prometheus.Registerer) *Basi
|
||||
Help: "The number of tokens to own in the ring.",
|
||||
ConstLabels: prometheus.Labels{"name": ringName},
|
||||
}),
|
||||
readOnly: promauto.With(reg).NewGauge(prometheus.GaugeOpts{
|
||||
Name: "lifecycler_read_only",
|
||||
Help: "Set to 1 if this lifecycler's instance entry is in read-only state.",
|
||||
ConstLabels: prometheus.Labels{"name": ringName},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -541,7 +541,7 @@ func (i *Lifecycler) Zones() []string {
|
||||
func (i *Lifecycler) loop(ctx context.Context) error {
|
||||
// First, see if we exist in the cluster, update our state to match if we do,
|
||||
// and add ourselves (without tokens) if we don't.
|
||||
if err := i.initRing(context.Background()); err != nil {
|
||||
if err := i.initRing(ctx); err != nil {
|
||||
return errors.Wrapf(err, "failed to join the ring %s", i.RingName)
|
||||
}
|
||||
|
||||
@@ -564,14 +564,14 @@ func (i *Lifecycler) loop(ctx context.Context) error {
|
||||
if i.cfg.ObservePeriod > 0 {
|
||||
// let's observe the ring. By using JOINING state, this ingester will be ignored by LEAVING
|
||||
// ingesters, but we also signal that it is not fully functional yet.
|
||||
if err := i.autoJoin(context.Background(), JOINING); err != nil {
|
||||
if err := i.autoJoin(ctx, JOINING); err != nil {
|
||||
return errors.Wrapf(err, "failed to pick tokens in the KV store, ring: %s", i.RingName)
|
||||
}
|
||||
|
||||
level.Info(i.logger).Log("msg", "observing tokens before going ACTIVE", "ring", i.RingName)
|
||||
observeChan = time.After(i.cfg.ObservePeriod)
|
||||
} else {
|
||||
if err := i.autoJoin(context.Background(), ACTIVE); err != nil {
|
||||
if err := i.autoJoin(ctx, ACTIVE); err != nil {
|
||||
return errors.Wrapf(err, "failed to pick tokens in the KV store, ring: %s", i.RingName)
|
||||
}
|
||||
}
|
||||
@@ -586,10 +586,10 @@ func (i *Lifecycler) loop(ctx context.Context) error {
|
||||
level.Error(i.logger).Log("msg", "unexpected state while observing tokens", "state", s, "ring", i.RingName)
|
||||
}
|
||||
|
||||
if i.verifyTokens(context.Background()) {
|
||||
if i.verifyTokens(ctx) {
|
||||
level.Info(i.logger).Log("msg", "token verification successful", "ring", i.RingName)
|
||||
|
||||
err := i.changeState(context.Background(), ACTIVE)
|
||||
err := i.changeState(ctx, ACTIVE)
|
||||
if err != nil {
|
||||
level.Error(i.logger).Log("msg", "failed to set state to ACTIVE", "ring", i.RingName, "err", err)
|
||||
}
|
||||
@@ -601,7 +601,7 @@ func (i *Lifecycler) loop(ctx context.Context) error {
|
||||
|
||||
case <-heartbeatTickerChan:
|
||||
i.lifecyclerMetrics.consulHeartbeats.Inc()
|
||||
if err := i.updateConsul(context.Background()); err != nil {
|
||||
if err := i.updateConsul(ctx); err != nil {
|
||||
level.Error(i.logger).Log("msg", "failed to write to the KV store, sleeping", "ring", i.RingName, "err", err)
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -41,6 +41,9 @@ type PartitionRing struct {
|
||||
|
||||
// activePartitionsCount is a saved count of active partitions to avoid recomputing it.
|
||||
activePartitionsCount int
|
||||
|
||||
// opts is used to propagate the options to sub rings when shuffle sharding.
|
||||
opts PartitionRingOptions
|
||||
}
|
||||
|
||||
// PartitionRingOptions holds optional configuration parameters for creating a PartitionRing.
|
||||
@@ -77,6 +80,7 @@ func NewPartitionRingWithOptions(desc PartitionRingDesc, opts PartitionRingOptio
|
||||
ownersByPartition: desc.ownersByPartition(),
|
||||
activePartitionsCount: desc.activePartitionsCount(),
|
||||
shuffleShardCache: shuffleShardCache,
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -281,7 +285,7 @@ func (r *PartitionRing) shuffleShard(identifier string, size int, lookbackPeriod
|
||||
}
|
||||
}
|
||||
|
||||
return NewPartitionRing(r.desc.WithPartitions(result))
|
||||
return NewPartitionRingWithOptions(r.desc.WithPartitions(result), r.opts)
|
||||
}
|
||||
|
||||
// PartitionsCount returns the number of partitions in the ring.
|
||||
|
||||
+10
-2
@@ -28,6 +28,9 @@ type PartitionRingWatcher struct {
|
||||
|
||||
// Metrics.
|
||||
numPartitionsGaugeVec *prometheus.GaugeVec
|
||||
|
||||
// opts is used to propagate the options each time the ring is updated.
|
||||
opts PartitionRingOptions
|
||||
}
|
||||
|
||||
type PartitionRingWatcherDelegate interface {
|
||||
@@ -36,7 +39,11 @@ type PartitionRingWatcherDelegate interface {
|
||||
}
|
||||
|
||||
func NewPartitionRingWatcher(name, key string, kv kv.Client, logger log.Logger, reg prometheus.Registerer) *PartitionRingWatcher {
|
||||
emptyRing, err := NewPartitionRing(*NewPartitionRingDesc())
|
||||
return NewPartitionRingWatcherWithOptions(name, key, kv, DefaultPartitionRingOptions(), logger, reg)
|
||||
}
|
||||
|
||||
func NewPartitionRingWatcherWithOptions(name, key string, kv kv.Client, opts PartitionRingOptions, logger log.Logger, reg prometheus.Registerer) *PartitionRingWatcher {
|
||||
emptyRing, err := NewPartitionRingWithOptions(*NewPartitionRingDesc(), opts)
|
||||
if err != nil {
|
||||
panic(err) // This should never executes.
|
||||
}
|
||||
@@ -50,6 +57,7 @@ func NewPartitionRingWatcher(name, key string, kv kv.Client, logger log.Logger,
|
||||
Help: "Number of partitions by state in the partitions ring.",
|
||||
ConstLabels: map[string]string{"name": name},
|
||||
}, []string{"state"}),
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
r.Service = services.NewBasicService(r.starting, r.loop, nil).WithName("partitions-ring-watcher")
|
||||
@@ -99,7 +107,7 @@ func (w *PartitionRingWatcher) loop(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (w *PartitionRingWatcher) updatePartitionRing(desc *PartitionRingDesc) error {
|
||||
newRing, err := NewPartitionRing(*desc)
|
||||
newRing, err := NewPartitionRingWithOptions(*desc, w.opts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create partition ring from descriptor")
|
||||
}
|
||||
|
||||
+13
@@ -145,6 +145,10 @@ type ReadRing interface {
|
||||
|
||||
// ZonesCount returns the number of zones for which there's at least 1 instance registered in the ring.
|
||||
ZonesCount() int
|
||||
|
||||
// Zones returns the list of zones for which there's at least 1 instance registered in the ring.
|
||||
// The returned slice is sorted alphabetically.
|
||||
Zones() []string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -1476,6 +1480,15 @@ func (r *Ring) ZonesCount() int {
|
||||
return len(r.ringZones)
|
||||
}
|
||||
|
||||
// Zones returns the list of zones for which there's at least 1 instance registered in the ring.
|
||||
// The returned slice is sorted alphabetically.
|
||||
func (r *Ring) Zones() []string {
|
||||
r.mtx.RLock()
|
||||
defer r.mtx.RUnlock()
|
||||
|
||||
return slices.Clone(r.ringZones)
|
||||
}
|
||||
|
||||
// readOnlyInstanceCount returns the number of read only instances in the ring.
|
||||
func (r *Ring) readOnlyInstanceCount() int {
|
||||
r.mtx.RLock()
|
||||
|
||||
+66
-61
@@ -1,90 +1,94 @@
|
||||
# Services
|
||||
|
||||
This is a Go implementation of [services model](https://github.com/google/guava/wiki/ServiceExplained) from [Google Guava](https://github.com/google/guava) library.
|
||||
This is a Go implementation of the [services model](https://github.com/google/guava/wiki/ServiceExplained) from the [Google Guava](https://github.com/google/guava) library.
|
||||
|
||||
It provides `Service` interface (with implementation in `BasicService` type) and `Manager` for managing group of services at once.
|
||||
It provides a `Service` interface (with implementation in the `BasicService` type) and a `Manager` for managing a group of services at once.
|
||||
|
||||
Main benefits of this model are:
|
||||
The main benefits of this model are:
|
||||
|
||||
- Services have well-defined explicit states. Services are not supposed to start any work until they are started, and they are supposed to enter Running state only if they have successfully done all initialization in Starting state.
|
||||
- States are observable by clients. Client can not only see the state, but also wait for Running or Terminated state.
|
||||
- Services have well-defined explicit states. Services don't start any work until they are started and only enter the `Running` state if they have successfully done all initialization in the `Starting` state.
|
||||
- States are observable by clients. A client can see the state and also wait for `Running` or `Terminated` states.
|
||||
- If more observability is needed, clients can register state listeners.
|
||||
- Service startup and shutdown is done asynchronously. This allows for nice parallelization of startup or shutdown of multiple services.
|
||||
- Services that depend on each other can simply wait for other service to be in correct state before using it.
|
||||
- Service startup and shutdown are done asynchronously. This allows for nice parallelization of startup or shutdown of multiple services.
|
||||
- Services that depend on each other can simply wait for other services to be in the correct state before using them.
|
||||
|
||||
## Service interface
|
||||
|
||||
As the user of the service, here is what you need to know:
|
||||
Each service starts in New state.
|
||||
In this state, service is not yet doing anything. It is only instantiated, and ready to be started.
|
||||
As a user of the service, here is what you need to know.
|
||||
|
||||
Service is started by calling its `StartAsync` method. This will make service transition to `Starting` state, and eventually to `Running` state, if starting is successful.
|
||||
Starting is done asynchronously, so that client can do other work while service is starting, for example start more services.
|
||||
Each service starts in the `New` state.
|
||||
In this state, the service is only instantiated and ready to be started.
|
||||
|
||||
Service spends most of its time in `Running` state, in which it provides it services to the clients. What exactly it does depends on service itself. Typical examples include responding to HTTP requests, running periodic background tasks, etc.
|
||||
Start a service by calling its `StartAsync` method. This makes the service transition to the `Starting` state, and eventually to the `Running` state, if starting is successful.
|
||||
Starting is done asynchronously, so that a client can do other work while the service is starting, for example, starting more services.
|
||||
|
||||
Clients can stop the service by calling `StopAsync`, which tells service to stop. `Service` will transition to `Stopping` state (in which it does the necessary cleanup) and eventually `Terminated` state.
|
||||
If service fails in its `Starting`, `Running` or `Stopping` state, it will end up in `Failed` state instead of Terminated.
|
||||
A service spends most of its time in the `Running` state, in which it provides its services to the clients. What exactly it does depends on the service itself. Typical examples include responding to HTTP requests, running periodic background tasks, etc.
|
||||
|
||||
Once service is in `Terminated` or `Failed` state, it cannot be restarted, these states are terminal.
|
||||
Clients can stop the service by calling `StopAsync`, which tells the service to stop. The service transitions to the `Stopping` state to perform necessary cleanup, and then to the `Terminated` state.
|
||||
If a service fails in its `Starting`, `Running`, or `Stopping` state, it ends up in the `Failed` state instead of `Terminated`.
|
||||
|
||||
Once a service is in a `Terminated` or `Failed` state, it cannot be restarted; these states are terminal.
|
||||
|
||||
Full state diagram:
|
||||
|
||||
```text
|
||||
┌────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ▼
|
||||
┌─────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐
|
||||
│ New │─────▶│ Starting │─────▶│ Running │────▶│ Stopping │───┬─▶│ Terminated │
|
||||
└─────┘ └──────────┘ └─────────┘ └──────────┘ │ └────────────┘
|
||||
│ │
|
||||
│ │
|
||||
│ │ ┌────────┐
|
||||
└──────────────────────────────────────────┴──▶│ Failed │
|
||||
└────────┘
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
new[New]
|
||||
starting[Starting]
|
||||
running[Running]
|
||||
stopping[Stopping]
|
||||
terminated[Terminated]
|
||||
failed[Failed]
|
||||
new --> starting --> running --> stopping --> terminated
|
||||
new --> terminated
|
||||
starting --> failed
|
||||
running --> failed
|
||||
stopping --> failed
|
||||
|
||||
```
|
||||
|
||||
API and states and semantics are implemented to correspond to [Service class](https://guava.dev/releases/snapshot/api/docs/com/google/common/util/concurrent/Service.html) in Guava library.
|
||||
The API, states, and semantics are implemented to correspond to the [Service class](https://guava.dev/releases/snapshot/api/docs/com/google/common/util/concurrent/Service.html) in the Guava library.
|
||||
|
||||
## Manager
|
||||
|
||||
Multiple services can be managed via `Manager` (corresponds to [ServiceManager](https://guava.dev/releases/snapshot/api/docs/com/google/common/util/concurrent/ServiceManager.html) in Guava library).
|
||||
Manager is initialized with list of New services.
|
||||
It can start the services, and wait until all services are running (= "Healthy" state).
|
||||
Manager can also be stopped – which triggers stopping of all its services.
|
||||
When all services are in their terminal states (Terminated or Final), manager is said to be Stopped.
|
||||
Multiple services can be managed via a `Manager` (corresponds to the [ServiceManager](https://guava.dev/releases/snapshot/api/docs/com/google/common/util/concurrent/ServiceManager.html) in Guava library).
|
||||
A `Manager` is initialized with a list of `New` services.
|
||||
It can start the services and wait until all services are running (= "Healthy" state).
|
||||
A `Manager` can also be stopped, which also stops all of its services.
|
||||
When all services are in their `Terminated` or `Failed` states, the `Manager` is said to be "stopped."
|
||||
|
||||
## Implementing custom Service
|
||||
## Implementing a custom service
|
||||
|
||||
As a developer who wants to implement your own service, there are several possibilities.
|
||||
|
||||
### Using `NewService`
|
||||
|
||||
The easiest possible way to create a service is using `NewService` function with three functions called `StartingFn`, `RunningFn` and `StoppingFn`.
|
||||
Returned service will be in `New` state.
|
||||
When it transitions to `Starting` state (by calling `StartAsync`), `StartingFn` is called.
|
||||
When `StartingFn` finishes with no error, service transitions to `Running` state and `RunningFn` is called.
|
||||
When `RunningFn` finishes, services transitions to `Stopping` state, and `StoppingFn` is called.
|
||||
After `StoppingFn` is done, service ends in `Terminated` state (if none of the functions returned error), or `Failed` state, if there were errors.
|
||||
The easiest possible way to create a service is using the `NewService` function with three functions as arguments called `StartingFn`, `RunningFn` and `StoppingFn`.
|
||||
The returned service is in the `New` state.
|
||||
When it transitions to the `Starting` state (by calling `StartAsync`), `StartingFn` is called.
|
||||
When `StartingFn` finishes with no error, the service transitions to the `Running` state and `RunningFn` is called.
|
||||
When `RunningFn` finishes, services transition to the `Stopping` state, and `StoppingFn` is called.
|
||||
After `StoppingFn` is done, the service ends in `Terminated` state (if none of the functions returned an error), or the `Failed` state, if there were errors.
|
||||
|
||||
Any of the functions can be `nil`, in which case service simply moves to the next state.
|
||||
Any of the functions can be `nil`, in which case the service simply moves to the next state.
|
||||
|
||||
### Using `NewIdleService`
|
||||
|
||||
"Idle" service is a service which needs to run custom code during `Starting` or `Stopping` state, but not in `Running` state.
|
||||
Service will remain in `Running` state until explicitly stopped via `StopAsync`.
|
||||
This "idle" service needs to run custom code during `Starting` or `Stopping` states, but not in the `Running` state.
|
||||
The service remains in the `Running` state until explicitly stopped using `StopAsync`.
|
||||
|
||||
Example usage is a service that registers some HTTP or gRPC handlers.
|
||||
|
||||
### Using `NewTimerService`
|
||||
|
||||
Timer service runs supplied function on every tick. If this function returns error, service will fail.
|
||||
Otherwise service will continue calling supplied function until stopped via `StopAsync`.
|
||||
This service runs a supplied function once every tick interval, defined by a `time.Duration` argument. If this function returns an error, the service fails.
|
||||
Otherwise, the service continues calling the supplied function until stopped using `StopAsync`.
|
||||
|
||||
### Using `BasicService` struct
|
||||
|
||||
All previous options use `BasicService` type internally, and it is `BasicService` which implements semantics of `Service` interface.
|
||||
This struct can also be embedded into custom struct, and then initialized with starting/running/stopping functions via `InitBasicService`:
|
||||
All previous options use the `BasicService` type internally, and it is `BasicService` that implements the semantics of the `Service` interface.
|
||||
This struct can also be embedded into a custom struct, and then initialized with starting/running/stopping functions via `InitBasicService`:
|
||||
|
||||
```go
|
||||
type exampleService struct {
|
||||
@@ -98,11 +102,12 @@ func newExampleServ() *exampleService {
|
||||
s := &exampleService{
|
||||
ch: make(chan string),
|
||||
}
|
||||
s.BasicService = NewBasicService(nil, s.collect, nil) // StartingFn, RunningFn, StoppingFn
|
||||
s.BasicService = NewBasicService(nil, s.collect, nil) // `StartingFn`, `RunningFn`, `StoppingFn`
|
||||
return s
|
||||
}
|
||||
|
||||
// used as Running function. When service is stopped, context is canceled, so we react on it.
|
||||
// This is used as the `RunningFn` function. When the service is stopped,
|
||||
// the context is canceled, so we can react to it.
|
||||
func (s *exampleService) collect(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
@@ -114,42 +119,42 @@ func (s *exampleService) collect(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// External method called by clients of the Service.
|
||||
// External method called by clients of the service.
|
||||
func (s *exampleService) Send(msg string) bool {
|
||||
ctx := s.ServiceContext() // provided by BasicService. Not part of Service interface.
|
||||
ctx := s.ServiceContext() // provided by `BasicService`, not part of the `Service` interface.
|
||||
if ctx == nil {
|
||||
// Service is not yet started
|
||||
// `Service` is not yet started.
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case s.ch <- msg:
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
// Service is not running anymore.
|
||||
// `Service` is not running anymore.
|
||||
return false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now `exampleService` is a service that can be started, observed for state changes, or stopped. As long as service is in Running state, clients can call its `Send` method:
|
||||
Now `exampleService` is a service that can be started, observed for state changes, or stopped. As long as the service is in the `Running` state, clients can call its `Send` method:
|
||||
|
||||
```go
|
||||
s := newExampleServ()
|
||||
s.StartAsync(context.Background())
|
||||
s.AwaitRunning(context.Background())
|
||||
// now collect() is running
|
||||
// Now collect() is running.
|
||||
s.Send("A")
|
||||
s.Send("B")
|
||||
s.Send("C")
|
||||
s.StopAsync()
|
||||
s.AwaitTerminated(context.Background())
|
||||
// now service is finished, and we can access s.log
|
||||
// Now the service is finished, and we can access s.log.
|
||||
```
|
||||
|
||||
After service is stopped (in Terminated or Failed state, although here the "running" function doesn't return error, so only Terminated state is possible), all collected messages can be read from `log` field.
|
||||
Notice that no further synchronization is necessary in this case... when service is stopped and client has observed that via `AwaitTerminated`, any access to `log` is safe.
|
||||
After a service is stopped, i.e. in the `Terminated` or `Failed` state, all collected messages can be read from the `log` field. However in this case the `RunningFn` function doesn't return an error, so only the `Terminated` state is possible.
|
||||
Notice that no further synchronization is necessary in this case; when the service is stopped and a client has observed that via `AwaitTerminated`, any access to `log` is safe.
|
||||
|
||||
(This example is adapted from unit tests in basic_service_test.go)
|
||||
(This example is adapted from the unit tests in `basic_service_test.go`)
|
||||
|
||||
This may seem like a lot of extra code, and for such a simple usage it probably is.
|
||||
Real benefit comes when one starts combining multiple services into a manager, observe them as a group, or let services depend on each other via Await methods.
|
||||
The real benefit comes when one starts combining multiple services into a `Manager`, observes them as a group, or lets services depend on each other via `Await` methods.
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import (
|
||||
var (
|
||||
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
|
||||
MinClusterVersion = "3.0.0"
|
||||
Version = "3.6.6"
|
||||
Version = "3.6.7"
|
||||
APIVersion = "unknown"
|
||||
|
||||
// Git SHA Value will be set during build
|
||||
|
||||
+10
-1
@@ -171,6 +171,11 @@ func init() {
|
||||
return nil, err
|
||||
}
|
||||
for _, producer := range producers {
|
||||
if _, ok := producer.(myProducer); ok {
|
||||
// Skip default prometheusbridge producer. Only add
|
||||
// user-configured producers.
|
||||
continue
|
||||
}
|
||||
exporterOpts = append(exporterOpts, promexporter.WithProducer(producer))
|
||||
}
|
||||
|
||||
@@ -212,13 +217,17 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterMetricProducer("prometheus", func(context.Context) (metric.Producer, error) {
|
||||
return prometheusbridge.NewMetricProducer(), nil
|
||||
return myProducer{prometheusbridge.NewMetricProducer()}, nil
|
||||
})
|
||||
RegisterMetricProducer("none", func(context.Context) (metric.Producer, error) {
|
||||
return newNoopMetricProducer(), nil
|
||||
})
|
||||
}
|
||||
|
||||
type myProducer struct {
|
||||
metric.Producer
|
||||
}
|
||||
|
||||
type readerWithServer struct {
|
||||
addr net.Addr
|
||||
metric.Reader
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger"
|
||||
|
||||
// Version is the current release version of the Jaeger propagator.
|
||||
func Version() string {
|
||||
return "1.38.0"
|
||||
return "1.39.0"
|
||||
// This string is updated by the pre_release.sh script during release
|
||||
}
|
||||
|
||||
|
||||
+52
-15
@@ -24,6 +24,7 @@ import (
|
||||
"sync"
|
||||
|
||||
jaeger_api_v2 "github.com/jaegertracing/jaeger-idl/proto-gen/api_v2"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
|
||||
@@ -34,25 +35,40 @@ const (
|
||||
defaultMaxOperations = 2000
|
||||
)
|
||||
|
||||
const (
|
||||
samplerTypeKey = "jaeger.sampler.type"
|
||||
samplerParamKey = "jaeger.sampler.param"
|
||||
samplerTypeValueProbabilistic = "probabilistic"
|
||||
samplerTypeValueRateLimiting = "ratelimiting"
|
||||
)
|
||||
|
||||
// -----------------------
|
||||
|
||||
// probabilisticSampler is a sampler that randomly samples a certain percentage
|
||||
// of traces.
|
||||
type probabilisticSampler struct {
|
||||
samplingRate float64
|
||||
sampler trace.Sampler
|
||||
samplingRate float64
|
||||
sampler trace.Sampler
|
||||
attributes []attribute.KeyValue
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
// newProbabilisticSampler creates a sampler that randomly samples a certain percentage of traces specified by the
|
||||
// samplingRate, in the range between 0.0 and 1.0. it utilizes the SDK `trace.TraceIDRatioBased` sampler.
|
||||
func newProbabilisticSampler(samplingRate float64) *probabilisticSampler {
|
||||
s := new(probabilisticSampler)
|
||||
func newProbabilisticSampler(samplingRate float64, attributesDisabled bool) *probabilisticSampler {
|
||||
s := &probabilisticSampler{
|
||||
attributesDisabled: attributesDisabled,
|
||||
}
|
||||
return s.init(samplingRate)
|
||||
}
|
||||
|
||||
func (s *probabilisticSampler) init(samplingRate float64) *probabilisticSampler {
|
||||
s.samplingRate = math.Max(0.0, math.Min(samplingRate, 1.0))
|
||||
s.sampler = trace.TraceIDRatioBased(s.samplingRate)
|
||||
if s.attributesDisabled {
|
||||
return s
|
||||
}
|
||||
s.attributes = []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueProbabilistic), attribute.Float64(samplerParamKey, s.samplingRate)}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -62,7 +78,12 @@ func (s *probabilisticSampler) SamplingRate() float64 {
|
||||
}
|
||||
|
||||
func (s *probabilisticSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult {
|
||||
return s.sampler.ShouldSample(p)
|
||||
r := s.sampler.ShouldSample(p)
|
||||
if r.Decision == trace.Drop {
|
||||
return r
|
||||
}
|
||||
r.Attributes = s.attributes
|
||||
return r
|
||||
}
|
||||
|
||||
// Equal compares with another sampler.
|
||||
@@ -95,11 +116,16 @@ func (s *probabilisticSampler) Description() string {
|
||||
type rateLimitingSampler struct {
|
||||
maxTracesPerSecond float64
|
||||
rateLimiter *ratelimiter.RateLimiter
|
||||
attributes []attribute.KeyValue
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
// newRateLimitingSampler creates new rateLimitingSampler.
|
||||
func newRateLimitingSampler(maxTracesPerSecond float64) *rateLimitingSampler {
|
||||
s := new(rateLimitingSampler)
|
||||
func newRateLimitingSampler(maxTracesPerSecond float64, attributesDisabled bool) *rateLimitingSampler {
|
||||
s := &rateLimitingSampler{
|
||||
attributesDisabled: attributesDisabled,
|
||||
}
|
||||
|
||||
return s.init(maxTracesPerSecond)
|
||||
}
|
||||
|
||||
@@ -110,6 +136,10 @@ func (s *rateLimitingSampler) init(maxTracesPerSecond float64) *rateLimitingSamp
|
||||
s.rateLimiter.Update(maxTracesPerSecond, math.Max(maxTracesPerSecond, 1.0))
|
||||
}
|
||||
s.maxTracesPerSecond = maxTracesPerSecond
|
||||
if s.attributesDisabled {
|
||||
return s
|
||||
}
|
||||
s.attributes = []attribute.KeyValue{attribute.String(samplerTypeKey, samplerTypeValueRateLimiting), attribute.Float64(samplerParamKey, s.maxTracesPerSecond)}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -119,6 +149,7 @@ func (s *rateLimitingSampler) ShouldSample(p trace.SamplingParameters) trace.Sam
|
||||
return trace.SamplingResult{
|
||||
Decision: trace.RecordAndSample,
|
||||
Tracestate: psc.TraceState(),
|
||||
Attributes: s.attributes,
|
||||
}
|
||||
}
|
||||
return trace.SamplingResult{
|
||||
@@ -161,12 +192,14 @@ type guaranteedThroughputProbabilisticSampler struct {
|
||||
lowerBoundSampler *rateLimitingSampler
|
||||
samplingRate float64
|
||||
lowerBound float64
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
func newGuaranteedThroughputProbabilisticSampler(lowerBound, samplingRate float64) *guaranteedThroughputProbabilisticSampler {
|
||||
func newGuaranteedThroughputProbabilisticSampler(lowerBound, samplingRate float64, attributesDisabled bool) *guaranteedThroughputProbabilisticSampler {
|
||||
s := &guaranteedThroughputProbabilisticSampler{
|
||||
lowerBoundSampler: newRateLimitingSampler(lowerBound),
|
||||
lowerBound: lowerBound,
|
||||
lowerBoundSampler: newRateLimitingSampler(lowerBound, attributesDisabled),
|
||||
lowerBound: lowerBound,
|
||||
attributesDisabled: attributesDisabled,
|
||||
}
|
||||
s.setProbabilisticSampler(samplingRate)
|
||||
return s
|
||||
@@ -174,7 +207,7 @@ func newGuaranteedThroughputProbabilisticSampler(lowerBound, samplingRate float6
|
||||
|
||||
func (s *guaranteedThroughputProbabilisticSampler) setProbabilisticSampler(samplingRate float64) {
|
||||
if s.probabilisticSampler == nil {
|
||||
s.probabilisticSampler = newProbabilisticSampler(samplingRate)
|
||||
s.probabilisticSampler = newProbabilisticSampler(samplingRate, s.attributesDisabled)
|
||||
} else if s.samplingRate != samplingRate {
|
||||
s.probabilisticSampler.init(samplingRate)
|
||||
}
|
||||
@@ -218,6 +251,7 @@ type perOperationSampler struct {
|
||||
|
||||
// see description in perOperationSamplerParams
|
||||
operationNameLateBinding bool
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
// perOperationSamplerParams defines parameters when creating perOperationSampler.
|
||||
@@ -238,7 +272,7 @@ type perOperationSamplerParams struct {
|
||||
}
|
||||
|
||||
// newPerOperationSampler returns a new perOperationSampler.
|
||||
func newPerOperationSampler(params perOperationSamplerParams) *perOperationSampler {
|
||||
func newPerOperationSampler(params perOperationSamplerParams, attributesDisabled bool) *perOperationSampler {
|
||||
if params.MaxOperations <= 0 {
|
||||
params.MaxOperations = defaultMaxOperations
|
||||
}
|
||||
@@ -247,15 +281,17 @@ func newPerOperationSampler(params perOperationSamplerParams) *perOperationSampl
|
||||
sampler := newGuaranteedThroughputProbabilisticSampler(
|
||||
params.Strategies.DefaultLowerBoundTracesPerSecond,
|
||||
strategy.ProbabilisticSampling.SamplingRate,
|
||||
attributesDisabled,
|
||||
)
|
||||
samplers[strategy.Operation] = sampler
|
||||
}
|
||||
return &perOperationSampler{
|
||||
samplers: samplers,
|
||||
defaultSampler: newProbabilisticSampler(params.Strategies.DefaultSamplingProbability),
|
||||
defaultSampler: newProbabilisticSampler(params.Strategies.DefaultSamplingProbability, attributesDisabled),
|
||||
lowerBound: params.Strategies.DefaultLowerBoundTracesPerSecond,
|
||||
maxOperations: params.MaxOperations,
|
||||
operationNameLateBinding: params.OperationNameLateBinding,
|
||||
attributesDisabled: attributesDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +320,7 @@ func (s *perOperationSampler) getSamplerForOperation(operation string) trace.Sam
|
||||
if len(s.samplers) >= s.maxOperations {
|
||||
return s.defaultSampler
|
||||
}
|
||||
newSampler := newGuaranteedThroughputProbabilisticSampler(s.lowerBound, s.defaultSampler.SamplingRate())
|
||||
newSampler := newGuaranteedThroughputProbabilisticSampler(s.lowerBound, s.defaultSampler.SamplingRate(), s.attributesDisabled)
|
||||
s.samplers[operation] = newSampler
|
||||
return newSampler
|
||||
}
|
||||
@@ -308,13 +344,14 @@ func (s *perOperationSampler) update(strategies *jaeger_api_v2.PerOperationSampl
|
||||
sampler := newGuaranteedThroughputProbabilisticSampler(
|
||||
lowerBound,
|
||||
samplingRate,
|
||||
s.attributesDisabled,
|
||||
)
|
||||
newSamplers[operation] = sampler
|
||||
}
|
||||
}
|
||||
s.lowerBound = strategies.DefaultLowerBoundTracesPerSecond
|
||||
if s.defaultSampler.SamplingRate() != strategies.DefaultSamplingProbability {
|
||||
s.defaultSampler = newProbabilisticSampler(strategies.DefaultSamplingProbability)
|
||||
s.defaultSampler = newProbabilisticSampler(strategies.DefaultSamplingProbability, s.attributesDisabled)
|
||||
}
|
||||
s.samplers = newSamplers
|
||||
}
|
||||
|
||||
+12
-7
@@ -189,10 +189,12 @@ func (s *Sampler) updateSamplerViaUpdaters(strategy any) error {
|
||||
// -----------------------
|
||||
|
||||
// probabilisticSamplerUpdater is used by Sampler to parse sampling configuration.
|
||||
type probabilisticSamplerUpdater struct{}
|
||||
type probabilisticSamplerUpdater struct {
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
// Update implements Update of samplerUpdater.
|
||||
func (*probabilisticSamplerUpdater) Update(sampler trace.Sampler, strategy any) (trace.Sampler, error) {
|
||||
func (u *probabilisticSamplerUpdater) Update(sampler trace.Sampler, strategy any) (trace.Sampler, error) {
|
||||
type response interface {
|
||||
GetProbabilisticSampling() *jaeger_api_v2.ProbabilisticSamplingStrategy
|
||||
}
|
||||
@@ -205,7 +207,7 @@ func (*probabilisticSamplerUpdater) Update(sampler trace.Sampler, strategy any)
|
||||
}
|
||||
return sampler, nil
|
||||
}
|
||||
return newProbabilisticSampler(probabilistic.SamplingRate), nil
|
||||
return newProbabilisticSampler(probabilistic.SamplingRate, u.attributesDisabled), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
@@ -214,10 +216,12 @@ func (*probabilisticSamplerUpdater) Update(sampler trace.Sampler, strategy any)
|
||||
// -----------------------
|
||||
|
||||
// rateLimitingSamplerUpdater is used by Sampler to parse sampling configuration.
|
||||
type rateLimitingSamplerUpdater struct{}
|
||||
type rateLimitingSamplerUpdater struct {
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
// Update implements Update of samplerUpdater.
|
||||
func (*rateLimitingSamplerUpdater) Update(sampler trace.Sampler, strategy any) (trace.Sampler, error) {
|
||||
func (u *rateLimitingSamplerUpdater) Update(sampler trace.Sampler, strategy any) (trace.Sampler, error) {
|
||||
type response interface {
|
||||
GetRateLimitingSampling() *jaeger_api_v2.RateLimitingSamplingStrategy
|
||||
}
|
||||
@@ -229,7 +233,7 @@ func (*rateLimitingSamplerUpdater) Update(sampler trace.Sampler, strategy any) (
|
||||
rl.Update(rateLimit)
|
||||
return rl, nil
|
||||
}
|
||||
return newRateLimitingSampler(rateLimit), nil
|
||||
return newRateLimitingSampler(rateLimit, u.attributesDisabled), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
@@ -242,6 +246,7 @@ func (*rateLimitingSamplerUpdater) Update(sampler trace.Sampler, strategy any) (
|
||||
type perOperationSamplerUpdater struct {
|
||||
MaxOperations int
|
||||
OperationNameLateBinding bool
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
// Update implements Update of samplerUpdater.
|
||||
@@ -260,7 +265,7 @@ func (u *perOperationSamplerUpdater) Update(sampler trace.Sampler, strategy any)
|
||||
MaxOperations: u.MaxOperations,
|
||||
OperationNameLateBinding: u.OperationNameLateBinding,
|
||||
Strategies: operations,
|
||||
}), nil
|
||||
}, u.attributesDisabled), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
Generated
Vendored
+21
-16
@@ -38,6 +38,7 @@ type config struct {
|
||||
updaters []samplerUpdater
|
||||
posParams perOperationSamplerParams
|
||||
logger logr.Logger
|
||||
attributesDisabled bool
|
||||
}
|
||||
|
||||
func getEnvOptions() ([]Option, []error) {
|
||||
@@ -87,15 +88,10 @@ func getEnvOptions() ([]Option, []error) {
|
||||
// newConfig returns an appropriately configured config.
|
||||
func newConfig(options ...Option) config {
|
||||
c := config{
|
||||
sampler: newProbabilisticSampler(0.001),
|
||||
samplingServerURL: defaultSamplingServerURL,
|
||||
samplingRefreshInterval: defaultSamplingRefreshInterval,
|
||||
samplingFetcher: newHTTPSamplingStrategyFetcher(defaultSamplingServerURL),
|
||||
samplingParser: new(samplingStrategyParserImpl),
|
||||
updaters: []samplerUpdater{
|
||||
new(probabilisticSamplerUpdater),
|
||||
new(rateLimitingSamplerUpdater),
|
||||
},
|
||||
posParams: perOperationSamplerParams{
|
||||
MaxOperations: defaultSamplingMaxOperations,
|
||||
OperationNameLateBinding: defaultSamplingOperationNameLateBinding,
|
||||
@@ -115,11 +111,20 @@ func newConfig(options ...Option) config {
|
||||
for _, err := range errs {
|
||||
c.logger.Error(err, "env variable parsing failure")
|
||||
}
|
||||
c.updaters = []samplerUpdater{
|
||||
&perOperationSamplerUpdater{
|
||||
MaxOperations: c.posParams.MaxOperations,
|
||||
OperationNameLateBinding: c.posParams.OperationNameLateBinding,
|
||||
attributesDisabled: c.attributesDisabled,
|
||||
},
|
||||
&probabilisticSamplerUpdater{attributesDisabled: c.attributesDisabled},
|
||||
&rateLimitingSamplerUpdater{attributesDisabled: c.attributesDisabled},
|
||||
}
|
||||
|
||||
if c.sampler == nil {
|
||||
c.sampler = newProbabilisticSampler(0.001, c.attributesDisabled)
|
||||
}
|
||||
|
||||
c.updaters = append([]samplerUpdater{&perOperationSamplerUpdater{
|
||||
MaxOperations: c.posParams.MaxOperations,
|
||||
OperationNameLateBinding: c.posParams.OperationNameLateBinding,
|
||||
}}, c.updaters...)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -192,16 +197,16 @@ func WithSamplingStrategyFetcher(fetcher SamplingStrategyFetcher) Option {
|
||||
})
|
||||
}
|
||||
|
||||
// WithAttributesDisabled configures the sampler to disable setting attributes jaeger.sampler.type and jaeger.sampler.param.
|
||||
func WithAttributesDisabled() Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.attributesDisabled = true
|
||||
})
|
||||
}
|
||||
|
||||
// samplingStrategyParser creates a Option that initializes sampling strategy parser.
|
||||
func withSamplingStrategyParser(parser samplingStrategyParser) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.samplingParser = parser
|
||||
})
|
||||
}
|
||||
|
||||
// withUpdaters creates a Option that initializes sampler updaters.
|
||||
func withUpdaters(updaters ...samplerUpdater) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.updaters = updaters
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@ package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremot
|
||||
|
||||
// Version is the current release version of the Jaeger remote sampler.
|
||||
func Version() string {
|
||||
return "0.32.0"
|
||||
return "0.33.0"
|
||||
// This string is updated by the pre_release.sh script during release
|
||||
}
|
||||
|
||||
+30
-8
@@ -6,7 +6,7 @@ package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/o
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
|
||||
@@ -21,7 +21,8 @@ import (
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry"
|
||||
)
|
||||
|
||||
@@ -38,6 +39,8 @@ type client struct {
|
||||
ourConn bool
|
||||
conn *grpc.ClientConn
|
||||
lsc collogpb.LogsServiceClient
|
||||
|
||||
instrumentation *observ.Instrumentation
|
||||
}
|
||||
|
||||
// Used for testing.
|
||||
@@ -72,7 +75,18 @@ func newClient(cfg config) (*client, error) {
|
||||
|
||||
c.lsc = collogpb.NewLogsServiceClient(c.conn)
|
||||
|
||||
return c, nil
|
||||
var err error
|
||||
id := nextExporterID()
|
||||
c.instrumentation, err = observ.NewInstrumentation(id, c.conn.CanonicalTarget())
|
||||
return c, err
|
||||
}
|
||||
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// nextExporterID returns the next unique ID for an exporter.
|
||||
func nextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
func newGRPCDialOptions(cfg config) []grpc.DialOption {
|
||||
@@ -121,7 +135,7 @@ func newGRPCDialOptions(cfg config) []grpc.DialOption {
|
||||
// The otlplog.Exporter synchronizes access to client methods, and
|
||||
// ensures this is not called after the Exporter is shutdown. Only thing
|
||||
// to do here is send data.
|
||||
func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) error {
|
||||
func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) (uploadErr error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Do not upload if the context is already expired.
|
||||
@@ -132,7 +146,15 @@ func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) error
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
return c.requestFunc(ctx, func(ctx context.Context) error {
|
||||
count := int64(len(rl))
|
||||
if c.instrumentation != nil {
|
||||
eo := c.instrumentation.ExportLogs(ctx, count)
|
||||
defer func() {
|
||||
eo.End(uploadErr)
|
||||
}()
|
||||
}
|
||||
|
||||
return errors.Join(uploadErr, c.requestFunc(ctx, func(ctx context.Context) error {
|
||||
resp, err := c.lsc.Export(ctx, &collogpb.ExportLogsServiceRequest{
|
||||
ResourceLogs: rl,
|
||||
})
|
||||
@@ -140,8 +162,8 @@ func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) error
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedLogRecords()
|
||||
if n != 0 || msg != "" {
|
||||
err := fmt.Errorf("OTLP partial success: %s (%d log records rejected)", msg, n)
|
||||
otel.Handle(err)
|
||||
err := internal.LogPartialSuccessError(n, msg)
|
||||
uploadErr = errors.Join(uploadErr, err)
|
||||
}
|
||||
}
|
||||
// nil is converted to OK.
|
||||
@@ -150,7 +172,7 @@ func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) error
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
// Shutdown shuts down the client, freeing all resources.
|
||||
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal functionality for the otlploggrpc
|
||||
// package.
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal"
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target.go.tmpl "--data={ \"pkg\": \"observ\", \"pkg_path\": \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ\" }" --out=observ/target.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target_test.go.tmpl "--data={ \"pkg\": \"observ\" }" --out=observ/target_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry.go.tmpl "--data={}" --out=retry/retry.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry_test.go.tmpl "--data={}" --out=retry/retry_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/attr_test.go.tmpl "--data={}" --out=transform/attr_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log.go.tmpl "--data={}" --out=transform/log.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl "--data={}" --out=transform/log_attr_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_test.go.tmpl "--data={}" --out=transform/log_test.go
|
||||
Generated
Vendored
+309
@@ -0,0 +1,309 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides observability metrics for OTLP log exporters.
|
||||
// This is an experimental feature controlled by the x.Observability feature flag.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeName is the unique name of the meter used for instrumentation.
|
||||
ScopeName = "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ"
|
||||
|
||||
// Version is the current version of this instrumentation.
|
||||
//
|
||||
// This matches the version of the exporter.
|
||||
Version = internal.Version
|
||||
)
|
||||
|
||||
var (
|
||||
attrsPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 + // component.name
|
||||
1 + // component.type
|
||||
1 + // server.addr
|
||||
1 + // server.port
|
||||
1 + // error.type
|
||||
1 // rpc.grpc.status_code
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
// Return a pointer to a slice instead of a slice itself
|
||||
// to avoid allocations on every call.
|
||||
return &s
|
||||
},
|
||||
}
|
||||
addOpPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.AddOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
recordOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.RecordOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||
func put[T any](p *sync.Pool, s *[]T) {
|
||||
*s = (*s)[:0]
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
// GetComponentName returns the constant name for the exporter with the
|
||||
// provided id.
|
||||
func GetComponentName(id int64) string {
|
||||
return fmt.Sprintf("%s/%d", otelconv.ComponentTypeOtlpGRPCLogExporter, id)
|
||||
}
|
||||
|
||||
// getPresetAttrs builds the preset attributes for instrumentation.
|
||||
func getPresetAttrs(id int64, target string) []attribute.KeyValue {
|
||||
serverAttrs := ServerAddrAttrs(target)
|
||||
attrs := make([]attribute.KeyValue, 0, 2+len(serverAttrs))
|
||||
|
||||
attrs = append(
|
||||
attrs,
|
||||
semconv.OTelComponentName(GetComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCLogExporter,
|
||||
)
|
||||
attrs = append(attrs, serverAttrs...)
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// Instrumentation is experimental instrumentation for the exporter.
|
||||
type Instrumentation struct {
|
||||
logInflightMetric metric.Int64UpDownCounter
|
||||
logExportedMetric metric.Int64Counter
|
||||
logExportedDurationMetric metric.Float64Histogram
|
||||
|
||||
presetAttrs []attribute.KeyValue
|
||||
addOpt metric.AddOption
|
||||
recOpt metric.RecordOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for otlplog grpc exporter.
|
||||
func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
i := &Instrumentation{}
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
logInflightMetric, e := otelconv.NewSDKExporterLogInflight(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create log inflight metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.logInflightMetric = logInflightMetric.Inst()
|
||||
|
||||
logExportedMetric, e := otelconv.NewSDKExporterLogExported(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create log exported metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.logExportedMetric = logExportedMetric.Inst()
|
||||
|
||||
logOpDurationMetric, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create log operation duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.logExportedDurationMetric = logOpDurationMetric.Inst()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.presetAttrs = getPresetAttrs(id, target)
|
||||
|
||||
i.addOpt = metric.WithAttributeSet(attribute.NewSet(i.presetAttrs...))
|
||||
i.recOpt = metric.WithAttributeSet(attribute.NewSet(append(
|
||||
// Default to OK status code.
|
||||
[]attribute.KeyValue{semconv.RPCGRPCStatusCodeOk},
|
||||
i.presetAttrs...,
|
||||
)...))
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// ExportLogs instruments the ExportLogs method of the exporter. It returns
|
||||
// an [ExportOp] that must have its [ExportOp.End] method called when the
|
||||
// ExportLogs method returns.
|
||||
func (i *Instrumentation) ExportLogs(ctx context.Context, count int64) ExportOp {
|
||||
start := time.Now()
|
||||
addOpt := get[metric.AddOption](addOpPool)
|
||||
defer put(addOpPool, addOpt)
|
||||
|
||||
*addOpt = append(*addOpt, i.addOpt)
|
||||
|
||||
i.logInflightMetric.Add(ctx, count, *addOpt...)
|
||||
|
||||
return ExportOp{
|
||||
nLogs: count,
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
inst: i,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp tracks the operation being observed by [Instrumentation.ExportLogs].
|
||||
type ExportOp struct {
|
||||
nLogs int64
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End completes the observation of the operation being observed by a call to
|
||||
// [Instrumentation.ExportLogs].
|
||||
// Any error that is encountered is provided as err.
|
||||
//
|
||||
// If err is not nil, all logs will be recorded as failures unless error is of
|
||||
// type [internal.PartialSuccess]. In the case of a PartialSuccess, the number
|
||||
// of successfully exported logs will be determined by inspecting the
|
||||
// RejectedItems field of the PartialSuccess.
|
||||
func (e ExportOp) End(err error) {
|
||||
addOpt := get[metric.AddOption](addOpPool)
|
||||
defer put(addOpPool, addOpt)
|
||||
*addOpt = append(*addOpt, e.inst.addOpt)
|
||||
|
||||
e.inst.logInflightMetric.Add(e.ctx, -e.nLogs, *addOpt...)
|
||||
success := successful(e.nLogs, err)
|
||||
e.inst.logExportedMetric.Add(e.ctx, success, *addOpt...)
|
||||
|
||||
if err != nil {
|
||||
// Add the error.type attribute to the attribute set.
|
||||
attrs := get[attribute.KeyValue](attrsPool)
|
||||
defer put(attrsPool, attrs)
|
||||
*attrs = append(*attrs, e.inst.presetAttrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
o := metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
|
||||
// Reset addOpt with new attribute set
|
||||
*addOpt = append((*addOpt)[:0], o)
|
||||
|
||||
e.inst.logExportedMetric.Add(e.ctx, e.nLogs-success, *addOpt...)
|
||||
}
|
||||
|
||||
recordOpt := get[metric.RecordOption](recordOptPool)
|
||||
defer put(recordOptPool, recordOpt)
|
||||
*recordOpt = append(*recordOpt, e.inst.recordOption(err))
|
||||
e.inst.logExportedDurationMetric.Record(e.ctx, time.Since(e.start).Seconds(), *recordOpt...)
|
||||
}
|
||||
|
||||
func (i *Instrumentation) recordOption(err error) metric.RecordOption {
|
||||
if err == nil {
|
||||
return i.recOpt
|
||||
}
|
||||
attrs := get[attribute.KeyValue](attrsPool)
|
||||
defer put(attrsPool, attrs)
|
||||
|
||||
*attrs = append(*attrs, i.presetAttrs...)
|
||||
code := int64(status.Code(err))
|
||||
*attrs = append(
|
||||
*attrs,
|
||||
semconv.RPCGRPCStatusCodeKey.Int64(code),
|
||||
semconv.ErrorType(err),
|
||||
)
|
||||
|
||||
return metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
}
|
||||
|
||||
// successful returns the number of successfully exported logs out of the n
|
||||
// that were exported based on the provided error.
|
||||
//
|
||||
// If err is nil, n is returned. All logs were successfully exported.
|
||||
//
|
||||
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
|
||||
// It is assumed all logs failed to be exported.
|
||||
//
|
||||
// If err is an [internal.PartialSuccess] error, the number of successfully
|
||||
// exported logs is computed by subtracting the RejectedItems field from n. If
|
||||
// RejectedItems is negative, n is returned. If RejectedItems is greater than
|
||||
// n, 0 is returned.
|
||||
func successful(n int64, err error) int64 {
|
||||
if err == nil {
|
||||
return n // All logs successfully exported.
|
||||
}
|
||||
// Split rejection calculation so successful is inlineable.
|
||||
return n - rejectedCount(n, err)
|
||||
}
|
||||
|
||||
var errPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(internal.PartialSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// rejectedCount returns how many out of the n logs exporter were rejected based on
|
||||
// the provided non-nil err.
|
||||
func rejectedCount(n int64, err error) int64 {
|
||||
ps := errPool.Get().(*internal.PartialSuccess)
|
||||
defer errPool.Put(ps)
|
||||
|
||||
// check for partial success
|
||||
if errors.As(err, ps) {
|
||||
return min(max(ps.RejectedItems, 0), n)
|
||||
}
|
||||
// all logs exporter
|
||||
return n
|
||||
}
|
||||
|
||||
// ServerAddrAttrs is a function that extracts server address and port attributes
|
||||
// from a target string.
|
||||
func ServerAddrAttrs(target string) []attribute.KeyValue {
|
||||
addr, port, err := ParseCanonicalTarget(target)
|
||||
if err != nil || (addr == "" && port < 0) {
|
||||
if err != nil {
|
||||
global.Debug("failed to parse target", "target", target, "error", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unix domain sockets: return only the path as server.address
|
||||
if port == -1 {
|
||||
return []attribute.KeyValue{semconv.ServerAddress(addr)}
|
||||
}
|
||||
|
||||
// For network addresses, only include port if it's valid (> 0)
|
||||
if port > 0 {
|
||||
return []attribute.KeyValue{
|
||||
semconv.ServerAddress(addr),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
// Port is 0 or invalid, only return address
|
||||
return []attribute.KeyValue{semconv.ServerAddress(addr)}
|
||||
}
|
||||
Generated
Vendored
+143
@@ -0,0 +1,143 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/observ/target.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeUnix = "unix"
|
||||
schemeUnixAbstract = "unix-abstract"
|
||||
)
|
||||
|
||||
// ParseCanonicalTarget parses a target string and returns the extracted host
|
||||
// (domain address or IP), the target port, or an error.
|
||||
//
|
||||
// If no port is specified, -1 is returned.
|
||||
//
|
||||
// If no host is specified, an empty string is returned.
|
||||
//
|
||||
// The target string is expected to always have the form
|
||||
// "<scheme>://[authority]/<endpoint>". For example:
|
||||
// - "dns:///example.com:42"
|
||||
// - "dns://8.8.8.8/example.com:42"
|
||||
// - "unix:///path/to/socket"
|
||||
// - "unix-abstract:///socket-name"
|
||||
// - "passthrough:///192.34.2.1:42"
|
||||
//
|
||||
// The target is expected to come from the CanonicalTarget method of a gRPC
|
||||
// Client.
|
||||
func ParseCanonicalTarget(target string) (string, int, error) {
|
||||
const sep = "://"
|
||||
|
||||
// Find scheme. Do not allocate the string by using url.Parse.
|
||||
idx := strings.Index(target, sep)
|
||||
if idx == -1 {
|
||||
return "", -1, fmt.Errorf("invalid target %q: missing scheme", target)
|
||||
}
|
||||
scheme, endpoint := target[:idx], target[idx+len(sep):]
|
||||
|
||||
// Check for unix schemes.
|
||||
if scheme == schemeUnix || scheme == schemeUnixAbstract {
|
||||
return parseUnix(endpoint)
|
||||
}
|
||||
|
||||
// Strip leading slash and any authority.
|
||||
if i := strings.Index(endpoint, "/"); i != -1 {
|
||||
endpoint = endpoint[i+1:]
|
||||
}
|
||||
|
||||
// DNS, passthrough, and custom resolvers.
|
||||
return parseEndpoint(endpoint)
|
||||
}
|
||||
|
||||
// parseUnix parses unix socket targets.
|
||||
func parseUnix(endpoint string) (string, int, error) {
|
||||
// Format: unix[-abstract]://path
|
||||
//
|
||||
// We should have "/path" (empty authority) if valid.
|
||||
if len(endpoint) >= 1 && endpoint[0] == '/' {
|
||||
// Return the full path including leading slash.
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
// If there's no leading slash, it means there might be an authority
|
||||
// Check for authority case (should error): "authority/path"
|
||||
if slashIdx := strings.Index(endpoint, "/"); slashIdx > 0 {
|
||||
return "", -1, fmt.Errorf("invalid (non-empty) authority: %s", endpoint[:slashIdx])
|
||||
}
|
||||
|
||||
return "", -1, errors.New("invalid unix target format")
|
||||
}
|
||||
|
||||
// parseEndpoint parses an endpoint from a gRPC target.
|
||||
//
|
||||
// It supports the following formats:
|
||||
// - "host"
|
||||
// - "host%zone"
|
||||
// - "host:port"
|
||||
// - "host%zone:port"
|
||||
// - "ipv4"
|
||||
// - "ipv4%zone"
|
||||
// - "ipv4:port"
|
||||
// - "ipv4%zone:port"
|
||||
// - "ipv6"
|
||||
// - "ipv6%zone"
|
||||
// - "[ipv6]"
|
||||
// - "[ipv6%zone]"
|
||||
// - "[ipv6]:port"
|
||||
// - "[ipv6%zone]:port"
|
||||
//
|
||||
// It returns the host or host%zone (domain address or IP), the port (or -1 if
|
||||
// not specified), or an error if the input is not a valid.
|
||||
func parseEndpoint(endpoint string) (string, int, error) {
|
||||
// First check if the endpoint is just an IP address.
|
||||
if ip := parseIP(endpoint); ip != "" {
|
||||
return ip, -1, nil
|
||||
}
|
||||
|
||||
// If there's no colon, there is no port (IPv6 with no port checked above).
|
||||
if !strings.Contains(endpoint, ":") {
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
host, portStr, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid host:port %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
const base, bitSize = 10, 16
|
||||
port16, err := strconv.ParseUint(portStr, base, bitSize)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||
}
|
||||
port := int(port16) // port is guaranteed to be in the range [0, 65535].
|
||||
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
// parseIP attempts to parse the entire endpoint as an IP address.
|
||||
// It returns the normalized string form of the IP if successful,
|
||||
// or an empty string if parsing fails.
|
||||
func parseIP(ip string) string {
|
||||
// Strip leading and trailing brackets for IPv6 addresses.
|
||||
if len(ip) >= 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
ip = ip[1 : len(ip)-1]
|
||||
}
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Return the normalized string form of the IP.
|
||||
return addr.String()
|
||||
}
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal"
|
||||
|
||||
import "fmt"
|
||||
|
||||
// PartialSuccess represents the underlying error for all handling
|
||||
// OTLP partial success messages. Use `errors.Is(err,
|
||||
// PartialSuccess{})` to test whether an error passed to the OTel
|
||||
// error handler belongs to this category.
|
||||
type PartialSuccess struct {
|
||||
ErrorMessage string
|
||||
RejectedItems int64
|
||||
RejectedKind string
|
||||
}
|
||||
|
||||
var _ error = PartialSuccess{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (ps PartialSuccess) Error() string {
|
||||
msg := ps.ErrorMessage
|
||||
if msg == "" {
|
||||
msg = "empty message"
|
||||
}
|
||||
return fmt.Sprintf("OTLP partial success: %s (%d %s rejected)", msg, ps.RejectedItems, ps.RejectedKind)
|
||||
}
|
||||
|
||||
// Is supports the errors.Is() interface.
|
||||
func (PartialSuccess) Is(err error) bool {
|
||||
_, ok := err.(PartialSuccess)
|
||||
return ok
|
||||
}
|
||||
|
||||
// LogPartialSuccessError returns an error describing a partial success
|
||||
// response for the log signal.
|
||||
func LogPartialSuccessError(itemsRejected int64, errorMessage string) error {
|
||||
return PartialSuccess{
|
||||
ErrorMessage: errorMessage,
|
||||
RejectedItems: itemsRejected,
|
||||
RejectedKind: "logs",
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+5
@@ -94,6 +94,11 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if context is canceled before attempting to wait and retry.
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("%w: %w", ctx.Err(), err)
|
||||
}
|
||||
|
||||
if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time elapsed: %w", err)
|
||||
}
|
||||
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry otlploggrpc
|
||||
// exporter in use.
|
||||
const Version = "0.14.0"
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
# Experimental Features
|
||||
|
||||
The `otlploggrpc` exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the `otlploggrpc` exporter prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
|
||||
|
||||
These features may change in backwards incompatible ways as feedback is applied.
|
||||
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
|
||||
|
||||
## Features
|
||||
|
||||
- [Observability](#observability)
|
||||
|
||||
### Observability
|
||||
|
||||
The `otlploggrpc` exporter can be configured to provide observability about itself using OpenTelemetry metrics.
|
||||
|
||||
To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`.
|
||||
|
||||
When enabled, the exporter will create the following metrics using the global `MeterProvider`:
|
||||
|
||||
- `otel.sdk.exporter.log.inflight`
|
||||
- `otel.sdk.exporter.log.exported`
|
||||
- `otel.sdk.exporter.operation.duration`
|
||||
|
||||
Please see the [Semantic conventions for OpenTelemetry SDK metrics] documentation for more details on these metrics.
|
||||
|
||||
[Semantic conventions for OpenTelemetry SDK metrics]: https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/otel/sdk-metrics.md
|
||||
|
||||
## Compatibility and Stability
|
||||
|
||||
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../../../VERSIONING.md).
|
||||
These features may be removed or modified in successive version releases, including patch versions.
|
||||
|
||||
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.
|
||||
There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version.
|
||||
If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support.
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that determines if exporter
|
||||
// observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Feature is an experimental feature control flag. It provides a uniform way
|
||||
// to interact with these feature flags and parse their values.
|
||||
type Feature[T any] struct {
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// Lookup returns the user configured value for the feature and true if the
|
||||
// user has enabled the feature. Otherwise, if the feature is not enabled, a
|
||||
// zero-value and false are returned.
|
||||
func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value
|
||||
//
|
||||
// > The SDK MUST interpret an empty value of an environment variable the
|
||||
// > same way as when the variable is unset.
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
func (f Feature[T]) Enabled() bool {
|
||||
_, ok := f.Lookup()
|
||||
return ok
|
||||
}
|
||||
+31
-8
@@ -16,13 +16,15 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
|
||||
logpb "go.opentelemetry.io/proto/otlp/logs/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/observ"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/retry"
|
||||
)
|
||||
|
||||
@@ -41,6 +43,14 @@ func newNoopClient() *client {
|
||||
return &client{}
|
||||
}
|
||||
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// nextExporterID returns the next unique ID for an exporter.
|
||||
func nextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// newHTTPClient creates a new HTTP log client.
|
||||
func newHTTPClient(cfg config) (*client, error) {
|
||||
hc := cfg.httpClient
|
||||
@@ -93,7 +103,11 @@ func newHTTPClient(cfg config) (*client, error) {
|
||||
requestFunc: cfg.retryCfg.Value.RequestFunc(evaluate),
|
||||
client: hc,
|
||||
}
|
||||
return &client{uploadLogs: c.uploadLogs}, nil
|
||||
|
||||
id := nextExporterID()
|
||||
c.inst, err = observ.NewInstrumentation(id, cfg.endpoint.Value)
|
||||
|
||||
return &client{uploadLogs: c.uploadLogs}, err
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
@@ -102,6 +116,8 @@ type httpClient struct {
|
||||
compression Compression
|
||||
requestFunc retry.RequestFunc
|
||||
client *http.Client
|
||||
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
// Keep it in sync with golang's DefaultTransport from net/http! We
|
||||
@@ -121,7 +137,7 @@ var ourTransport = &http.Transport{
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
func (c *httpClient) uploadLogs(ctx context.Context, data []*logpb.ResourceLogs) error {
|
||||
func (c *httpClient) uploadLogs(ctx context.Context, data []*logpb.ResourceLogs) (uploadErr error) {
|
||||
// The Exporter synchronizes access to client methods. This is not called
|
||||
// after the Exporter is shutdown. Only thing to do here is send data.
|
||||
|
||||
@@ -135,7 +151,13 @@ func (c *httpClient) uploadLogs(ctx context.Context, data []*logpb.ResourceLogs)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
var statusCode int
|
||||
if c.inst != nil {
|
||||
op := c.inst.ExportLogs(ctx, int64(len(data)))
|
||||
defer func() { op.End(uploadErr, statusCode) }()
|
||||
}
|
||||
|
||||
return errors.Join(uploadErr, c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
select {
|
||||
case <-iCtx.Done():
|
||||
return iCtx.Err()
|
||||
@@ -154,11 +176,12 @@ func (c *httpClient) uploadLogs(ctx context.Context, data []*logpb.ResourceLogs)
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
otel.Handle(err)
|
||||
uploadErr = errors.Join(uploadErr, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
statusCode = resp.StatusCode
|
||||
if sc := resp.StatusCode; sc >= 200 && sc <= 299 {
|
||||
// Success, do not retry.
|
||||
|
||||
@@ -181,8 +204,8 @@ func (c *httpClient) uploadLogs(ctx context.Context, data []*logpb.ResourceLogs)
|
||||
msg := respProto.PartialSuccess.GetErrorMessage()
|
||||
n := respProto.PartialSuccess.GetRejectedLogRecords()
|
||||
if n != 0 || msg != "" {
|
||||
err := fmt.Errorf("OTLP partial success: %s (%d log records rejected)", msg, n)
|
||||
otel.Handle(err)
|
||||
err := internal.LogPartialSuccessError(n, msg)
|
||||
uploadErr = errors.Join(uploadErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +238,7 @@ func (c *httpClient) uploadLogs(ctx context.Context, data []*logpb.ResourceLogs)
|
||||
// Non-retryable failure.
|
||||
return fmt.Errorf("failed to send logs to %s: %s (%w)", request.URL, resp.Status, bodyErr)
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
var gzPool = sync.Pool{
|
||||
|
||||
+50
-9
@@ -36,7 +36,10 @@ var (
|
||||
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
}
|
||||
envInsecure = envEndpoint
|
||||
envInsecure = []string{
|
||||
"OTEL_EXPORTER_OTLP_LOGS_INSECURE",
|
||||
"OTEL_EXPORTER_OTLP_INSECURE",
|
||||
}
|
||||
|
||||
// Split because these are parsed differently.
|
||||
envPathSignal = []string{"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"}
|
||||
@@ -114,6 +117,7 @@ func newConfig(options []Option) config {
|
||||
fallback[string](defaultPath),
|
||||
)
|
||||
c.insecure = c.insecure.Resolve(
|
||||
loadInsecureFromEnvEndpoint(envEndpoint),
|
||||
getenv[bool](envInsecure, convInsecure),
|
||||
)
|
||||
c.tlsCfg = c.tlsCfg.Resolve(
|
||||
@@ -185,7 +189,7 @@ func WithEndpointURL(rawURL string) Option {
|
||||
return fnOpt(func(c config) config {
|
||||
c.endpoint = newSetting(u.Host)
|
||||
c.path = newSetting(u.Path)
|
||||
c.insecure = newSetting(u.Scheme != "https")
|
||||
c.insecure = insecureFromScheme(c.insecure, u.Scheme)
|
||||
return c
|
||||
})
|
||||
}
|
||||
@@ -541,15 +545,52 @@ func convPath(s string) (string, error) {
|
||||
return u.Path + "/v1/logs", nil
|
||||
}
|
||||
|
||||
// convInsecure parses s as a URL string and returns if the connection should
|
||||
// use client transport security or not. If s is an invalid URL, false and an
|
||||
// error are returned.
|
||||
// convInsecure converts s from string to bool without case sensitivity.
|
||||
// If s is not valid returns error.
|
||||
func convInsecure(s string) (bool, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return false, err
|
||||
s = strings.ToLower(s)
|
||||
if s != "true" && s != "false" {
|
||||
return false, fmt.Errorf("can't convert %q to bool", s)
|
||||
}
|
||||
return u.Scheme != "https", nil
|
||||
|
||||
return s == "true", nil
|
||||
}
|
||||
|
||||
// loadInsecureFromEnvEndpoint returns a resolver that fetches
|
||||
// insecure setting from envEndpoint is it possible.
|
||||
func loadInsecureFromEnvEndpoint(envEndpoint []string) resolver[bool] {
|
||||
return func(s setting[bool]) setting[bool] {
|
||||
if s.Set {
|
||||
// Passed, valid, options have precedence.
|
||||
return s
|
||||
}
|
||||
|
||||
for _, key := range envEndpoint {
|
||||
if vStr := os.Getenv(key); vStr != "" {
|
||||
u, err := url.Parse(vStr)
|
||||
if err != nil {
|
||||
otel.Handle(fmt.Errorf("invalid %s value %s: %w", key, vStr, err))
|
||||
continue
|
||||
}
|
||||
|
||||
return insecureFromScheme(s, u.Scheme)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// insecureFromScheme return setting if the connection should
|
||||
// use client transport security or not.
|
||||
// Empty scheme doesn't force insecure setting.
|
||||
func insecureFromScheme(prev setting[bool], scheme string) setting[bool] {
|
||||
if scheme == "https" {
|
||||
return newSetting(false)
|
||||
} else if scheme != "" {
|
||||
return newSetting(true)
|
||||
}
|
||||
|
||||
return prev
|
||||
}
|
||||
|
||||
// convHeaders converts the OTel environment variable header value s into a
|
||||
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal functionality for the otlploghttp
|
||||
// package.
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal"
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry.go.tmpl "--data={}" --out=retry/retry.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry_test.go.tmpl "--data={}" --out=retry/retry_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/attr_test.go.tmpl "--data={}" --out=transform/attr_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log.go.tmpl "--data={}" --out=transform/log.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl "--data={}" --out=transform/log_attr_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_test.go.tmpl "--data={}" --out=transform/log_test.go
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides experimental observability instrumentation for the
|
||||
// otlploghttp exporter.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/observ"
|
||||
Generated
Vendored
+347
@@ -0,0 +1,347 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeName is the unique name of the meter used for instrumentation.
|
||||
ScopeName = "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/observ"
|
||||
|
||||
// Version is the current version of this instrumentation
|
||||
//
|
||||
// This matches the version of the exporter.
|
||||
Version = internal.Version
|
||||
)
|
||||
|
||||
var (
|
||||
attrsPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 + // component.name
|
||||
1 + // component.type
|
||||
1 + // server.addr
|
||||
1 + // server.port
|
||||
1 + // error.port
|
||||
1 // http.response.status.code
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
return &s
|
||||
},
|
||||
}
|
||||
addOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
s := make([]metric.AddOption, 0, n)
|
||||
return &s
|
||||
},
|
||||
}
|
||||
recordPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
s := make([]metric.RecordOption, 0, n)
|
||||
return &s
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func get[T any](pool *sync.Pool) *[]T {
|
||||
return pool.Get().(*[]T)
|
||||
}
|
||||
|
||||
func put[T any](pool *sync.Pool, value *[]T) {
|
||||
*value = (*value)[:0]
|
||||
pool.Put(value)
|
||||
}
|
||||
|
||||
// GetComponentName returns the constant name for the exporter with the
|
||||
// provided id.
|
||||
func GetComponentName(id int64) string {
|
||||
return fmt.Sprintf("%s/%d", otelconv.ComponentTypeOtlpHTTPLogExporter, id)
|
||||
}
|
||||
|
||||
// Instrumentation is experimental instrumentation for the exporter.
|
||||
type Instrumentation struct {
|
||||
inflightMetric metric.Int64UpDownCounter
|
||||
exportedMetric metric.Int64Counter
|
||||
operationDuration metric.Float64Histogram
|
||||
|
||||
presetAttrs []attribute.KeyValue
|
||||
addOpt metric.AddOption
|
||||
recordOpt metric.RecordOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for otlplog http exporter.
|
||||
func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
inst := &Instrumentation{}
|
||||
|
||||
provider := otel.GetMeterProvider()
|
||||
m := provider.Meter(
|
||||
ScopeName,
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
)
|
||||
|
||||
var e, err error
|
||||
logInflight, e := otelconv.NewSDKExporterLogInflight(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create the inflight metric %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
inst.inflightMetric = logInflight.Inst()
|
||||
|
||||
exported, e := otelconv.NewSDKExporterLogExported(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create the exported metric %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
inst.exportedMetric = exported.Inst()
|
||||
|
||||
operation, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create the operation duration metric %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
inst.operationDuration = operation.Inst()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inst.presetAttrs = setPresetAttrs(GetComponentName(id), target)
|
||||
|
||||
inst.addOpt = metric.WithAttributeSet(attribute.NewSet(inst.presetAttrs...))
|
||||
inst.recordOpt = metric.WithAttributeSet(attribute.NewSet(append(
|
||||
[]attribute.KeyValue{semconv.HTTPResponseStatusCode(http.StatusOK)},
|
||||
inst.presetAttrs...,
|
||||
)...))
|
||||
|
||||
return inst, nil
|
||||
}
|
||||
|
||||
func setPresetAttrs(name, target string) []attribute.KeyValue {
|
||||
addrAttrs := ServerAddrAttrs(target)
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, 2+len(addrAttrs))
|
||||
attrs = append(
|
||||
attrs,
|
||||
semconv.OTelComponentName(name),
|
||||
semconv.OTelComponentTypeOtlpHTTPLogExporter,
|
||||
)
|
||||
attrs = append(attrs, addrAttrs...)
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ServerAddrAttrs is a function that extracts server address and port attributes
|
||||
// from a target string.
|
||||
func ServerAddrAttrs(target string) []attribute.KeyValue {
|
||||
host, port, err := parseTarget(target)
|
||||
if err != nil || (host == "" && port < 0) {
|
||||
if err != nil {
|
||||
global.Debug("failed to parse target", "target", target, "error", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if port < 0 {
|
||||
return []attribute.KeyValue{semconv.ServerAddress(host)}
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return []attribute.KeyValue{
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
return []attribute.KeyValue{
|
||||
semconv.ServerAddress(host),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instrumentation) ExportLogs(ctx context.Context, count int64) ExportOp {
|
||||
start := time.Now()
|
||||
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, i.addOpt)
|
||||
i.inflightMetric.Add(ctx, count, *addOpt...)
|
||||
|
||||
return ExportOp{
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
inst: i,
|
||||
count: count,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp tracks the operationDuration being observed by [Instrumentation.ExportLogs].
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
inst *Instrumentation
|
||||
count int64
|
||||
}
|
||||
|
||||
// End completes the observation of the operationDuration being observed by a call to
|
||||
// [Instrumentation.ExportLogs].
|
||||
// Any error that is encountered is provided as err.
|
||||
//
|
||||
// If err is not nil, all logs will be recorded as failures unless error is of
|
||||
// type [internal.PartialSuccess]. In the case of a PartialSuccess, the number
|
||||
// of successfully exported logs will be determined by inspecting the
|
||||
// RejectedItems field of the PartialSuccess.
|
||||
func (e ExportOp) End(err error, code int) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, e.inst.addOpt)
|
||||
|
||||
e.inst.inflightMetric.Add(e.ctx, -e.count, *addOpt...)
|
||||
success := successful(e.count, err)
|
||||
e.inst.exportedMetric.Add(e.ctx, success, *addOpt...)
|
||||
|
||||
if err != nil {
|
||||
attrs := get[attribute.KeyValue](attrsPool)
|
||||
defer put(attrsPool, attrs)
|
||||
|
||||
*attrs = append(*attrs, e.inst.presetAttrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
a := metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
e.inst.exportedMetric.Add(e.ctx, e.count-success, a)
|
||||
}
|
||||
|
||||
record := get[metric.RecordOption](recordPool)
|
||||
defer put(recordPool, record)
|
||||
*record = append(*record, e.recordOption(err, code))
|
||||
|
||||
duration := time.Since(e.start).Seconds()
|
||||
e.inst.operationDuration.Record(e.ctx, duration, *record...)
|
||||
}
|
||||
|
||||
func (e ExportOp) recordOption(err error, code int) metric.RecordOption {
|
||||
if err == nil {
|
||||
return e.inst.recordOpt
|
||||
}
|
||||
|
||||
attrs := get[attribute.KeyValue](attrsPool)
|
||||
defer put(attrsPool, attrs)
|
||||
|
||||
*attrs = append(*attrs, e.inst.presetAttrs...)
|
||||
*attrs = append(
|
||||
*attrs,
|
||||
semconv.HTTPResponseStatusCode(code),
|
||||
semconv.ErrorType(err),
|
||||
)
|
||||
return metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
}
|
||||
|
||||
// successful returns the number of successfully exported logs out of the n
|
||||
// that were exported based on the provided error.
|
||||
//
|
||||
// If err is nil, n is returned. All logs were successfully exported.
|
||||
//
|
||||
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
|
||||
// It is assumed all logs failed to be exported.
|
||||
//
|
||||
// If err is an [internal.PartialSuccess] error, the number of successfully
|
||||
// exported logs is computed by subtracting the RejectedItems field from n. If
|
||||
// RejectedItems is negative, n is returned. If RejectedItems is greater than
|
||||
// n, 0 is returned.
|
||||
func successful(count int64, err error) int64 {
|
||||
if err == nil {
|
||||
return count
|
||||
}
|
||||
return count - rejected(count, err)
|
||||
}
|
||||
|
||||
var errPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(internal.PartialSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// rejected returns how many out of the n logs exporter were rejected based on
|
||||
// the provided non-nil err.
|
||||
func rejected(n int64, err error) int64 {
|
||||
ps := errPool.Get().(*internal.PartialSuccess)
|
||||
defer errPool.Put(ps)
|
||||
|
||||
if errors.As(err, ps) {
|
||||
// Bound RejectedItems to [0, n]. This should not be needed,
|
||||
// but be defensive as this is from an external source.
|
||||
return min(max(ps.RejectedItems, 0), n)
|
||||
}
|
||||
// all logs exported
|
||||
return n
|
||||
}
|
||||
|
||||
// parseEndpoint parses the host and port from target that has the form
|
||||
// "host[:port]", or it returns an error if the target is not parsable.
|
||||
//
|
||||
// If no port is specified, -1 is returned.
|
||||
//
|
||||
// If no host is specified, an empty string is returned.
|
||||
func parseTarget(endpoint string) (string, int, error) {
|
||||
if ip := parseIP(endpoint); ip != "" {
|
||||
return ip, -1, nil
|
||||
}
|
||||
|
||||
// If there's no colon, there is no port (IPv6 with no port checked above).
|
||||
if !strings.Contains(endpoint, ":") {
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
// Otherwise, parse as host:port.
|
||||
host, portStr, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid host:port %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
const base, bitSize = 10, 16
|
||||
port16, err := strconv.ParseUint(portStr, base, bitSize)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||
}
|
||||
port := int(port16)
|
||||
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
// parseIP attempts to parse the entire target as an IP address.
|
||||
// It returns the normalized string form of the IP if successful,
|
||||
// or an empty string if parsing fails.
|
||||
func parseIP(ip string) string {
|
||||
// Strip leading and trailing brackets for IPv6 addresses.
|
||||
if len(ip) >= 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
ip = ip[1 : len(ip)-1]
|
||||
}
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Return the normalized string form of the IP.
|
||||
return addr.String()
|
||||
}
|
||||
Generated
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal"
|
||||
|
||||
import "fmt"
|
||||
|
||||
// PartialSuccess represents the underlying error for all handling
|
||||
// OTLP partial success messages. Use `errors.Is(err,
|
||||
// PartialSuccess{})` to test whether an error passed to the OTel
|
||||
// error handler belongs to this category.
|
||||
type PartialSuccess struct {
|
||||
ErrorMessage string
|
||||
RejectedItems int64
|
||||
RejectedKind string
|
||||
}
|
||||
|
||||
var _ error = PartialSuccess{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (ps PartialSuccess) Error() string {
|
||||
msg := ps.ErrorMessage
|
||||
if msg == "" {
|
||||
msg = "empty message"
|
||||
}
|
||||
return fmt.Sprintf("OTLP partial success: %s (%d %s rejected)", msg, ps.RejectedItems, ps.RejectedKind)
|
||||
}
|
||||
|
||||
// Is supports the errors.Is() interface.
|
||||
func (PartialSuccess) Is(err error) bool {
|
||||
_, ok := err.(PartialSuccess)
|
||||
return ok
|
||||
}
|
||||
|
||||
// LogPartialSuccessError returns an error describing a partial success.
|
||||
func LogPartialSuccessError(itemsRejected int64, errorMessage string) error {
|
||||
return PartialSuccess{
|
||||
ErrorMessage: errorMessage,
|
||||
RejectedItems: itemsRejected,
|
||||
RejectedKind: "logs",
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+5
@@ -94,6 +94,11 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if context is canceled before attempting to wait and retry.
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("%w: %w", ctx.Err(), err)
|
||||
}
|
||||
|
||||
if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time elapsed: %w", err)
|
||||
}
|
||||
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP over HTTP/protobuf logs exporter in use.
|
||||
const Version = "0.14.0"
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
# Experimental Features
|
||||
|
||||
The `otlploghttp` exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the `otlploghttp` exporter prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
|
||||
|
||||
These features may change in backwards incompatible ways as feedback is applied.
|
||||
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
|
||||
|
||||
## Features
|
||||
|
||||
- [Observability](#observability)
|
||||
|
||||
### Observability
|
||||
|
||||
The `otlploghttp` exporter can be configured to provide observability about itself using OpenTelemetry metrics.
|
||||
|
||||
To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`.
|
||||
|
||||
When enabled, the exporter will create the following metrics using the global `MeterProvider`:
|
||||
|
||||
- `otel.sdk.exporter.log.inflight`
|
||||
- `otel.sdk.exporter.log.exported`
|
||||
- `otel.sdk.exporter.operation.duration`
|
||||
|
||||
Please see the [Semantic conventions for OpenTelemetry SDK metrics] documentation for more details on these metrics.
|
||||
|
||||
[Semantic conventions for OpenTelemetry SDK metrics]: https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/otel/sdk-metrics.md
|
||||
|
||||
## Compatibility and Stability
|
||||
|
||||
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../../../VERSIONING.md).
|
||||
These features may be removed or modified in successive version releases, including patch versions.
|
||||
|
||||
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.
|
||||
There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version.
|
||||
If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support.
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that determines if exporter
|
||||
// observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Feature is an experimental feature control flag. It provides a uniform way
|
||||
// to interact with these feature flags and parse their values.
|
||||
type Feature[T any] struct {
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// Lookup returns the user configured value for the feature and true if the
|
||||
// user has enabled the feature. Otherwise, if the feature is not enabled, a
|
||||
// zero-value and false are returned.
|
||||
func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value
|
||||
//
|
||||
// > The SDK MUST interpret an empty value of an environment variable the
|
||||
// > same way as when the variable is unset.
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
func (f Feature[T]) Enabled() bool {
|
||||
_, ok := f.Lookup()
|
||||
return ok
|
||||
}
|
||||
Generated
Vendored
+5
-6
@@ -16,7 +16,6 @@ import (
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/retry"
|
||||
@@ -101,7 +100,7 @@ func (c *client) Shutdown(ctx context.Context) error {
|
||||
//
|
||||
// Retryable errors from the server will be handled according to any
|
||||
// RetryConfig the client was created with.
|
||||
func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics) error {
|
||||
func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics) (uploadErr error) {
|
||||
// The otlpmetric.Exporter synchronizes access to client methods, and
|
||||
// ensures this is not called after the Exporter is shutdown. Only thing
|
||||
// to do here is send data.
|
||||
@@ -116,7 +115,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
return c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
return errors.Join(uploadErr, c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
resp, err := c.msc.Export(iCtx, &colmetricpb.ExportMetricsServiceRequest{
|
||||
ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics},
|
||||
})
|
||||
@@ -124,8 +123,8 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedDataPoints()
|
||||
if n != 0 || msg != "" {
|
||||
err := internal.MetricPartialSuccessError(n, msg)
|
||||
otel.Handle(err)
|
||||
e := internal.MetricPartialSuccessError(n, msg)
|
||||
uploadErr = errors.Join(uploadErr, e)
|
||||
}
|
||||
}
|
||||
// nil is converted to OK.
|
||||
@@ -134,7 +133,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
// exportContext returns a copy of parent with an appropriate deadline and
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -20,7 +20,7 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/optiontypes.go.tmpl "--data={}" --out=oconf/optiontypes.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/tls.go.tmpl "--data={}" --out=oconf/tls.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal\"}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal\"}" --out=otest/client_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/collector.go.tmpl "--data={\"oconfImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf\"}" --out=otest/collector.go
|
||||
|
||||
|
||||
vendor/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go
Generated
Vendored
+3
-26
@@ -18,7 +18,6 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
)
|
||||
|
||||
// DefaultEnvOptionsReader is the default environments reader.
|
||||
@@ -165,11 +164,11 @@ func withEnvTemporalityPreference(n string, fn func(metric.TemporalitySelector))
|
||||
if s, ok := e.GetEnvValue(n); ok {
|
||||
switch strings.ToLower(s) {
|
||||
case "cumulative":
|
||||
fn(cumulativeTemporality)
|
||||
fn(metric.CumulativeTemporalitySelector)
|
||||
case "delta":
|
||||
fn(deltaTemporality)
|
||||
fn(metric.DeltaTemporalitySelector)
|
||||
case "lowmemory":
|
||||
fn(lowMemory)
|
||||
fn(metric.LowMemoryTemporalitySelector)
|
||||
default:
|
||||
global.Warn(
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE is set to an invalid value, ignoring.",
|
||||
@@ -181,28 +180,6 @@ func withEnvTemporalityPreference(n string, fn func(metric.TemporalitySelector))
|
||||
}
|
||||
}
|
||||
|
||||
func cumulativeTemporality(metric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
|
||||
func deltaTemporality(ik metric.InstrumentKind) metricdata.Temporality {
|
||||
switch ik {
|
||||
case metric.InstrumentKindCounter, metric.InstrumentKindHistogram, metric.InstrumentKindObservableCounter:
|
||||
return metricdata.DeltaTemporality
|
||||
default:
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
}
|
||||
|
||||
func lowMemory(ik metric.InstrumentKind) metricdata.Temporality {
|
||||
switch ik {
|
||||
case metric.InstrumentKindCounter, metric.InstrumentKindHistogram:
|
||||
return metricdata.DeltaTemporality
|
||||
default:
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
}
|
||||
|
||||
func withEnvAggPreference(n string, fn func(metric.AggregationSelector)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if s, ok := e.GetEnvValue(n); ok {
|
||||
|
||||
Generated
Vendored
+11
@@ -29,6 +29,17 @@ func (ps PartialSuccess) Error() string {
|
||||
return fmt.Sprintf("OTLP partial success: %s (%d %s rejected)", msg, ps.RejectedItems, ps.RejectedKind)
|
||||
}
|
||||
|
||||
// As returns true if ps can be assigned to target and makes the assignment.
|
||||
// Otherwise, it returns false. This supports the errors.As() interface.
|
||||
func (ps PartialSuccess) As(target any) bool {
|
||||
t, ok := target.(*PartialSuccess)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
*t = ps
|
||||
return true
|
||||
}
|
||||
|
||||
// Is supports the errors.Is() interface.
|
||||
func (ps PartialSuccess) Is(err error) bool {
|
||||
_, ok := err.(PartialSuccess)
|
||||
|
||||
Generated
Vendored
+5
@@ -94,6 +94,11 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if context is canceled before attempting to wait and retry.
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("%w: %w", ctx.Err(), err)
|
||||
}
|
||||
|
||||
if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time elapsed: %w", err)
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -5,5 +5,5 @@ package otlpmetricgrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP over gRPC metrics exporter in use.
|
||||
func Version() string {
|
||||
return "1.38.0"
|
||||
return "1.39.0"
|
||||
}
|
||||
|
||||
Generated
Vendored
+5
-6
@@ -22,7 +22,6 @@ import (
|
||||
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry"
|
||||
@@ -122,7 +121,7 @@ func (c *client) Shutdown(ctx context.Context) error {
|
||||
//
|
||||
// Retryable errors from the server will be handled according to any
|
||||
// RetryConfig the client was created with.
|
||||
func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics) error {
|
||||
func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics) (uploadErr error) {
|
||||
// The otlpmetric.Exporter synchronizes access to client methods, and
|
||||
// ensures this is not called after the Exporter is shutdown. Only thing
|
||||
// to do here is send data.
|
||||
@@ -139,7 +138,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
return err
|
||||
}
|
||||
|
||||
return c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
return errors.Join(uploadErr, c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
select {
|
||||
case <-iCtx.Done():
|
||||
return iCtx.Err()
|
||||
@@ -158,7 +157,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
otel.Handle(err)
|
||||
uploadErr = errors.Join(uploadErr, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -186,7 +185,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
n := respProto.PartialSuccess.GetRejectedDataPoints()
|
||||
if n != 0 || msg != "" {
|
||||
err := internal.MetricPartialSuccessError(n, msg)
|
||||
otel.Handle(err)
|
||||
uploadErr = errors.Join(uploadErr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +218,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
// Non-retryable failure.
|
||||
return fmt.Errorf("failed to send metrics to %s: %s (%w)", request.URL, resp.Status, bodyErr)
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
var gzPool = sync.Pool{
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -20,7 +20,7 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/optiontypes.go.tmpl "--data={}" --out=oconf/optiontypes.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/tls.go.tmpl "--data={}" --out=oconf/tls.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal\"}" --out=otest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal\"}" --out=otest/client_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/collector.go.tmpl "--data={\"oconfImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf\"}" --out=otest/collector.go
|
||||
|
||||
|
||||
vendor/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go
Generated
Vendored
+3
-26
@@ -18,7 +18,6 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
)
|
||||
|
||||
// DefaultEnvOptionsReader is the default environments reader.
|
||||
@@ -165,11 +164,11 @@ func withEnvTemporalityPreference(n string, fn func(metric.TemporalitySelector))
|
||||
if s, ok := e.GetEnvValue(n); ok {
|
||||
switch strings.ToLower(s) {
|
||||
case "cumulative":
|
||||
fn(cumulativeTemporality)
|
||||
fn(metric.CumulativeTemporalitySelector)
|
||||
case "delta":
|
||||
fn(deltaTemporality)
|
||||
fn(metric.DeltaTemporalitySelector)
|
||||
case "lowmemory":
|
||||
fn(lowMemory)
|
||||
fn(metric.LowMemoryTemporalitySelector)
|
||||
default:
|
||||
global.Warn(
|
||||
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE is set to an invalid value, ignoring.",
|
||||
@@ -181,28 +180,6 @@ func withEnvTemporalityPreference(n string, fn func(metric.TemporalitySelector))
|
||||
}
|
||||
}
|
||||
|
||||
func cumulativeTemporality(metric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
|
||||
func deltaTemporality(ik metric.InstrumentKind) metricdata.Temporality {
|
||||
switch ik {
|
||||
case metric.InstrumentKindCounter, metric.InstrumentKindHistogram, metric.InstrumentKindObservableCounter:
|
||||
return metricdata.DeltaTemporality
|
||||
default:
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
}
|
||||
|
||||
func lowMemory(ik metric.InstrumentKind) metricdata.Temporality {
|
||||
switch ik {
|
||||
case metric.InstrumentKindCounter, metric.InstrumentKindHistogram:
|
||||
return metricdata.DeltaTemporality
|
||||
default:
|
||||
return metricdata.CumulativeTemporality
|
||||
}
|
||||
}
|
||||
|
||||
func withEnvAggPreference(n string, fn func(metric.AggregationSelector)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if s, ok := e.GetEnvValue(n); ok {
|
||||
|
||||
Generated
Vendored
+11
@@ -29,6 +29,17 @@ func (ps PartialSuccess) Error() string {
|
||||
return fmt.Sprintf("OTLP partial success: %s (%d %s rejected)", msg, ps.RejectedItems, ps.RejectedKind)
|
||||
}
|
||||
|
||||
// As returns true if ps can be assigned to target and makes the assignment.
|
||||
// Otherwise, it returns false. This supports the errors.As() interface.
|
||||
func (ps PartialSuccess) As(target any) bool {
|
||||
t, ok := target.(*PartialSuccess)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
*t = ps
|
||||
return true
|
||||
}
|
||||
|
||||
// Is supports the errors.Is() interface.
|
||||
func (ps PartialSuccess) Is(err error) bool {
|
||||
_, ok := err.(PartialSuccess)
|
||||
|
||||
Generated
Vendored
+5
@@ -94,6 +94,11 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if context is canceled before attempting to wait and retry.
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("%w: %w", ctx.Err(), err)
|
||||
}
|
||||
|
||||
if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time elapsed: %w", err)
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -5,5 +5,5 @@ package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP over HTTP/protobuf metrics exporter in use.
|
||||
func Version() string {
|
||||
return "1.38.0"
|
||||
return "1.39.0"
|
||||
}
|
||||
|
||||
+4
-26
@@ -4,14 +4,10 @@
|
||||
package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/otlptranslator"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
)
|
||||
|
||||
@@ -28,12 +24,6 @@ type config struct {
|
||||
resourceAttributesFilter attribute.Filter
|
||||
}
|
||||
|
||||
var logTemporaryDefault = sync.OnceFunc(func() {
|
||||
global.Warn(
|
||||
"The default Prometheus naming translation strategy is planned to be changed from otlptranslator.NoUTF8EscapingWithSuffixes to otlptranslator.UnderscoreEscapingWithSuffixes in a future release. Add prometheus.WithTranslationStrategy(otlptranslator.NoUTF8EscapingWithSuffixes) to preserve the existing behavior, or prometheus.WithTranslationStrategy(otlptranslator.UnderscoreEscapingWithSuffixes) to opt into the future default behavior.",
|
||||
)
|
||||
})
|
||||
|
||||
// newConfig creates a validated config configured with options.
|
||||
func newConfig(opts ...Option) config {
|
||||
cfg := config{}
|
||||
@@ -42,27 +32,15 @@ func newConfig(opts ...Option) config {
|
||||
}
|
||||
|
||||
if cfg.translationStrategy == "" {
|
||||
// If no translation strategy was specified, deduce one based on the global
|
||||
// NameValidationScheme. NOTE: this logic will change in the future, always
|
||||
// defaulting to UnderscoreEscapingWithSuffixes
|
||||
|
||||
//nolint:staticcheck // NameValidationScheme is deprecated but we still need it for now.
|
||||
if model.NameValidationScheme == model.UTF8Validation {
|
||||
logTemporaryDefault()
|
||||
cfg.translationStrategy = otlptranslator.NoUTF8EscapingWithSuffixes
|
||||
} else {
|
||||
cfg.translationStrategy = otlptranslator.UnderscoreEscapingWithSuffixes
|
||||
}
|
||||
} else {
|
||||
cfg.translationStrategy = otlptranslator.UnderscoreEscapingWithSuffixes
|
||||
} else if !cfg.translationStrategy.ShouldAddSuffixes() {
|
||||
// Note, if the translation strategy implies that suffixes should be added,
|
||||
// the user can still use WithoutUnits and WithoutCounterSuffixes to
|
||||
// explicitly disable specific suffixes. We do not override their preference
|
||||
// in this case. However if the chosen strategy disables suffixes, we should
|
||||
// forcibly disable all of them.
|
||||
if !cfg.translationStrategy.ShouldAddSuffixes() {
|
||||
cfg.withoutCounterSuffixes = true
|
||||
cfg.withoutUnits = true
|
||||
}
|
||||
cfg.withoutCounterSuffixes = true
|
||||
cfg.withoutUnits = true
|
||||
}
|
||||
|
||||
if cfg.registerer == nil {
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"
|
||||
|
||||
import "errors"
|
||||
|
||||
// Sentinel errors for consistent error checks in tests.
|
||||
var (
|
||||
errInvalidMetricType = errors.New("invalid metric type")
|
||||
errInvalidMetric = errors.New("invalid metric")
|
||||
errEHScaleBelowMin = errors.New("exponential histogram scale below minimum supported")
|
||||
)
|
||||
+156
-59
@@ -20,6 +20,8 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus/internal/counter"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus/internal/observ"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
@@ -92,6 +94,8 @@ type collector struct {
|
||||
metricNamer otlptranslator.MetricNamer
|
||||
labelNamer otlptranslator.LabelNamer
|
||||
unitNamer otlptranslator.UnitNamer
|
||||
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
// New returns a Prometheus Exporter.
|
||||
@@ -137,7 +141,10 @@ func New(opts ...Option) (*Exporter, error) {
|
||||
Reader: reader,
|
||||
}
|
||||
|
||||
return e, nil
|
||||
var err error
|
||||
collector.inst, err = observ.NewInstrumentation(counter.NextExporterID())
|
||||
|
||||
return e, err
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
@@ -153,9 +160,26 @@ func (*collector) Describe(chan<- *prometheus.Desc) {
|
||||
//
|
||||
// This method is safe to call concurrently.
|
||||
func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
var err error
|
||||
// Blocked by this issue: Propagate context.Context through Gather and Collect (#1538)
|
||||
// https://github.com/prometheus/client_golang/issues/1538.
|
||||
ctx := context.TODO()
|
||||
|
||||
if c.inst != nil {
|
||||
timer := c.inst.RecordOperationDuration(ctx)
|
||||
defer func() { timer.Stop(err) }()
|
||||
}
|
||||
|
||||
metrics := metricsPool.Get().(*metricdata.ResourceMetrics)
|
||||
defer metricsPool.Put(metrics)
|
||||
err := c.reader.Collect(context.TODO(), metrics)
|
||||
|
||||
endCollection := func(error) {}
|
||||
if c.inst != nil {
|
||||
endCollection = c.inst.RecordCollectionDuration(ctx).Stop
|
||||
}
|
||||
err = c.reader.Collect(ctx, metrics)
|
||||
endCollection(err)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, metric.ErrReaderShutdown) {
|
||||
return
|
||||
@@ -174,15 +198,16 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.targetInfo == nil && !c.disableTargetInfo {
|
||||
targetInfo, err := c.createInfoMetric(
|
||||
targetInfo, e := c.createInfoMetric(
|
||||
otlptranslator.TargetInfoMetricName,
|
||||
targetInfoDescription,
|
||||
metrics.Resource,
|
||||
)
|
||||
if err != nil {
|
||||
if e != nil {
|
||||
// If the target info metric is invalid, disable sending it.
|
||||
c.disableTargetInfo = true
|
||||
otel.Handle(err)
|
||||
otel.Handle(e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to createInfoMetric: %w", e))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -195,14 +220,15 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
}
|
||||
|
||||
if c.resourceAttributesFilter != nil && len(c.resourceKeyVals.keys) == 0 {
|
||||
err := c.createResourceAttributes(metrics.Resource)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
e := c.createResourceAttributes(metrics.Resource)
|
||||
if e != nil {
|
||||
otel.Handle(e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to createResourceAttributes: %w", e))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, scopeMetrics := range metrics.ScopeMetrics {
|
||||
for j, scopeMetrics := range metrics.ScopeMetrics {
|
||||
n := len(c.resourceKeyVals.keys) + 2 // resource attrs + scope name + scope version
|
||||
kv := keyVals{
|
||||
keys: make([]string, 0, n),
|
||||
@@ -213,9 +239,10 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
kv.keys = append(kv.keys, scopeNameLabel, scopeVersionLabel, scopeSchemaLabel)
|
||||
kv.vals = append(kv.vals, scopeMetrics.Scope.Name, scopeMetrics.Scope.Version, scopeMetrics.Scope.SchemaURL)
|
||||
|
||||
attrKeys, attrVals, err := getAttrs(scopeMetrics.Scope.Attributes, c.labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
attrKeys, attrVals, e := getAttrs(scopeMetrics.Scope.Attributes, c.labelNamer)
|
||||
if e != nil {
|
||||
reportError(ch, nil, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to getAttrs for ScopeMetrics %d: %w", j, e))
|
||||
continue
|
||||
}
|
||||
for i := range attrKeys {
|
||||
@@ -228,21 +255,22 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
kv.keys = append(kv.keys, c.resourceKeyVals.keys...)
|
||||
kv.vals = append(kv.vals, c.resourceKeyVals.vals...)
|
||||
|
||||
for _, m := range scopeMetrics.Metrics {
|
||||
for k, m := range scopeMetrics.Metrics {
|
||||
typ := c.metricType(m)
|
||||
if typ == nil {
|
||||
reportError(ch, nil, errInvalidMetricType)
|
||||
continue
|
||||
}
|
||||
name, err := c.getName(m)
|
||||
if err != nil {
|
||||
// TODO(#7066): Handle this error better. It's not clear this can be
|
||||
// reached, bad metric names should / will be caught at creation time.
|
||||
otel.Handle(err)
|
||||
name, e := c.getName(m)
|
||||
if e != nil {
|
||||
reportError(ch, nil, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to getAttrs for ScopeMetrics %d, Metrics %d: %w", j, k, e))
|
||||
continue
|
||||
}
|
||||
|
||||
drop, help := c.validateMetrics(name, m.Description, typ)
|
||||
if drop {
|
||||
reportError(ch, nil, errInvalidMetric)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -252,21 +280,21 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
|
||||
switch v := m.Data.(type) {
|
||||
case metricdata.Histogram[int64]:
|
||||
addHistogramMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addHistogramMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
case metricdata.Histogram[float64]:
|
||||
addHistogramMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addHistogramMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
case metricdata.ExponentialHistogram[int64]:
|
||||
addExponentialHistogramMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addExponentialHistogramMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
case metricdata.ExponentialHistogram[float64]:
|
||||
addExponentialHistogramMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addExponentialHistogramMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
case metricdata.Sum[int64]:
|
||||
addSumMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addSumMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
case metricdata.Sum[float64]:
|
||||
addSumMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addSumMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
case metricdata.Gauge[int64]:
|
||||
addGaugeMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addGaugeMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
case metricdata.Gauge[float64]:
|
||||
addGaugeMetric(ch, v, m, name, kv, c.labelNamer)
|
||||
addGaugeMetric(ch, v, m, name, kv, c.labelNamer, c.inst, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,11 +360,21 @@ func addExponentialHistogramMetric[N int64 | float64](
|
||||
name string,
|
||||
kv keyVals,
|
||||
labelNamer otlptranslator.LabelNamer,
|
||||
inst *observ.Instrumentation,
|
||||
ctx context.Context,
|
||||
) {
|
||||
for _, dp := range histogram.DataPoints {
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
var err error
|
||||
var success int64
|
||||
if inst != nil {
|
||||
op := inst.ExportMetrics(ctx, int64(len(histogram.DataPoints)))
|
||||
defer func() { op.End(success, err) }()
|
||||
}
|
||||
|
||||
for j, dp := range histogram.DataPoints {
|
||||
keys, values, e := getAttrs(dp.Attributes, labelNamer)
|
||||
if e != nil {
|
||||
reportError(ch, nil, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to getAttrs for histogram.DataPoints %d: %w", j, e))
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
@@ -348,9 +386,12 @@ func addExponentialHistogramMetric[N int64 | float64](
|
||||
scale := dp.Scale
|
||||
if scale < -4 {
|
||||
// Reject scales below -4 as they cannot be represented in Prometheus
|
||||
otel.Handle(fmt.Errorf(
|
||||
"exponential histogram scale %d is below minimum supported scale -4, skipping data point",
|
||||
scale))
|
||||
reportError(
|
||||
ch,
|
||||
desc,
|
||||
fmt.Errorf("%w: %d (min -4)", errEHScaleBelowMin, scale),
|
||||
)
|
||||
err = errors.Join(err, e)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -368,7 +409,9 @@ func addExponentialHistogramMetric[N int64 | float64](
|
||||
positiveBuckets := make(map[int]int64)
|
||||
for i, c := range positiveBucket.Counts {
|
||||
if c > math.MaxInt64 {
|
||||
otel.Handle(fmt.Errorf("positive count %d is too large to be represented as int64", c))
|
||||
e := fmt.Errorf("positive count %d is too large to be represented as int64", c)
|
||||
otel.Handle(e)
|
||||
err = errors.Join(err, e)
|
||||
continue
|
||||
}
|
||||
positiveBuckets[int(positiveBucket.Offset)+i+1] = int64(c) // nolint: gosec // Size check above.
|
||||
@@ -377,13 +420,15 @@ func addExponentialHistogramMetric[N int64 | float64](
|
||||
negativeBuckets := make(map[int]int64)
|
||||
for i, c := range negativeBucket.Counts {
|
||||
if c > math.MaxInt64 {
|
||||
otel.Handle(fmt.Errorf("negative count %d is too large to be represented as int64", c))
|
||||
e := fmt.Errorf("negative count %d is too large to be represented as int64", c)
|
||||
otel.Handle(e)
|
||||
err = errors.Join(err, e)
|
||||
continue
|
||||
}
|
||||
negativeBuckets[int(negativeBucket.Offset)+i+1] = int64(c) // nolint: gosec // Size check above.
|
||||
}
|
||||
|
||||
m, err := prometheus.NewConstNativeHistogram(
|
||||
m, e := prometheus.NewConstNativeHistogram(
|
||||
desc,
|
||||
dp.Count,
|
||||
float64(dp.Sum),
|
||||
@@ -394,12 +439,18 @@ func addExponentialHistogramMetric[N int64 | float64](
|
||||
dp.ZeroThreshold,
|
||||
dp.StartTime,
|
||||
values...)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
if e != nil {
|
||||
reportError(ch, desc, e)
|
||||
err = errors.Join(
|
||||
err,
|
||||
fmt.Errorf("failed to NewConstNativeHistogram for histogram.DataPoints %d: %w", j, e),
|
||||
)
|
||||
continue
|
||||
}
|
||||
m = addExemplars(m, dp.Exemplars, labelNamer)
|
||||
ch <- m
|
||||
|
||||
success++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,11 +461,21 @@ func addHistogramMetric[N int64 | float64](
|
||||
name string,
|
||||
kv keyVals,
|
||||
labelNamer otlptranslator.LabelNamer,
|
||||
inst *observ.Instrumentation,
|
||||
ctx context.Context,
|
||||
) {
|
||||
for _, dp := range histogram.DataPoints {
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
var err error
|
||||
var success int64
|
||||
if inst != nil {
|
||||
op := inst.ExportMetrics(ctx, int64(len(histogram.DataPoints)))
|
||||
defer func() { op.End(success, err) }()
|
||||
}
|
||||
|
||||
for j, dp := range histogram.DataPoints {
|
||||
keys, values, e := getAttrs(dp.Attributes, labelNamer)
|
||||
if e != nil {
|
||||
reportError(ch, nil, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to getAttrs for histogram.DataPoints %d: %w", j, e))
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
@@ -428,13 +489,16 @@ func addHistogramMetric[N int64 | float64](
|
||||
cumulativeCount += dp.BucketCounts[i]
|
||||
buckets[bound] = cumulativeCount
|
||||
}
|
||||
m, err := prometheus.NewConstHistogram(desc, dp.Count, float64(dp.Sum), buckets, values...)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
m, e := prometheus.NewConstHistogram(desc, dp.Count, float64(dp.Sum), buckets, values...)
|
||||
if e != nil {
|
||||
reportError(ch, desc, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to NewConstMetric for histogram.DataPoints %d: %w", j, e))
|
||||
continue
|
||||
}
|
||||
m = addExemplars(m, dp.Exemplars, labelNamer)
|
||||
ch <- m
|
||||
|
||||
success++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,25 +509,36 @@ func addSumMetric[N int64 | float64](
|
||||
name string,
|
||||
kv keyVals,
|
||||
labelNamer otlptranslator.LabelNamer,
|
||||
inst *observ.Instrumentation,
|
||||
ctx context.Context,
|
||||
) {
|
||||
var err error
|
||||
var success int64
|
||||
if inst != nil {
|
||||
op := inst.ExportMetrics(ctx, int64(len(sum.DataPoints)))
|
||||
defer func() { op.End(success, err) }()
|
||||
}
|
||||
|
||||
valueType := prometheus.CounterValue
|
||||
if !sum.IsMonotonic {
|
||||
valueType = prometheus.GaugeValue
|
||||
}
|
||||
|
||||
for _, dp := range sum.DataPoints {
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
for i, dp := range sum.DataPoints {
|
||||
keys, values, e := getAttrs(dp.Attributes, labelNamer)
|
||||
if e != nil {
|
||||
reportError(ch, nil, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to getAttrs for sum.DataPoints %d: %w", i, e))
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
values = append(values, kv.vals...)
|
||||
|
||||
desc := prometheus.NewDesc(name, m.Description, keys, nil)
|
||||
m, err := prometheus.NewConstMetric(desc, valueType, float64(dp.Value), values...)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
m, e := prometheus.NewConstMetric(desc, valueType, float64(dp.Value), values...)
|
||||
if e != nil {
|
||||
reportError(ch, desc, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to NewConstMetric for sum.DataPoints %d: %w", i, e))
|
||||
continue
|
||||
}
|
||||
// GaugeValues don't support Exemplars at this time
|
||||
@@ -472,6 +547,8 @@ func addSumMetric[N int64 | float64](
|
||||
m = addExemplars(m, dp.Exemplars, labelNamer)
|
||||
}
|
||||
ch <- m
|
||||
|
||||
success++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,23 +559,36 @@ func addGaugeMetric[N int64 | float64](
|
||||
name string,
|
||||
kv keyVals,
|
||||
labelNamer otlptranslator.LabelNamer,
|
||||
inst *observ.Instrumentation,
|
||||
ctx context.Context,
|
||||
) {
|
||||
for _, dp := range gauge.DataPoints {
|
||||
keys, values, err := getAttrs(dp.Attributes, labelNamer)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
var err error
|
||||
var success int64
|
||||
if inst != nil {
|
||||
op := inst.ExportMetrics(ctx, int64(len(gauge.DataPoints)))
|
||||
defer func() { op.End(success, err) }()
|
||||
}
|
||||
|
||||
for i, dp := range gauge.DataPoints {
|
||||
keys, values, e := getAttrs(dp.Attributes, labelNamer)
|
||||
if e != nil {
|
||||
reportError(ch, nil, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to getAttrs for gauge.DataPoints %d: %w", i, e))
|
||||
continue
|
||||
}
|
||||
keys = append(keys, kv.keys...)
|
||||
values = append(values, kv.vals...)
|
||||
|
||||
desc := prometheus.NewDesc(name, m.Description, keys, nil)
|
||||
m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(dp.Value), values...)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
m, e := prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(dp.Value), values...)
|
||||
if e != nil {
|
||||
reportError(ch, desc, e)
|
||||
err = errors.Join(err, fmt.Errorf("failed to NewConstMetric for gauge.DataPoints %d: %w", i, e))
|
||||
continue
|
||||
}
|
||||
ch <- m
|
||||
|
||||
success++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,3 +803,10 @@ func attributesToLabels(attrs []attribute.KeyValue, labelNamer otlptranslator.La
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func reportError(ch chan<- prometheus.Metric, desc *prometheus.Desc, err error) {
|
||||
if desc == nil {
|
||||
desc = prometheus.NewInvalidDesc(err)
|
||||
}
|
||||
ch <- prometheus.NewInvalidMetric(desc, err)
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/counter/counter.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package counter provides a simple counter for generating unique IDs.
|
||||
//
|
||||
// This package is used to generate unique IDs while allowing testing packages
|
||||
// to reset the counter.
|
||||
package counter // import "go.opentelemetry.io/otel/exporters/prometheus/internal/counter"
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// exporterN is a global 0-based count of the number of exporters created.
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// NextExporterID returns the next unique ID for an exporter.
|
||||
func NextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// SetExporterID sets the exporter ID counter to v and returns the previous
|
||||
// value.
|
||||
//
|
||||
// This function is useful for testing purposes, allowing you to reset the
|
||||
// counter. It should not be used in production code.
|
||||
func SetExporterID(v int64) int64 {
|
||||
return exporterN.Swap(v)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal functionality for the prometheus
|
||||
// package.
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/prometheus/internal"
|
||||
|
||||
//go:generate gotmpl --body=../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/prometheus/internal/counter\" }" --out=counter/counter.go
|
||||
//go:generate gotmpl --body=../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/prometheus\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
Generated
Vendored
+249
@@ -0,0 +1,249 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides experimental observability instrumentation
|
||||
// for the prometheus exporter.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/prometheus/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus/internal"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus/internal/x"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ComponentType uniquely identifies the OpenTelemetry Exporter component
|
||||
// being instrumented.
|
||||
ComponentType = "go.opentelemetry.io/otel/exporters/prometheus/prometheus.Exporter"
|
||||
|
||||
// ScopeName is the unique name of the meter used for instrumentation.
|
||||
ScopeName = "go.opentelemetry.io/otel/exporters/prometheus/internal/observ"
|
||||
|
||||
// SchemaURL is the schema URL of the metrics produced by this
|
||||
// instrumentation.
|
||||
SchemaURL = semconv.SchemaURL
|
||||
|
||||
// Version is the current version of this instrumentation.
|
||||
//
|
||||
// This matches the version of the exporter.
|
||||
Version = internal.Version
|
||||
)
|
||||
|
||||
var (
|
||||
measureAttrsPool = &sync.Pool{
|
||||
New: func() any {
|
||||
// "component.name" + "component.type" + "error.type"
|
||||
const n = 1 + 1 + 1
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
// Return a pointer to a slice instead of a slice itself
|
||||
// to avoid allocations on every call.
|
||||
return &s
|
||||
},
|
||||
}
|
||||
|
||||
addOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.AddOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
|
||||
recordOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.RecordOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||
|
||||
func put[T any](p *sync.Pool, s *[]T) {
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func ComponentName(id int64) string {
|
||||
return fmt.Sprintf("%s/%d", ComponentType, id)
|
||||
}
|
||||
|
||||
type Instrumentation struct {
|
||||
inflightMetric metric.Int64UpDownCounter
|
||||
exportedMetric metric.Int64Counter
|
||||
operationDuration metric.Float64Histogram
|
||||
collectionDuration metric.Float64Histogram
|
||||
|
||||
attrs []attribute.KeyValue
|
||||
setOpt metric.MeasurementOption
|
||||
}
|
||||
|
||||
func NewInstrumentation(id int64) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
i := &Instrumentation{
|
||||
attrs: []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(ComponentType),
|
||||
},
|
||||
}
|
||||
|
||||
s := attribute.NewSet(i.attrs...)
|
||||
i.setOpt = metric.WithAttributeSet(s)
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(SchemaURL),
|
||||
)
|
||||
|
||||
var err, e error
|
||||
|
||||
inflightMetric, e := otelconv.NewSDKExporterMetricDataPointInflight(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create inflight metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.inflightMetric = inflightMetric.Inst()
|
||||
|
||||
exportedMetric, e := otelconv.NewSDKExporterMetricDataPointExported(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create exported metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.exportedMetric = exportedMetric.Inst()
|
||||
|
||||
operationDuration, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.operationDuration = operationDuration.Inst()
|
||||
|
||||
collectionDuration, e := otelconv.NewSDKMetricReaderCollectionDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create collection duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.collectionDuration = collectionDuration.Inst()
|
||||
|
||||
return i, err
|
||||
}
|
||||
|
||||
// RecordOperationDuration starts the timing of an operation.
|
||||
//
|
||||
// It returns a [Timer] that tracks the operation duration. The [Timer.Stop]
|
||||
// method must be called when the operation completes.
|
||||
func (i *Instrumentation) RecordOperationDuration(ctx context.Context) Timer {
|
||||
return Timer{
|
||||
ctx: ctx,
|
||||
start: time.Now(),
|
||||
inst: i,
|
||||
hist: i.operationDuration,
|
||||
}
|
||||
}
|
||||
|
||||
// RecordCollectionDuration starts the timing of a collection operation.
|
||||
//
|
||||
// It returns a [Timer] that tracks the collection duration. The [Timer.Stop]
|
||||
// method must be called when the collection completes.
|
||||
func (i *Instrumentation) RecordCollectionDuration(ctx context.Context) Timer {
|
||||
return Timer{
|
||||
ctx: ctx,
|
||||
start: time.Now(),
|
||||
inst: i,
|
||||
hist: i.collectionDuration,
|
||||
}
|
||||
}
|
||||
|
||||
// Timer tracks the duration of an operation.
|
||||
type Timer struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
|
||||
inst *Instrumentation
|
||||
hist metric.Float64Histogram
|
||||
}
|
||||
|
||||
// Stop ends the timing operation and records the elapsed duration.
|
||||
//
|
||||
// If err is non-nil, an appropriate error type attribute will be included.
|
||||
func (t Timer) Stop(err error) {
|
||||
recordOpt := get[metric.RecordOption](recordOptPool)
|
||||
defer put(recordOptPool, recordOpt)
|
||||
*recordOpt = append(*recordOpt, t.inst.setOpt)
|
||||
|
||||
if err != nil {
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, t.inst.attrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
set := attribute.NewSet(*attrs...)
|
||||
*recordOpt = append((*recordOpt)[:0], metric.WithAttributeSet(set))
|
||||
}
|
||||
|
||||
t.hist.Record(t.ctx, time.Since(t.start).Seconds(), *recordOpt...)
|
||||
}
|
||||
|
||||
// ExportMetrics starts the observation of a metric export operation.
|
||||
//
|
||||
// It returns an [ExportOp] that tracks the export operation. The
|
||||
// [ExportOp.End] method must be called when the export completes.
|
||||
func (i *Instrumentation) ExportMetrics(ctx context.Context, n int64) ExportOp {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, i.setOpt)
|
||||
|
||||
i.inflightMetric.Add(ctx, n, *addOpt...)
|
||||
|
||||
return ExportOp{ctx: ctx, nMetrics: n, inst: i}
|
||||
}
|
||||
|
||||
// ExportOp tracks a metric export operation.
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
nMetrics int64
|
||||
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End ends the observation of a metric export operation.
|
||||
//
|
||||
// The success parameter is the number of metrics that were successfully
|
||||
// exported. If a non-nil error is provided, the number of failed metrics will
|
||||
// be recorded with the error type attribute.
|
||||
func (e ExportOp) End(success int64, err error) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, e.inst.setOpt)
|
||||
|
||||
e.inst.inflightMetric.Add(e.ctx, -e.nMetrics, *addOpt...)
|
||||
e.inst.exportedMetric.Add(e.ctx, success, *addOpt...)
|
||||
|
||||
if err != nil {
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, e.inst.attrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
set := attribute.NewSet(*attrs...)
|
||||
|
||||
*addOpt = append((*addOpt)[:0], metric.WithAttributeSet(set))
|
||||
e.inst.exportedMetric.Add(e.ctx, e.nMetrics-success, *addOpt...)
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal utilities for the OpenTelemetry prometheus exporter.
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/prometheus/internal"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry prometheus
|
||||
// exporter in use.
|
||||
const Version = "0.61.0"
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Experimental Features
|
||||
|
||||
The `prometheus` exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the `prometheus` exporter prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
|
||||
|
||||
These features may change in backwards incompatible ways as feedback is applied.
|
||||
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
|
||||
|
||||
## Features
|
||||
|
||||
- [Observability](#observability)
|
||||
|
||||
### Observability
|
||||
|
||||
The `prometheus` exporter can be configured to provide observability about itself using OpenTelemetry metrics.
|
||||
|
||||
To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`.
|
||||
|
||||
When enabled, the SDK will create the following metrics using the global `MeterProvider`:
|
||||
|
||||
- `otel.sdk.exporter.metric_data_point.inflight`
|
||||
- `otel.sdk.exporter.metric_data_point.exported`
|
||||
- `otel.sdk.metric_reader.collection.duration`
|
||||
- `otel.sdk.exporter.operation.duration`
|
||||
|
||||
Please see the [Semantic conventions for OpenTelemetry SDK metrics] documentation for more details on these metrics.
|
||||
|
||||
[Semantic conventions for OpenTelemetry SDK metrics]: https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/otel/sdk-metrics.md
|
||||
|
||||
## Compatibility and Stability
|
||||
|
||||
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../VERSIONING.md).
|
||||
These features may be removed or modified in successive version releases, including patch versions.
|
||||
|
||||
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.
|
||||
There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version.
|
||||
If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package x // import "go.opentelemetry.io/otel/exporters/prometheus/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that determines if exporter
|
||||
// observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/prometheus].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/prometheus/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Feature is an experimental feature control flag. It provides a uniform way
|
||||
// to interact with these feature flags and parse their values.
|
||||
type Feature[T any] struct {
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// Lookup returns the user configured value for the feature and true if the
|
||||
// user has enabled the feature. Otherwise, if the feature is not enabled, a
|
||||
// zero-value and false are returned.
|
||||
func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value
|
||||
//
|
||||
// > The SDK MUST interpret an empty value of an environment variable the
|
||||
// > same way as when the variable is unset.
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
func (f Feature[T]) Enabled() bool {
|
||||
_, ok := f.Lookup()
|
||||
return ok
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ type Encoder interface {
|
||||
}
|
||||
|
||||
// encoderHolder is the concrete type used to wrap an Encoder so it can be
|
||||
// used as a atomic.Value type.
|
||||
// used as an atomic.Value type.
|
||||
type encoderHolder struct {
|
||||
encoder Encoder
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal functionality for the stdouttrace
|
||||
// package.
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||
|
||||
//go:generate gotmpl --body=../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter\" }" --out=counter/counter.go
|
||||
//go:generate gotmpl --body=../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/stdout/stdouttrace\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
Generated
Vendored
+214
@@ -0,0 +1,214 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides experimental observability instrumentation
|
||||
// for the stdout trace exporter.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ComponentType uniquely identifies the OpenTelemetry Exporter component
|
||||
// being instrumented.
|
||||
//
|
||||
// The STDOUT trace exporter is not a standardized OTel component type, so
|
||||
// it uses the Go package prefixed type name to ensure uniqueness and
|
||||
// identity.
|
||||
ComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
|
||||
|
||||
// ScopeName is the unique name of the meter used for instrumentation.
|
||||
ScopeName = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||
|
||||
// SchemaURL is the schema URL of the metrics produced by this
|
||||
// instrumentation.
|
||||
SchemaURL = semconv.SchemaURL
|
||||
|
||||
// Version is the current version of this instrumentation.
|
||||
//
|
||||
// This matches the version of the exporter.
|
||||
Version = internal.Version
|
||||
)
|
||||
|
||||
var (
|
||||
measureAttrsPool = &sync.Pool{
|
||||
New: func() any {
|
||||
// "component.name" + "component.type" + "error.type"
|
||||
const n = 1 + 1 + 1
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
// Return a pointer to a slice instead of a slice itself
|
||||
// to avoid allocations on every call.
|
||||
return &s
|
||||
},
|
||||
}
|
||||
|
||||
addOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.AddOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
|
||||
recordOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.RecordOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||
|
||||
func put[T any](p *sync.Pool, s *[]T) {
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func ComponentName(id int64) string {
|
||||
return fmt.Sprintf("%s/%d", ComponentType, id)
|
||||
}
|
||||
|
||||
// Instrumentation is experimental instrumentation for the exporter.
|
||||
type Instrumentation struct {
|
||||
inflightSpans metric.Int64UpDownCounter
|
||||
exportedSpans metric.Int64Counter
|
||||
opDuration metric.Float64Histogram
|
||||
|
||||
attrs []attribute.KeyValue
|
||||
setOpt metric.MeasurementOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for a STDOUT trace exporter with
|
||||
// the provided ID using the global MeterProvider.
|
||||
//
|
||||
// If the experimental observability is disabled, nil is returned.
|
||||
func NewInstrumentation(id int64) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
i := &Instrumentation{
|
||||
attrs: []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(ComponentType),
|
||||
},
|
||||
}
|
||||
|
||||
s := attribute.NewSet(i.attrs...)
|
||||
i.setOpt = metric.WithAttributeSet(s)
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
inflightSpans, e := otelconv.NewSDKExporterSpanInflight(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.inflightSpans = inflightSpans.Inst()
|
||||
|
||||
exportedSpans, e := otelconv.NewSDKExporterSpanExported(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.exportedSpans = exportedSpans.Inst()
|
||||
|
||||
opDuration, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.opDuration = opDuration.Inst()
|
||||
|
||||
return i, err
|
||||
}
|
||||
|
||||
// ExportSpans instruments the ExportSpans method of the exporter. It returns a
|
||||
// function that needs to be deferred so it is called when the method returns.
|
||||
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
|
||||
start := time.Now()
|
||||
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, i.setOpt)
|
||||
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
|
||||
|
||||
return ExportOp{
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
nSpans: int64(nSpans),
|
||||
inst: i,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp is an in-progress ExportSpans operation.
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
nSpans int64
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End ends the ExportSpans operation, recording its success and duration.
|
||||
//
|
||||
// The success parameter indicates how many spans were successfully exported.
|
||||
// The err parameter indicates whether the operation failed. If err is not nil,
|
||||
// the number of failed spans (nSpans - success) is also recorded.
|
||||
func (e ExportOp) End(success int64, err error) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, e.inst.setOpt)
|
||||
|
||||
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
|
||||
|
||||
// Record the success and duration of the operation.
|
||||
//
|
||||
// Do not exclude 0 values, as they are valid and indicate no spans
|
||||
// were exported which is meaningful for certain aggregations.
|
||||
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
|
||||
|
||||
mOpt := e.inst.setOpt
|
||||
if err != nil {
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, e.inst.attrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using
|
||||
// WithAttributes instead of WithAttributeSet.
|
||||
set := attribute.NewSet(*attrs...)
|
||||
mOpt = metric.WithAttributeSet(set)
|
||||
|
||||
// Reset addOpt with new attribute set.
|
||||
*addOpt = append((*addOpt)[:0], mOpt)
|
||||
|
||||
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
|
||||
}
|
||||
|
||||
recordOpt := get[metric.RecordOption](recordOptPool)
|
||||
defer put(recordOptPool, recordOpt)
|
||||
*recordOpt = append(*recordOpt, mOpt)
|
||||
e.inst.opDuration.Record(e.ctx, time.Since(e.start).Seconds(), *recordOpt...)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry stdouttrace
|
||||
// exporter in use.
|
||||
const Version = "1.39.0"
|
||||
Generated
Vendored
+4
-4
@@ -8,13 +8,13 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for
|
||||
|
||||
## Features
|
||||
|
||||
- [Self-Observability](#self-observability)
|
||||
- [Observability](#observability)
|
||||
|
||||
### Self-Observability
|
||||
### Observability
|
||||
|
||||
The `stdouttrace` exporter provides a self-observability feature that allows you to monitor the SDK itself.
|
||||
The `stdouttrace` exporter can be configured to provide observability about itself using OpenTelemetry metrics.
|
||||
|
||||
To opt-in, set the environment variable `OTEL_GO_X_SELF_OBSERVABILITY` to `true`.
|
||||
To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`.
|
||||
|
||||
When enabled, the SDK will create the following metrics using the global `MeterProvider`:
|
||||
|
||||
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/stdout/stdouttrace].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that determines if exporter
|
||||
// observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY", "SELF_OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
+18
-23
@@ -1,3 +1,6 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -6,40 +9,30 @@ package x // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/inter
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SelfObservability is an experimental feature flag that determines if SDK
|
||||
// self-observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_SELF_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var SelfObservability = newFeature("SELF_OBSERVABILITY", func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
|
||||
// Feature is an experimental feature control flag. It provides a uniform way
|
||||
// to interact with these feature flags and parse their values.
|
||||
type Feature[T any] struct {
|
||||
key string
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
key: envKeyRoot + suffix,
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the environment variable key that needs to be set to enable the
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Key() string { return f.key }
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// Lookup returns the user configured value for the feature and true if the
|
||||
// user has enabled the feature. Otherwise, if the feature is not enabled, a
|
||||
@@ -49,11 +42,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
//
|
||||
// > The SDK MUST interpret an empty value of an environment variable the
|
||||
// > same way as when the variable is unset.
|
||||
vRaw := os.Getenv(f.key)
|
||||
if vRaw == "" {
|
||||
return v, ok
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return f.parse(vRaw)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
|
||||
+7
-141
@@ -11,23 +11,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
)
|
||||
|
||||
// otelComponentType is a name identifying the type of the OpenTelemetry
|
||||
// component. It is not a standardized OTel component type, so it uses the
|
||||
// Go package prefixed type name to ensure uniqueness and identity.
|
||||
const otelComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
|
||||
|
||||
var zeroTime time.Time
|
||||
|
||||
var _ trace.SpanExporter = &Exporter{}
|
||||
@@ -46,39 +35,8 @@ func New(options ...Option) (*Exporter, error) {
|
||||
timestamps: cfg.Timestamps,
|
||||
}
|
||||
|
||||
if !x.SelfObservability.Enabled() {
|
||||
return exporter, nil
|
||||
}
|
||||
|
||||
exporter.selfObservabilityEnabled = true
|
||||
exporter.selfObservabilityAttrs = []attribute.KeyValue{
|
||||
semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, counter.NextExporterID())),
|
||||
semconv.OTelComponentTypeKey.String(otelComponentType),
|
||||
}
|
||||
s := attribute.NewSet(exporter.selfObservabilityAttrs...)
|
||||
exporter.selfObservabilitySetOpt = metric.WithAttributeSet(s)
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter(
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
|
||||
metric.WithInstrumentationVersion(sdk.Version()),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
var err, e error
|
||||
if exporter.spanInflightMetric, e = otelconv.NewSDKExporterSpanInflight(m); e != nil {
|
||||
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
if exporter.spanExportedMetric, e = otelconv.NewSDKExporterSpanExported(m); e != nil {
|
||||
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
if exporter.operationDurationMetric, e = otelconv.NewSDKExporterOperationDuration(m); e != nil {
|
||||
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
|
||||
var err error
|
||||
exporter.inst, err = observ.NewInstrumentation(counter.NextExporterID())
|
||||
return exporter, err
|
||||
}
|
||||
|
||||
@@ -91,107 +49,15 @@ type Exporter struct {
|
||||
stoppedMu sync.RWMutex
|
||||
stopped bool
|
||||
|
||||
selfObservabilityEnabled bool
|
||||
selfObservabilityAttrs []attribute.KeyValue // selfObservability common attributes
|
||||
selfObservabilitySetOpt metric.MeasurementOption
|
||||
spanInflightMetric otelconv.SDKExporterSpanInflight
|
||||
spanExportedMetric otelconv.SDKExporterSpanExported
|
||||
operationDurationMetric otelconv.SDKExporterOperationDuration
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
var (
|
||||
measureAttrsPool = sync.Pool{
|
||||
New: func() any {
|
||||
// "component.name" + "component.type" + "error.type"
|
||||
const n = 1 + 1 + 1
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
// Return a pointer to a slice instead of a slice itself
|
||||
// to avoid allocations on every call.
|
||||
return &s
|
||||
},
|
||||
}
|
||||
|
||||
addOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.AddOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
|
||||
recordOptPool = &sync.Pool{
|
||||
New: func() any {
|
||||
const n = 1 // WithAttributeSet
|
||||
o := make([]metric.RecordOption, 0, n)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// ExportSpans writes spans in json format to stdout.
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
|
||||
var success int64
|
||||
if e.selfObservabilityEnabled {
|
||||
count := int64(len(spans))
|
||||
|
||||
addOpt := addOptPool.Get().(*[]metric.AddOption)
|
||||
defer func() {
|
||||
*addOpt = (*addOpt)[:0]
|
||||
addOptPool.Put(addOpt)
|
||||
}()
|
||||
|
||||
*addOpt = append(*addOpt, e.selfObservabilitySetOpt)
|
||||
|
||||
e.spanInflightMetric.Inst().Add(ctx, count, *addOpt...)
|
||||
defer func(starting time.Time) {
|
||||
e.spanInflightMetric.Inst().Add(ctx, -count, *addOpt...)
|
||||
|
||||
// Record the success and duration of the operation.
|
||||
//
|
||||
// Do not exclude 0 values, as they are valid and indicate no spans
|
||||
// were exported which is meaningful for certain aggregations.
|
||||
e.spanExportedMetric.Inst().Add(ctx, success, *addOpt...)
|
||||
|
||||
mOpt := e.selfObservabilitySetOpt
|
||||
if err != nil {
|
||||
// additional attributes for self-observability,
|
||||
// only spanExportedMetric and operationDurationMetric are supported.
|
||||
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
*attrs = (*attrs)[:0] // reset the slice for reuse
|
||||
measureAttrsPool.Put(attrs)
|
||||
}()
|
||||
*attrs = append(*attrs, e.selfObservabilityAttrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using
|
||||
// WithAttributes instead of WithAttributeSet.
|
||||
set := attribute.NewSet(*attrs...)
|
||||
mOpt = metric.WithAttributeSet(set)
|
||||
|
||||
// Reset addOpt with new attribute set.
|
||||
*addOpt = append((*addOpt)[:0], mOpt)
|
||||
|
||||
e.spanExportedMetric.Inst().Add(
|
||||
ctx,
|
||||
count-success,
|
||||
*addOpt...,
|
||||
)
|
||||
}
|
||||
|
||||
recordOpt := recordOptPool.Get().(*[]metric.RecordOption)
|
||||
defer func() {
|
||||
*recordOpt = (*recordOpt)[:0]
|
||||
recordOptPool.Put(recordOpt)
|
||||
}()
|
||||
|
||||
*recordOpt = append(*recordOpt, mOpt)
|
||||
e.operationDurationMetric.Inst().Record(
|
||||
ctx,
|
||||
time.Since(starting).Seconds(),
|
||||
*recordOpt...,
|
||||
)
|
||||
}(time.Now())
|
||||
if e.inst != nil {
|
||||
op := e.inst.ExportSpans(ctx, len(spans))
|
||||
defer func() { op.End(success, err) }()
|
||||
}
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
|
||||
+3
-2
@@ -23,8 +23,9 @@ const _Kind_name = "EmptyBoolFloat64Int64StringBytesSliceMap"
|
||||
var _Kind_index = [...]uint8{0, 5, 9, 16, 21, 27, 32, 37, 40}
|
||||
|
||||
func (i Kind) String() string {
|
||||
if i < 0 || i >= Kind(len(_Kind_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_Kind_index)-1 {
|
||||
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
|
||||
return _Kind_name[_Kind_index[idx]:_Kind_index[idx+1]]
|
||||
}
|
||||
|
||||
+42
-2
@@ -5,6 +5,7 @@ package log // import "go.opentelemetry.io/otel/log"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/log/embedded"
|
||||
@@ -114,13 +115,52 @@ func WithInstrumentationVersion(version string) LoggerOption {
|
||||
})
|
||||
}
|
||||
|
||||
// mergeSets returns the union of keys between a and b. Any duplicate keys will
|
||||
// use the value associated with b.
|
||||
func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
// NewMergeIterator uses the first value for any duplicates.
|
||||
iter := attribute.NewMergeIterator(&b, &a)
|
||||
merged := make([]attribute.KeyValue, 0, a.Len()+b.Len())
|
||||
for iter.Next() {
|
||||
merged = append(merged, iter.Attribute())
|
||||
}
|
||||
return attribute.NewSet(merged...)
|
||||
}
|
||||
|
||||
// WithInstrumentationAttributes returns a [LoggerOption] that sets the
|
||||
// instrumentation attributes of a [Logger].
|
||||
//
|
||||
// The passed attributes will be de-duplicated.
|
||||
// This is equivalent to calling WithInstrumentationAttributeSet with an
|
||||
// [attribute.Set] created from a clone of the passed attributes.
|
||||
// [WithInstrumentationAttributeSet] is recommended for more control.
|
||||
//
|
||||
// If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet]
|
||||
// options are passed, the attributes will be merged together in the order
|
||||
// they are passed. Attributes with duplicate keys will use the last value passed.
|
||||
func WithInstrumentationAttributes(attr ...attribute.KeyValue) LoggerOption {
|
||||
set := attribute.NewSet(slices.Clone(attr)...)
|
||||
return WithInstrumentationAttributeSet(set)
|
||||
}
|
||||
|
||||
// WithInstrumentationAttributeSet returns a [LoggerOption] that adds the
|
||||
// instrumentation attributes of a [Logger].
|
||||
//
|
||||
// If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet]
|
||||
// options are passed, the attributes will be merged together in the order
|
||||
// they are passed. Attributes with duplicate keys will use the last value passed.
|
||||
func WithInstrumentationAttributeSet(set attribute.Set) LoggerOption {
|
||||
if set.Len() == 0 {
|
||||
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
return loggerOptionFunc(func(config LoggerConfig) LoggerConfig {
|
||||
config.attrs = attribute.NewSet(attr...)
|
||||
if config.attrs.Len() == 0 {
|
||||
config.attrs = set
|
||||
} else {
|
||||
config.attrs = mergeSets(config.attrs, set)
|
||||
}
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
||||
+3
-2
@@ -40,8 +40,9 @@ const _Severity_name = "UNDEFINEDTRACETRACE2TRACE3TRACE4DEBUGDEBUG2DEBUG3DEBUG4I
|
||||
var _Severity_index = [...]uint8{0, 9, 14, 20, 26, 32, 37, 43, 49, 55, 59, 64, 69, 74, 78, 83, 88, 93, 98, 104, 110, 116, 121, 127, 133, 139}
|
||||
|
||||
func (i Severity) String() string {
|
||||
if i < 0 || i >= Severity(len(_Severity_index)-1) {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_Severity_index)-1 {
|
||||
return "Severity(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Severity_name[_Severity_index[i]:_Severity_index[i+1]]
|
||||
return _Severity_name[_Severity_index[idx]:_Severity_index[idx+1]]
|
||||
}
|
||||
|
||||
+9
-4
@@ -184,6 +184,11 @@ func (b *BatchProcessor) poll(interval time.Duration) (done chan struct{}) {
|
||||
return done
|
||||
}
|
||||
|
||||
// Enabled returns true, indicating this Processor will process all records.
|
||||
func (*BatchProcessor) Enabled(context.Context, EnabledParameters) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// OnEmit batches provided log record.
|
||||
func (b *BatchProcessor) OnEmit(_ context.Context, r *Record) error {
|
||||
if b.stopped.Load() || b.q == nil {
|
||||
@@ -375,25 +380,25 @@ func newBatchConfig(options []BatchProcessorOption) batchConfig {
|
||||
c.maxQSize = c.maxQSize.Resolve(
|
||||
clearLessThanOne[int](),
|
||||
getenv[int](envarMaxQSize),
|
||||
clearLessThanOne[int](),
|
||||
clearLessThanOne[int](), // nolint:gocritic // the function argument is duplicated on purpose
|
||||
fallback[int](dfltMaxQSize),
|
||||
)
|
||||
c.expInterval = c.expInterval.Resolve(
|
||||
clearLessThanOne[time.Duration](),
|
||||
getenv[time.Duration](envarExpInterval),
|
||||
clearLessThanOne[time.Duration](),
|
||||
clearLessThanOne[time.Duration](), // nolint:gocritic // the function argument is duplicated on purpose
|
||||
fallback[time.Duration](dfltExpInterval),
|
||||
)
|
||||
c.expTimeout = c.expTimeout.Resolve(
|
||||
clearLessThanOne[time.Duration](),
|
||||
getenv[time.Duration](envarExpTimeout),
|
||||
clearLessThanOne[time.Duration](),
|
||||
clearLessThanOne[time.Duration](), // nolint:gocritic // the function argument is duplicated on purpose
|
||||
fallback[time.Duration](dfltExpTimeout),
|
||||
)
|
||||
c.expMaxBatchSize = c.expMaxBatchSize.Resolve(
|
||||
clearLessThanOne[int](),
|
||||
getenv[int](envarExpMaxBatchSize),
|
||||
clearLessThanOne[int](),
|
||||
clearLessThanOne[int](), // nolint:gocritic // the function argument is duplicated on purpose
|
||||
clampMax[int](c.maxQSize.Value),
|
||||
fallback[int](dfltExpMaxBatchSize),
|
||||
)
|
||||
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package log // import "go.opentelemetry.io/otel/sdk/log"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
)
|
||||
|
||||
// FilterProcessor is a [Processor] that knows, and can identify, what [Record]
|
||||
// it will process or drop when it is passed to [Processor.OnEmit].
|
||||
//
|
||||
// This is useful for users that want to know if a [log.Record]
|
||||
// will be processed or dropped before they perform complex operations to
|
||||
// construct the [log.Record].
|
||||
//
|
||||
// The SDK's Logger.Enabled returns false
|
||||
// if all the registered Processors implement FilterProcessor
|
||||
// and they all return false.
|
||||
//
|
||||
// Processor implementations that choose to support this by satisfying this
|
||||
// interface are expected to re-evaluate the [Record] passed to [Processor.OnEmit],
|
||||
// it is not expected that the caller to OnEmit will use the functionality
|
||||
// from this interface prior to calling OnEmit.
|
||||
//
|
||||
// See the [go.opentelemetry.io/contrib/processors/minsev] for an example use-case.
|
||||
// It provides a Processor used to filter out [Record]
|
||||
// that has a [log.Severity] below a threshold.
|
||||
type FilterProcessor interface {
|
||||
// Enabled reports whether the Processor will process for the given context
|
||||
// and param.
|
||||
//
|
||||
// The passed param is likely to be a partial record information being
|
||||
// provided (e.g a param with only the Severity set).
|
||||
// If a Processor needs more information than is provided, it
|
||||
// is said to be in an indeterminate state (see below).
|
||||
//
|
||||
// The returned value will be true when the Processor will process for the
|
||||
// provided context and param, and will be false if the Logger will not
|
||||
// emit. The returned value may be true or false in an indeterminate state.
|
||||
// An implementation should default to returning true for an indeterminate
|
||||
// state, but may return false if valid reasons in particular circumstances
|
||||
// exist (e.g. performance, correctness).
|
||||
//
|
||||
// The param should not be held by the implementation. A copy should be
|
||||
// made if the param needs to be held after the call returns.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(ctx context.Context, param EnabledParameters) bool
|
||||
}
|
||||
|
||||
// EnabledParameters represents payload for [FilterProcessor]'s Enabled method.
|
||||
type EnabledParameters struct {
|
||||
InstrumentationScope instrumentation.Scope
|
||||
Severity log.Severity
|
||||
EventName string
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package log // import "go.opentelemetry.io/otel/sdk/log"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/sdk/log/internal/x"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
)
|
||||
|
||||
// newRecordCounterIncr returns a function that increments the log record
|
||||
// counter metric. If observability is disabled, it returns nil.
|
||||
func newRecordCounterIncr() (func(context.Context), error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
m := otel.GetMeterProvider().Meter(
|
||||
"go.opentelemetry.io/otel/sdk/log",
|
||||
metric.WithInstrumentationVersion(sdk.Version()),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
created, err := otelconv.NewSDKLogCreated(m)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to create log created metric: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
inst := created.Inst()
|
||||
f := func(ctx context.Context) { inst.Add(ctx, 1) }
|
||||
return f, nil
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides observability instrumentation for the OTel log SDK
|
||||
// package.
|
||||
package observ // import "go.opentelemetry.io/otel/sdk/log/internal/observ"
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/sdk/log/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/sdk/log/internal/x"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeName is the name of the instrumentation scope.
|
||||
ScopeName = "go.opentelemetry.io/otel/sdk/log/internal/observ"
|
||||
)
|
||||
|
||||
var measureAttrsPool = sync.Pool{
|
||||
New: func() any {
|
||||
// "component.name" + "component.type" + "error.type"
|
||||
const n = 1 + 1 + 1
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
// Return a pointer to a slice instead of a slice itself
|
||||
// to avoid allocations on every call.
|
||||
return &s
|
||||
},
|
||||
}
|
||||
|
||||
// simpleProcessorN is a global 0-based count of the number of simple processor created.
|
||||
var simpleProcessorN atomic.Int64
|
||||
|
||||
// NextSimpleProcessorID returns the next unique ID for a simpleProcessor.
|
||||
func NextSimpleProcessorID() int64 {
|
||||
const inc = 1
|
||||
return simpleProcessorN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// SetSimpleProcessorID sets the exporter ID counter to v and returns the previous
|
||||
// value.
|
||||
//
|
||||
// This function is useful for testing purposes, allowing you to reset the
|
||||
// counter. It should not be used in production code.
|
||||
func SetSimpleProcessorID(v int64) int64 {
|
||||
return simpleProcessorN.Swap(v)
|
||||
}
|
||||
|
||||
// GetSLPComponentName returns the component name attribute for a
|
||||
// SimpleLogProcessor with the given ID.
|
||||
func GetSLPComponentName(id int64) attribute.KeyValue {
|
||||
t := otelconv.ComponentTypeSimpleLogProcessor
|
||||
name := fmt.Sprintf("%s/%d", t, id)
|
||||
return semconv.OTelComponentName(name)
|
||||
}
|
||||
|
||||
// SLP is the instrumentation for an OTel SDK SimpleLogProcessor.
|
||||
type SLP struct {
|
||||
processed metric.Int64Counter
|
||||
attrs []attribute.KeyValue
|
||||
addOpts []metric.AddOption
|
||||
}
|
||||
|
||||
// NewSLP returns instrumentation for an OTel SDK SimpleLogProcessor with the
|
||||
// provided ID.
|
||||
//
|
||||
// If the experimental observability is disabled, nil is returned.
|
||||
func NewSLP(id int64) (*SLP, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
meter := otel.GetMeterProvider()
|
||||
mt := meter.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(sdk.Version()),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
p, err := otelconv.NewSDKProcessorLogProcessed(mt)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to create a processed log metric: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := GetSLPComponentName(id)
|
||||
componentType := p.AttrComponentType(otelconv.ComponentTypeSimpleLogProcessor)
|
||||
attrs := []attribute.KeyValue{name, componentType}
|
||||
addOpts := []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(attrs...))}
|
||||
|
||||
return &SLP{
|
||||
processed: p.Inst(),
|
||||
attrs: attrs,
|
||||
addOpts: addOpts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LogProcessed records that a log has been processed by the SimpleLogProcessor.
|
||||
// If err is non-nil, it records the processing error as an attribute.
|
||||
func (slp *SLP) LogProcessed(ctx context.Context, err error) {
|
||||
slp.processed.Add(ctx, 1, slp.addOption(err)...)
|
||||
}
|
||||
|
||||
func (slp *SLP) addOption(err error) []metric.AddOption {
|
||||
if err == nil {
|
||||
return slp.addOpts
|
||||
}
|
||||
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
*attrs = (*attrs)[:0] // reset the slice
|
||||
measureAttrsPool.Put(attrs)
|
||||
}()
|
||||
|
||||
*attrs = append(*attrs, slp.attrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using
|
||||
// WithAttributes instead of WithAttributeSet.
|
||||
return []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(*attrs...))}
|
||||
}
|
||||
+4
-4
@@ -8,13 +8,13 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for
|
||||
|
||||
## Features
|
||||
|
||||
- [Self-Observability](#self-observability)
|
||||
- [Observability](#observability)
|
||||
|
||||
### Self-Observability
|
||||
### Observability
|
||||
|
||||
The Logs SDK provides a self-observability feature that allows you to monitor the SDK itself.
|
||||
The Logs SDK can be configured to provide observability about itself using OpenTelemetry metrics.
|
||||
|
||||
To opt-in, set the environment variable `OTEL_GO_X_SELF_OBSERVABILITY` to `true`.
|
||||
To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`.
|
||||
|
||||
When enabled, the SDK will create the following metrics using the global `MeterProvider`:
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/sdk/log].
|
||||
package x // import "go.opentelemetry.io/otel/sdk/log/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that determines if SDK
|
||||
// observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY", "SELF_OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
+18
-23
@@ -1,3 +1,6 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -6,40 +9,30 @@ package x // import "go.opentelemetry.io/otel/sdk/log/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SelfObservability is an experimental feature flag that determines if SDK
|
||||
// self-observability metrics are enabled.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_SELF_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var SelfObservability = newFeature("SELF_OBSERVABILITY", func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
|
||||
// Feature is an experimental feature control flag. It provides a uniform way
|
||||
// to interact with these feature flags and parse their values.
|
||||
type Feature[T any] struct {
|
||||
key string
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
key: envKeyRoot + suffix,
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the environment variable key that needs to be set to enable the
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Key() string { return f.key }
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// Lookup returns the user configured value for the feature and true if the
|
||||
// user has enabled the feature. Otherwise, if the feature is not enabled, a
|
||||
@@ -49,11 +42,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
//
|
||||
// > The SDK MUST interpret an empty value of an environment variable the
|
||||
// > same way as when the variable is unset.
|
||||
vRaw := os.Getenv(f.key)
|
||||
if vRaw == "" {
|
||||
return v, ok
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return f.parse(vRaw)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
|
||||
+12
-35
@@ -5,18 +5,12 @@ package log // import "go.opentelemetry.io/otel/sdk/log"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/log/embedded"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
"go.opentelemetry.io/otel/sdk/log/internal/x"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
@@ -31,8 +25,9 @@ type logger struct {
|
||||
provider *LoggerProvider
|
||||
instrumentationScope instrumentation.Scope
|
||||
|
||||
selfObservabilityEnabled bool
|
||||
logCreatedMetric otelconv.SDKLogCreated
|
||||
// recCntIncr increments the count of log records created. It will be nil
|
||||
// if observability is disabled.
|
||||
recCntIncr func(context.Context)
|
||||
}
|
||||
|
||||
func newLogger(p *LoggerProvider, scope instrumentation.Scope) *logger {
|
||||
@@ -40,18 +35,10 @@ func newLogger(p *LoggerProvider, scope instrumentation.Scope) *logger {
|
||||
provider: p,
|
||||
instrumentationScope: scope,
|
||||
}
|
||||
if !x.SelfObservability.Enabled() {
|
||||
return l
|
||||
}
|
||||
l.selfObservabilityEnabled = true
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter("go.opentelemetry.io/otel/sdk/log",
|
||||
metric.WithInstrumentationVersion(sdk.Version()),
|
||||
metric.WithSchemaURL(semconv.SchemaURL))
|
||||
|
||||
var err error
|
||||
if l.logCreatedMetric, err = otelconv.NewSDKLogCreated(m); err != nil {
|
||||
err = fmt.Errorf("failed to create log created metric: %w", err)
|
||||
l.recCntIncr, err = newRecordCounterIncr()
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
return l
|
||||
@@ -67,9 +54,9 @@ func (l *logger) Emit(ctx context.Context, r log.Record) {
|
||||
}
|
||||
|
||||
// Enabled returns true if at least one Processor held by the LoggerProvider
|
||||
// that created the logger will process param for the provided context and param.
|
||||
// that created the logger will process for the provided context and param.
|
||||
//
|
||||
// If it is not possible to definitively determine the param will be
|
||||
// If it is not possible to definitively determine the record will be
|
||||
// processed, true will be returned by default. A value of false will only be
|
||||
// returned if it can be positively verified that no Processor will process.
|
||||
func (l *logger) Enabled(ctx context.Context, param log.EnabledParameters) bool {
|
||||
@@ -79,23 +66,13 @@ func (l *logger) Enabled(ctx context.Context, param log.EnabledParameters) bool
|
||||
EventName: param.EventName,
|
||||
}
|
||||
|
||||
// If there are more Processors than FilterProcessors,
|
||||
// which means not all Processors are FilterProcessors,
|
||||
// we cannot be sure that all Processors will drop the record.
|
||||
// Therefore, return true.
|
||||
//
|
||||
// If all Processors are FilterProcessors, check if any is enabled.
|
||||
return len(l.provider.processors) > len(l.provider.fltrProcessors) || anyEnabled(ctx, p, l.provider.fltrProcessors)
|
||||
}
|
||||
|
||||
func anyEnabled(ctx context.Context, param EnabledParameters, fltrs []FilterProcessor) bool {
|
||||
for _, f := range fltrs {
|
||||
if f.Enabled(ctx, param) {
|
||||
for _, processor := range l.provider.processors {
|
||||
if processor.Enabled(ctx, p) {
|
||||
// At least one Processor will process the Record.
|
||||
return true
|
||||
}
|
||||
}
|
||||
// No Processor will process the record
|
||||
// No Processor will process the record.
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -119,8 +96,8 @@ func (l *logger) newRecord(ctx context.Context, r log.Record) Record {
|
||||
attributeCountLimit: l.provider.attributeCountLimit,
|
||||
allowDupKeys: l.provider.allowDupKeys,
|
||||
}
|
||||
if l.selfObservabilityEnabled {
|
||||
l.logCreatedMetric.Add(ctx, 1)
|
||||
if l.recCntIncr != nil {
|
||||
l.recCntIncr(ctx)
|
||||
}
|
||||
|
||||
// This ensures we deduplicate key-value collections in the log body
|
||||
|
||||
+41
-3
@@ -5,6 +5,9 @@ package log // import "go.opentelemetry.io/otel/sdk/log"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
)
|
||||
|
||||
// Processor handles the processing of log records.
|
||||
@@ -12,9 +15,36 @@ import (
|
||||
// Any of the Processor's methods may be called concurrently with itself
|
||||
// or with other methods. It is the responsibility of the Processor to manage
|
||||
// this concurrency.
|
||||
//
|
||||
// See [FilterProcessor] for information about how a Processor can support filtering.
|
||||
type Processor interface {
|
||||
// Enabled reports whether the Processor will process for the given context
|
||||
// and param.
|
||||
//
|
||||
// The passed param is likely to be partial record information being
|
||||
// provided (e.g. a param with only the Severity set).
|
||||
// If a Processor needs more information than is provided, it
|
||||
// is said to be in an indeterminate state (see below).
|
||||
//
|
||||
// The returned value will be true when the Processor will process for the
|
||||
// provided context and param, and will be false if the Processor will not
|
||||
// process. The returned value may be true or false in an indeterminate state.
|
||||
// An implementation should default to returning true for an indeterminate
|
||||
// state, but may return false if valid reasons in particular circumstances
|
||||
// exist (e.g. performance, correctness).
|
||||
//
|
||||
// The param should not be held by the implementation. A copy should be
|
||||
// made if the param needs to be held after the call returns.
|
||||
//
|
||||
// Processor implementations are expected to re-evaluate the [Record] passed
|
||||
// to OnEmit. It is not expected that the caller to OnEmit will
|
||||
// use the result from Enabled prior to calling OnEmit.
|
||||
//
|
||||
// The SDK's Logger.Enabled returns false if all the registered processors
|
||||
// return false. Otherwise, it returns true.
|
||||
//
|
||||
// Implementations of this method need to be safe for a user to call
|
||||
// concurrently.
|
||||
Enabled(ctx context.Context, param EnabledParameters) bool
|
||||
|
||||
// OnEmit is called when a Record is emitted.
|
||||
//
|
||||
// OnEmit will be called independent of Enabled. Implementations need to
|
||||
@@ -32,7 +62,8 @@ type Processor interface {
|
||||
// they were registered using WithProcessor.
|
||||
// Implementations may synchronously modify the record so that the changes
|
||||
// are visible in the next registered processor.
|
||||
// Notice that Record is not concurrent safe. Therefore, asynchronous
|
||||
//
|
||||
// Note that Record is not concurrent safe. Therefore, asynchronous
|
||||
// processing may cause race conditions. Use Record.Clone
|
||||
// to create a copy that shares no state with the original.
|
||||
OnEmit(ctx context.Context, record *Record) error
|
||||
@@ -54,3 +85,10 @@ type Processor interface {
|
||||
// appropriate error should be returned in these situations.
|
||||
ForceFlush(ctx context.Context) error
|
||||
}
|
||||
|
||||
// EnabledParameters represents payload for [Processor]'s Enabled method.
|
||||
type EnabledParameters struct {
|
||||
InstrumentationScope instrumentation.Scope
|
||||
Severity log.Severity
|
||||
EventName string
|
||||
}
|
||||
|
||||
+5
-13
@@ -27,12 +27,11 @@ const (
|
||||
)
|
||||
|
||||
type providerConfig struct {
|
||||
resource *resource.Resource
|
||||
processors []Processor
|
||||
fltrProcessors []FilterProcessor
|
||||
attrCntLim setting[int]
|
||||
attrValLenLim setting[int]
|
||||
allowDupKeys setting[bool]
|
||||
resource *resource.Resource
|
||||
processors []Processor
|
||||
attrCntLim setting[int]
|
||||
attrValLenLim setting[int]
|
||||
allowDupKeys setting[bool]
|
||||
}
|
||||
|
||||
func newProviderConfig(opts []LoggerProviderOption) providerConfig {
|
||||
@@ -65,7 +64,6 @@ type LoggerProvider struct {
|
||||
|
||||
resource *resource.Resource
|
||||
processors []Processor
|
||||
fltrProcessors []FilterProcessor
|
||||
attributeCountLimit int
|
||||
attributeValueLengthLimit int
|
||||
allowDupKeys bool
|
||||
@@ -92,7 +90,6 @@ func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider {
|
||||
return &LoggerProvider{
|
||||
resource: cfg.resource,
|
||||
processors: cfg.processors,
|
||||
fltrProcessors: cfg.fltrProcessors,
|
||||
attributeCountLimit: cfg.attrCntLim.Value,
|
||||
attributeValueLengthLimit: cfg.attrValLenLim.Value,
|
||||
allowDupKeys: cfg.allowDupKeys.Value,
|
||||
@@ -208,14 +205,9 @@ func WithResource(res *resource.Resource) LoggerProviderOption {
|
||||
//
|
||||
// For production, use [NewBatchProcessor] to batch log records before they are exported.
|
||||
// For testing and debugging, use [NewSimpleProcessor] to synchronously export log records.
|
||||
//
|
||||
// See [FilterProcessor] for information about how a Processor can support filtering.
|
||||
func WithProcessor(processor Processor) LoggerProviderOption {
|
||||
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
|
||||
cfg.processors = append(cfg.processors, processor)
|
||||
if f, ok := processor.(FilterProcessor); ok {
|
||||
cfg.fltrProcessors = append(cfg.fltrProcessors, f)
|
||||
}
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
+251
-46
@@ -27,7 +27,25 @@ var logAttrDropped = sync.OnceFunc(func() {
|
||||
global.Warn("limit reached: dropping log Record attributes")
|
||||
})
|
||||
|
||||
// indexPool is a pool of index maps used for de-duplication.
|
||||
// uniquePool is a pool of unique attributes used for attributes de-duplication.
|
||||
var uniquePool = sync.Pool{
|
||||
New: func() any { return new([]log.KeyValue) },
|
||||
}
|
||||
|
||||
func getUnique() *[]log.KeyValue {
|
||||
return uniquePool.Get().(*[]log.KeyValue)
|
||||
}
|
||||
|
||||
func putUnique(v *[]log.KeyValue) {
|
||||
// To reduce peak allocation.
|
||||
const maxUniqueSize = 1028
|
||||
if cap(*v) <= maxUniqueSize {
|
||||
*v = (*v)[:0]
|
||||
uniquePool.Put(v)
|
||||
}
|
||||
}
|
||||
|
||||
// indexPool is a pool of index maps used for attributes de-duplication.
|
||||
var indexPool = sync.Pool{
|
||||
New: func() any { return make(map[string]int) },
|
||||
}
|
||||
@@ -41,6 +59,20 @@ func putIndex(index map[string]int) {
|
||||
indexPool.Put(index)
|
||||
}
|
||||
|
||||
// seenPool is a pool of seen keys used for maps de-duplication.
|
||||
var seenPool = sync.Pool{
|
||||
New: func() any { return make(map[string]struct{}) },
|
||||
}
|
||||
|
||||
func getSeen() map[string]struct{} {
|
||||
return seenPool.Get().(map[string]struct{})
|
||||
}
|
||||
|
||||
func putSeen(seen map[string]struct{}) {
|
||||
clear(seen)
|
||||
seenPool.Put(seen)
|
||||
}
|
||||
|
||||
// Record is a log record emitted by the Logger.
|
||||
// A log record with non-empty event name is interpreted as an event record.
|
||||
//
|
||||
@@ -212,34 +244,36 @@ func (r *Record) AddAttributes(attrs ...log.KeyValue) {
|
||||
}
|
||||
|
||||
if !r.allowDupKeys {
|
||||
// Use a slice from the pool to avoid modifying the original.
|
||||
// Note, do not iterate attrs twice by just calling dedup(attrs) here.
|
||||
unique := getUnique()
|
||||
defer putUnique(unique)
|
||||
|
||||
// Used to find duplicates between attrs and existing attributes in r.
|
||||
rIndex := r.attrIndex()
|
||||
defer putIndex(rIndex)
|
||||
|
||||
// Unique attrs that need to be added to r. This uses the same underlying
|
||||
// array as attrs.
|
||||
//
|
||||
// Note, do not iterate attrs twice by just calling dedup(attrs) here.
|
||||
unique := attrs[:0]
|
||||
// Used to find duplicates within attrs itself. The index value is the
|
||||
// index of the element in unique.
|
||||
// Used to find duplicates within attrs itself.
|
||||
// The index value is the index of the element in unique.
|
||||
uIndex := getIndex()
|
||||
defer putIndex(uIndex)
|
||||
|
||||
dropped := 0
|
||||
|
||||
// Deduplicate attrs within the scope of all existing attributes.
|
||||
for _, a := range attrs {
|
||||
// Last-value-wins for any duplicates in attrs.
|
||||
idx, found := uIndex[a.Key]
|
||||
if found {
|
||||
r.addDropped(1)
|
||||
unique[idx] = a
|
||||
dropped++
|
||||
(*unique)[idx] = a
|
||||
continue
|
||||
}
|
||||
|
||||
idx, found = rIndex[a.Key]
|
||||
if found {
|
||||
// New attrs overwrite any existing with the same key.
|
||||
r.addDropped(1)
|
||||
dropped++
|
||||
if idx < 0 {
|
||||
r.front[-(idx + 1)] = a
|
||||
} else {
|
||||
@@ -247,11 +281,16 @@ func (r *Record) AddAttributes(attrs ...log.KeyValue) {
|
||||
}
|
||||
} else {
|
||||
// Unique attribute.
|
||||
unique = append(unique, a)
|
||||
uIndex[a.Key] = len(unique) - 1
|
||||
(*unique) = append(*unique, a)
|
||||
uIndex[a.Key] = len(*unique) - 1
|
||||
}
|
||||
}
|
||||
attrs = unique
|
||||
|
||||
if dropped > 0 {
|
||||
attrs = make([]log.KeyValue, len(*unique))
|
||||
copy(attrs, *unique)
|
||||
r.addDropped(dropped)
|
||||
}
|
||||
}
|
||||
|
||||
if r.attributeCountLimit > 0 && n+len(attrs) > r.attributeCountLimit {
|
||||
@@ -294,15 +333,17 @@ func (r *Record) addAttrs(attrs []log.KeyValue) {
|
||||
var i int
|
||||
for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
|
||||
a := attrs[i]
|
||||
r.front[r.nFront] = r.applyAttrLimits(a)
|
||||
r.front[r.nFront] = r.applyAttrLimitsAndDedup(a)
|
||||
r.nFront++
|
||||
}
|
||||
|
||||
for j, a := range attrs[i:] {
|
||||
attrs[i+j] = r.applyAttrLimits(a)
|
||||
}
|
||||
// Make a copy to avoid modifying the original.
|
||||
j := len(r.back)
|
||||
r.back = slices.Grow(r.back, len(attrs[i:]))
|
||||
r.back = append(r.back, attrs[i:]...)
|
||||
for i, a := range r.back[j:] {
|
||||
r.back[i+j] = r.applyAttrLimitsAndDedup(a)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAttributes sets (and overrides) attributes to the log record.
|
||||
@@ -321,13 +362,13 @@ func (r *Record) SetAttributes(attrs ...log.KeyValue) {
|
||||
var i int
|
||||
for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
|
||||
a := attrs[i]
|
||||
r.front[r.nFront] = r.applyAttrLimits(a)
|
||||
r.front[r.nFront] = r.applyAttrLimitsAndDedup(a)
|
||||
r.nFront++
|
||||
}
|
||||
|
||||
r.back = slices.Clone(attrs[i:])
|
||||
for i, a := range r.back {
|
||||
r.back[i] = r.applyAttrLimits(a)
|
||||
r.back[i] = r.applyAttrLimitsAndDedup(a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,20 +383,31 @@ func head(kvs []log.KeyValue, n int) (out []log.KeyValue, dropped int) {
|
||||
|
||||
// dedup deduplicates kvs front-to-back with the last value saved.
|
||||
func dedup(kvs []log.KeyValue) (unique []log.KeyValue, dropped int) {
|
||||
if len(kvs) <= 1 {
|
||||
return kvs, 0 // No deduplication needed.
|
||||
}
|
||||
|
||||
index := getIndex()
|
||||
defer putIndex(index)
|
||||
|
||||
unique = kvs[:0] // Use the same underlying array as kvs.
|
||||
u := getUnique()
|
||||
defer putUnique(u)
|
||||
for _, a := range kvs {
|
||||
idx, found := index[a.Key]
|
||||
if found {
|
||||
dropped++
|
||||
unique[idx] = a
|
||||
(*u)[idx] = a
|
||||
} else {
|
||||
unique = append(unique, a)
|
||||
index[a.Key] = len(unique) - 1
|
||||
*u = append(*u, a)
|
||||
index[a.Key] = len(*u) - 1
|
||||
}
|
||||
}
|
||||
|
||||
if dropped == 0 {
|
||||
return kvs, 0
|
||||
}
|
||||
|
||||
unique = make([]log.KeyValue, len(*u))
|
||||
copy(unique, *u)
|
||||
return unique, dropped
|
||||
}
|
||||
|
||||
@@ -421,59 +473,212 @@ func (r *Record) Clone() Record {
|
||||
return res
|
||||
}
|
||||
|
||||
func (r *Record) applyAttrLimits(attr log.KeyValue) log.KeyValue {
|
||||
attr.Value = r.applyValueLimits(attr.Value)
|
||||
func (r *Record) applyAttrLimitsAndDedup(attr log.KeyValue) log.KeyValue {
|
||||
attr.Value = r.applyValueLimitsAndDedup(attr.Value)
|
||||
return attr
|
||||
}
|
||||
|
||||
func (r *Record) applyValueLimits(val log.Value) log.Value {
|
||||
func (r *Record) applyValueLimitsAndDedup(val log.Value) log.Value {
|
||||
switch val.Kind() {
|
||||
case log.KindString:
|
||||
s := val.AsString()
|
||||
if len(s) > r.attributeValueLengthLimit {
|
||||
if r.attributeValueLengthLimit >= 0 && len(s) > r.attributeValueLengthLimit {
|
||||
val = log.StringValue(truncate(r.attributeValueLengthLimit, s))
|
||||
}
|
||||
case log.KindSlice:
|
||||
sl := val.AsSlice()
|
||||
for i := range sl {
|
||||
sl[i] = r.applyValueLimits(sl[i])
|
||||
|
||||
// First check if any limits need to be applied.
|
||||
needsChange := false
|
||||
for _, v := range sl {
|
||||
if r.needsValueLimitsOrDedup(v) {
|
||||
needsChange = true
|
||||
break
|
||||
}
|
||||
}
|
||||
val = log.SliceValue(sl...)
|
||||
|
||||
if needsChange {
|
||||
// Create a new slice to avoid modifying the original.
|
||||
newSl := make([]log.Value, len(sl))
|
||||
for i, item := range sl {
|
||||
newSl[i] = r.applyValueLimitsAndDedup(item)
|
||||
}
|
||||
val = log.SliceValue(newSl...)
|
||||
}
|
||||
|
||||
case log.KindMap:
|
||||
kvs := val.AsMap()
|
||||
var newKvs []log.KeyValue
|
||||
var dropped int
|
||||
|
||||
if !r.allowDupKeys {
|
||||
// Deduplicate then truncate. Do not do at the same time to avoid
|
||||
// wasted truncation operations.
|
||||
var dropped int
|
||||
kvs, dropped = dedup(kvs)
|
||||
// Deduplicate then truncate.
|
||||
// Do not do at the same time to avoid wasted truncation operations.
|
||||
newKvs, dropped = dedup(kvs)
|
||||
r.addDropped(dropped)
|
||||
} else {
|
||||
newKvs = kvs
|
||||
}
|
||||
for i := range kvs {
|
||||
kvs[i] = r.applyAttrLimits(kvs[i])
|
||||
|
||||
// Check if any attribute limits need to be applied.
|
||||
needsChange := false
|
||||
if dropped > 0 {
|
||||
needsChange = true // Already changed by dedup.
|
||||
} else {
|
||||
for _, kv := range newKvs {
|
||||
if r.needsValueLimitsOrDedup(kv.Value) {
|
||||
needsChange = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if needsChange {
|
||||
// Only create new slice if changes are needed.
|
||||
if dropped == 0 {
|
||||
// Make a copy to avoid modifying the original.
|
||||
newKvs = make([]log.KeyValue, len(kvs))
|
||||
copy(newKvs, kvs)
|
||||
}
|
||||
|
||||
for i := range newKvs {
|
||||
newKvs[i] = r.applyAttrLimitsAndDedup(newKvs[i])
|
||||
}
|
||||
val = log.MapValue(newKvs...)
|
||||
}
|
||||
val = log.MapValue(kvs...)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// needsValueLimitsOrDedup checks if a value would be modified by applyValueLimitsAndDedup.
|
||||
func (r *Record) needsValueLimitsOrDedup(val log.Value) bool {
|
||||
switch val.Kind() {
|
||||
case log.KindString:
|
||||
return r.attributeValueLengthLimit >= 0 && len(val.AsString()) > r.attributeValueLengthLimit
|
||||
case log.KindSlice:
|
||||
for _, v := range val.AsSlice() {
|
||||
if r.needsValueLimitsOrDedup(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case log.KindMap:
|
||||
kvs := val.AsMap()
|
||||
if !r.allowDupKeys && len(kvs) > 1 {
|
||||
// Check for duplicates.
|
||||
hasDuplicates := func() bool {
|
||||
seen := getSeen()
|
||||
defer putSeen(seen)
|
||||
for _, kv := range kvs {
|
||||
if _, ok := seen[kv.Key]; ok {
|
||||
return true
|
||||
}
|
||||
seen[kv.Key] = struct{}{}
|
||||
}
|
||||
return false
|
||||
}()
|
||||
if hasDuplicates {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, kv := range kvs {
|
||||
if r.needsValueLimitsOrDedup(kv.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Record) dedupeBodyCollections(val log.Value) log.Value {
|
||||
switch val.Kind() {
|
||||
case log.KindSlice:
|
||||
sl := val.AsSlice()
|
||||
for i := range sl {
|
||||
sl[i] = r.dedupeBodyCollections(sl[i])
|
||||
|
||||
// Check if any nested values need deduplication.
|
||||
needsChange := false
|
||||
for _, item := range sl {
|
||||
if r.needsBodyDedup(item) {
|
||||
needsChange = true
|
||||
break
|
||||
}
|
||||
}
|
||||
val = log.SliceValue(sl...)
|
||||
|
||||
if needsChange {
|
||||
// Create a new slice to avoid modifying the original.
|
||||
newSl := make([]log.Value, len(sl))
|
||||
for i, item := range sl {
|
||||
newSl[i] = r.dedupeBodyCollections(item)
|
||||
}
|
||||
val = log.SliceValue(newSl...)
|
||||
}
|
||||
|
||||
case log.KindMap:
|
||||
kvs, _ := dedup(val.AsMap())
|
||||
for i := range kvs {
|
||||
kvs[i].Value = r.dedupeBodyCollections(kvs[i].Value)
|
||||
kvs := val.AsMap()
|
||||
newKvs, dropped := dedup(kvs)
|
||||
|
||||
// Check if any nested values need deduplication.
|
||||
needsValueChange := false
|
||||
for _, kv := range newKvs {
|
||||
if r.needsBodyDedup(kv.Value) {
|
||||
needsValueChange = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dropped > 0 || needsValueChange {
|
||||
// Only create new value if changes are needed.
|
||||
if dropped == 0 {
|
||||
// Make a copy to avoid modifying the original.
|
||||
newKvs = make([]log.KeyValue, len(kvs))
|
||||
copy(newKvs, kvs)
|
||||
}
|
||||
|
||||
for i := range newKvs {
|
||||
newKvs[i].Value = r.dedupeBodyCollections(newKvs[i].Value)
|
||||
}
|
||||
val = log.MapValue(newKvs...)
|
||||
}
|
||||
val = log.MapValue(kvs...)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// needsBodyDedup checks if a value would be modified by dedupeBodyCollections.
|
||||
func (r *Record) needsBodyDedup(val log.Value) bool {
|
||||
switch val.Kind() {
|
||||
case log.KindSlice:
|
||||
for _, item := range val.AsSlice() {
|
||||
if r.needsBodyDedup(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case log.KindMap:
|
||||
kvs := val.AsMap()
|
||||
if len(kvs) > 1 {
|
||||
// Check for duplicates.
|
||||
hasDuplicates := func() bool {
|
||||
seen := getSeen()
|
||||
defer putSeen(seen)
|
||||
for _, kv := range kvs {
|
||||
if _, ok := seen[kv.Key]; ok {
|
||||
return true
|
||||
}
|
||||
seen[kv.Key] = struct{}{}
|
||||
}
|
||||
return false
|
||||
}()
|
||||
if hasDuplicates {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, kv := range kvs {
|
||||
if r.needsBodyDedup(kv.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// truncate returns a truncated version of s such that it contains less than
|
||||
// the limit number of characters. Truncation is applied by returning the limit
|
||||
// number of valid characters contained in s.
|
||||
|
||||
+25
-4
@@ -6,6 +6,9 @@ package log // import "go.opentelemetry.io/otel/sdk/log"
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/sdk/log/internal/observ"
|
||||
)
|
||||
|
||||
// Compile-time check SimpleProcessor implements Processor.
|
||||
@@ -17,8 +20,8 @@ var _ Processor = (*SimpleProcessor)(nil)
|
||||
type SimpleProcessor struct {
|
||||
mu sync.Mutex
|
||||
exporter Exporter
|
||||
|
||||
noCmp [0]func() //nolint: unused // This is indeed used.
|
||||
inst *observ.SLP
|
||||
noCmp [0]func() //nolint: unused // This is indeed used.
|
||||
}
|
||||
|
||||
// NewSimpleProcessor is a simple Processor adapter.
|
||||
@@ -30,7 +33,15 @@ type SimpleProcessor struct {
|
||||
// [NewBatchProcessor] instead. However, there may be exceptions where certain
|
||||
// [Exporter] implementations perform better with this Processor.
|
||||
func NewSimpleProcessor(exporter Exporter, _ ...SimpleProcessorOption) *SimpleProcessor {
|
||||
return &SimpleProcessor{exporter: exporter}
|
||||
slp := &SimpleProcessor{
|
||||
exporter: exporter,
|
||||
}
|
||||
var err error
|
||||
slp.inst, err = observ.NewSLP(observ.NextSimpleProcessorID())
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
return slp
|
||||
}
|
||||
|
||||
var simpleProcRecordsPool = sync.Pool{
|
||||
@@ -40,8 +51,13 @@ var simpleProcRecordsPool = sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
// Enabled returns true, indicating this Processor will process all records.
|
||||
func (*SimpleProcessor) Enabled(context.Context, EnabledParameters) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// OnEmit batches provided log record.
|
||||
func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) error {
|
||||
func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) (err error) {
|
||||
if s.exporter == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -55,6 +71,11 @@ func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) error {
|
||||
simpleProcRecordsPool.Put(records)
|
||||
}()
|
||||
|
||||
if s.inst != nil {
|
||||
defer func() {
|
||||
s.inst.LogProcessed(ctx, err)
|
||||
}()
|
||||
}
|
||||
return s.exporter.Export(ctx, *records)
|
||||
}
|
||||
|
||||
|
||||
Vendored
+51
-38
@@ -1204,7 +1204,7 @@ github.com/gorilla/websocket
|
||||
# github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2
|
||||
## explicit; go 1.17
|
||||
github.com/grafana/cloudflare-go
|
||||
# github.com/grafana/dskit v0.0.0-20251218155159-03c38747d0d9
|
||||
# github.com/grafana/dskit v0.0.0-20260120145137-7f862deba99d
|
||||
## explicit; go 1.24.0
|
||||
github.com/grafana/dskit/backoff
|
||||
github.com/grafana/dskit/cancellation
|
||||
@@ -1355,8 +1355,8 @@ github.com/influxdata/tdigest
|
||||
## explicit; go 1.25.3
|
||||
github.com/influxdata/telegraf
|
||||
github.com/influxdata/telegraf/plugins/inputs
|
||||
# github.com/jaegertracing/jaeger-idl v0.5.0
|
||||
## explicit; go 1.22.7
|
||||
# github.com/jaegertracing/jaeger-idl v0.6.0
|
||||
## explicit; go 1.23.6
|
||||
github.com/jaegertracing/jaeger-idl/model/v1
|
||||
github.com/jaegertracing/jaeger-idl/proto-gen/api_v2
|
||||
# github.com/jcmturner/aescts/v2 v2.0.0
|
||||
@@ -1790,7 +1790,7 @@ github.com/prometheus/common/version
|
||||
# github.com/prometheus/common/sigv4 v0.1.0
|
||||
## explicit; go 1.15
|
||||
github.com/prometheus/common/sigv4
|
||||
# github.com/prometheus/exporter-toolkit v0.15.0
|
||||
# github.com/prometheus/exporter-toolkit v0.15.1
|
||||
## explicit; go 1.24.0
|
||||
github.com/prometheus/exporter-toolkit/web
|
||||
# github.com/prometheus/otlptranslator v1.0.0
|
||||
@@ -2109,8 +2109,8 @@ go.etcd.io/bbolt
|
||||
go.etcd.io/bbolt/errors
|
||||
go.etcd.io/bbolt/internal/common
|
||||
go.etcd.io/bbolt/internal/freelist
|
||||
# go.etcd.io/etcd/api/v3 v3.6.6
|
||||
## explicit; go 1.24
|
||||
# go.etcd.io/etcd/api/v3 v3.6.7
|
||||
## explicit; go 1.24.0
|
||||
go.etcd.io/etcd/api/v3/authpb
|
||||
go.etcd.io/etcd/api/v3/etcdserverpb
|
||||
go.etcd.io/etcd/api/v3/membershippb
|
||||
@@ -2118,8 +2118,8 @@ go.etcd.io/etcd/api/v3/mvccpb
|
||||
go.etcd.io/etcd/api/v3/v3rpc/rpctypes
|
||||
go.etcd.io/etcd/api/v3/version
|
||||
go.etcd.io/etcd/api/v3/versionpb
|
||||
# go.etcd.io/etcd/client/pkg/v3 v3.6.6
|
||||
## explicit; go 1.24
|
||||
# go.etcd.io/etcd/client/pkg/v3 v3.6.7
|
||||
## explicit; go 1.24.0
|
||||
go.etcd.io/etcd/client/pkg/v3/fileutil
|
||||
go.etcd.io/etcd/client/pkg/v3/logutil
|
||||
go.etcd.io/etcd/client/pkg/v3/systemd
|
||||
@@ -2127,8 +2127,8 @@ go.etcd.io/etcd/client/pkg/v3/tlsutil
|
||||
go.etcd.io/etcd/client/pkg/v3/transport
|
||||
go.etcd.io/etcd/client/pkg/v3/types
|
||||
go.etcd.io/etcd/client/pkg/v3/verify
|
||||
# go.etcd.io/etcd/client/v3 v3.6.6
|
||||
## explicit; go 1.24
|
||||
# go.etcd.io/etcd/client/v3 v3.6.7
|
||||
## explicit; go 1.24.0
|
||||
go.etcd.io/etcd/client/v3
|
||||
go.etcd.io/etcd/client/v3/credentials
|
||||
go.etcd.io/etcd/client/v3/internal/endpoint
|
||||
@@ -2199,14 +2199,14 @@ go.opentelemetry.io/collector/processor/internal
|
||||
# go.opentelemetry.io/collector/semconv v0.128.0
|
||||
## explicit; go 1.23.0
|
||||
go.opentelemetry.io/collector/semconv/v1.6.1
|
||||
# go.opentelemetry.io/contrib/bridges/prometheus v0.63.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/contrib/bridges/prometheus v0.64.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/contrib/bridges/prometheus
|
||||
# go.opentelemetry.io/contrib/detectors/gcp v1.38.0
|
||||
## explicit; go 1.23.8
|
||||
go.opentelemetry.io/contrib/detectors/gcp
|
||||
# go.opentelemetry.io/contrib/exporters/autoexport v0.63.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/contrib/exporters/autoexport v0.64.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/contrib/exporters/autoexport
|
||||
# go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0
|
||||
## explicit; go 1.24.0
|
||||
@@ -2221,11 +2221,11 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/int
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv
|
||||
# go.opentelemetry.io/contrib/propagators/jaeger v1.38.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/contrib/propagators/jaeger v1.39.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/contrib/propagators/jaeger
|
||||
# go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/contrib/samplers/jaegerremote v0.33.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote
|
||||
go.opentelemetry.io/contrib/samplers/jaegerremote/internal/ratelimiter
|
||||
# go.opentelemetry.io/otel v1.39.0
|
||||
@@ -2253,26 +2253,32 @@ go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/agent
|
||||
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/jaeger
|
||||
go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/zipkincore
|
||||
go.opentelemetry.io/otel/exporters/jaeger/internal/third_party/thrift/lib/go/thrift
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0
|
||||
## explicit; go 1.23.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/observ
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/retry
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/transform
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0
|
||||
## explicit; go 1.23.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/x
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/retry
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig
|
||||
@@ -2303,22 +2309,28 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/observ
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/retry
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/x
|
||||
# go.opentelemetry.io/otel/exporters/prometheus v0.60.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/otel/exporters/prometheus v0.61.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus
|
||||
# go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0
|
||||
## explicit; go 1.23.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus/internal
|
||||
go.opentelemetry.io/otel/exporters/prometheus/internal/counter
|
||||
go.opentelemetry.io/otel/exporters/prometheus/internal/observ
|
||||
go.opentelemetry.io/otel/exporters/prometheus/internal/x
|
||||
# go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog
|
||||
# go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric
|
||||
# go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x
|
||||
# go.opentelemetry.io/otel/log v0.14.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/otel/log v0.15.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/log
|
||||
go.opentelemetry.io/otel/log/embedded
|
||||
go.opentelemetry.io/otel/log/noop
|
||||
@@ -2337,9 +2349,10 @@ go.opentelemetry.io/otel/sdk/trace
|
||||
go.opentelemetry.io/otel/sdk/trace/internal/env
|
||||
go.opentelemetry.io/otel/sdk/trace/internal/observ
|
||||
go.opentelemetry.io/otel/sdk/trace/tracetest
|
||||
# go.opentelemetry.io/otel/sdk/log v0.14.0
|
||||
## explicit; go 1.23.0
|
||||
# go.opentelemetry.io/otel/sdk/log v0.15.0
|
||||
## explicit; go 1.24.0
|
||||
go.opentelemetry.io/otel/sdk/log
|
||||
go.opentelemetry.io/otel/sdk/log/internal/observ
|
||||
go.opentelemetry.io/otel/sdk/log/internal/x
|
||||
# go.opentelemetry.io/otel/sdk/metric v1.39.0
|
||||
## explicit; go 1.24.0
|
||||
|
||||
Reference in New Issue
Block a user