[chore] Bump all otel deps (#3241)

This commit is contained in:
tobi 2024-08-26 18:05:54 +02:00 committed by GitHub
parent 291bb68b47
commit 28d57d1f13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
193 changed files with 13714 additions and 2346 deletions

30
go.mod
View file

@ -65,14 +65,14 @@ require (
github.com/uptrace/bun/extra/bunotel v1.2.1 github.com/uptrace/bun/extra/bunotel v1.2.1
github.com/wagslane/go-password-validator v0.3.0 github.com/wagslane/go-password-validator v0.3.0
github.com/yuin/goldmark v1.7.4 github.com/yuin/goldmark v1.7.4
go.opentelemetry.io/otel v1.26.0 go.opentelemetry.io/otel v1.29.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0
go.opentelemetry.io/otel/exporters/prometheus v0.46.0 go.opentelemetry.io/otel/exporters/prometheus v0.51.0
go.opentelemetry.io/otel/metric v1.26.0 go.opentelemetry.io/otel/metric v1.29.0
go.opentelemetry.io/otel/sdk v1.26.0 go.opentelemetry.io/otel/sdk v1.29.0
go.opentelemetry.io/otel/sdk/metric v1.24.0 go.opentelemetry.io/otel/sdk/metric v1.29.0
go.opentelemetry.io/otel/trace v1.26.0 go.opentelemetry.io/otel/trace v1.29.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
golang.org/x/crypto v0.26.0 golang.org/x/crypto v0.26.0
golang.org/x/image v0.19.0 golang.org/x/image v0.19.0
@ -122,7 +122,7 @@ require (
github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect
@ -148,7 +148,7 @@ require (
github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect github.com/gorilla/sessions v1.2.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect
@ -209,8 +209,8 @@ require (
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
@ -218,9 +218,9 @@ require (
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect golang.org/x/sys v0.24.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect
google.golang.org/grpc v1.63.2 // indirect google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

60
go.sum
View file

@ -212,8 +212,8 @@ github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
@ -344,8 +344,8 @@ github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8L
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw= github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw=
github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30= github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
@ -640,26 +640,26 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc=
go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= go.opentelemetry.io/otel/exporters/prometheus v0.51.0 h1:G7uexXb/K3T+T9fNLCCKncweEtNEBMTO+46hKX5EdKw=
go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= go.opentelemetry.io/otel/exporters/prometheus v0.51.0/go.mod h1:v0mFe5Kk7woIh938mrZBJBmENYquyA0IICrlYm4Y0t4=
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY=
go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ=
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -927,10 +927,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -943,8 +943,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View file

@ -1,6 +1,7 @@
# A minimal logging API for Go # A minimal logging API for Go
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr) [![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-logr/logr)](https://goreportcard.com/report/github.com/go-logr/logr)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-logr/logr/badge)](https://securityscorecards.dev/viewer/?platform=github.com&org=go-logr&repo=logr) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-logr/logr/badge)](https://securityscorecards.dev/viewer/?platform=github.com&org=go-logr&repo=logr)
logr offers an(other) opinion on how Go programs and libraries can do logging logr offers an(other) opinion on how Go programs and libraries can do logging

View file

@ -240,11 +240,10 @@ type Formatter struct {
prefix string prefix string
values []any values []any
valuesStr string valuesStr string
parentValuesStr string
depth int depth int
opts *Options opts *Options
group string // for slog groups groupName string // for slog groups
groupDepth int groups []groupDef
} }
// outputFormat indicates which outputFormat to use. // outputFormat indicates which outputFormat to use.
@ -257,6 +256,13 @@ const (
outputJSON outputJSON
) )
// groupDef represents a saved group. The values may be empty, but we don't
// know if we need to render the group until the final record is rendered.
type groupDef struct {
name string
values string
}
// PseudoStruct is a list of key-value pairs that gets logged as a struct. // PseudoStruct is a list of key-value pairs that gets logged as a struct.
type PseudoStruct []any type PseudoStruct []any
@ -264,76 +270,102 @@ type PseudoStruct []any
func (f Formatter) render(builtins, args []any) string { func (f Formatter) render(builtins, args []any) string {
// Empirically bytes.Buffer is faster than strings.Builder for this. // Empirically bytes.Buffer is faster than strings.Builder for this.
buf := bytes.NewBuffer(make([]byte, 0, 1024)) buf := bytes.NewBuffer(make([]byte, 0, 1024))
if f.outputFormat == outputJSON { if f.outputFormat == outputJSON {
buf.WriteByte('{') // for the whole line buf.WriteByte('{') // for the whole record
} }
// Render builtins
vals := builtins vals := builtins
if hook := f.opts.RenderBuiltinsHook; hook != nil { if hook := f.opts.RenderBuiltinsHook; hook != nil {
vals = hook(f.sanitize(vals)) vals = hook(f.sanitize(vals))
} }
f.flatten(buf, vals, false, false) // keys are ours, no need to escape f.flatten(buf, vals, false) // keys are ours, no need to escape
continuing := len(builtins) > 0 continuing := len(builtins) > 0
if f.parentValuesStr != "" { // Turn the inner-most group into a string
if continuing { argsStr := func() string {
buf.WriteByte(f.comma()) buf := bytes.NewBuffer(make([]byte, 0, 1024))
}
buf.WriteString(f.parentValuesStr)
continuing = true
}
groupDepth := f.groupDepth
if f.group != "" {
if f.valuesStr != "" || len(args) != 0 {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
} else {
// The group was empty
groupDepth--
}
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
continuing = true
}
vals = args vals = args
if hook := f.opts.RenderArgsHook; hook != nil { if hook := f.opts.RenderArgsHook; hook != nil {
vals = hook(f.sanitize(vals)) vals = hook(f.sanitize(vals))
} }
f.flatten(buf, vals, continuing, true) // escape user-provided keys f.flatten(buf, vals, true) // escape user-provided keys
for i := 0; i < groupDepth; i++ { return buf.String()
buf.WriteByte('}') // for the groups }()
// Render the stack of groups from the inside out.
bodyStr := f.renderGroup(f.groupName, f.valuesStr, argsStr)
for i := len(f.groups) - 1; i >= 0; i-- {
grp := &f.groups[i]
if grp.values == "" && bodyStr == "" {
// no contents, so we must elide the whole group
continue
}
bodyStr = f.renderGroup(grp.name, grp.values, bodyStr)
}
if bodyStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(bodyStr)
} }
if f.outputFormat == outputJSON { if f.outputFormat == outputJSON {
buf.WriteByte('}') // for the whole line buf.WriteByte('}') // for the whole record
} }
return buf.String() return buf.String()
} }
// flatten renders a list of key-value pairs into a buffer. If continuing is // renderGroup returns a string representation of the named group with rendered
// true, it assumes that the buffer has previous values and will emit a // values and args. If the name is empty, this will return the values and args,
// separator (which depends on the output format) before the first pair it // joined. If the name is not empty, this will return a single key-value pair,
// writes. If escapeKeys is true, the keys are assumed to have // where the value is a grouping of the values and args. If the values and
// non-JSON-compatible characters in them and must be evaluated for escapes. // args are both empty, this will return an empty string, even if the name was
// specified.
func (f Formatter) renderGroup(name string, values string, args string) string {
buf := bytes.NewBuffer(make([]byte, 0, 1024))
needClosingBrace := false
if name != "" && (values != "" || args != "") {
buf.WriteString(f.quoted(name, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{')
needClosingBrace = true
}
continuing := false
if values != "" {
buf.WriteString(values)
continuing = true
}
if args != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(args)
}
if needClosingBrace {
buf.WriteByte('}')
}
return buf.String()
}
// flatten renders a list of key-value pairs into a buffer. If escapeKeys is
// true, the keys are assumed to have non-JSON-compatible characters in them
// and must be evaluated for escapes.
// //
// This function returns a potentially modified version of kvList, which // This function returns a potentially modified version of kvList, which
// ensures that there is a value for every key (adding a value if needed) and // ensures that there is a value for every key (adding a value if needed) and
// that each key is a string (substituting a key if needed). // that each key is a string (substituting a key if needed).
func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, escapeKeys bool) []any { func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any {
// This logic overlaps with sanitize() but saves one type-cast per key, // This logic overlaps with sanitize() but saves one type-cast per key,
// which can be measurable. // which can be measurable.
if len(kvList)%2 != 0 { if len(kvList)%2 != 0 {
@ -354,7 +386,7 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
} }
v := kvList[i+1] v := kvList[i+1]
if i > 0 || continuing { if i > 0 {
if f.outputFormat == outputJSON { if f.outputFormat == outputJSON {
buf.WriteByte(f.comma()) buf.WriteByte(f.comma())
} else { } else {
@ -766,46 +798,17 @@ func (f Formatter) sanitize(kvList []any) []any {
// startGroup opens a new group scope (basically a sub-struct), which locks all // startGroup opens a new group scope (basically a sub-struct), which locks all
// the current saved values and starts them anew. This is needed to satisfy // the current saved values and starts them anew. This is needed to satisfy
// slog. // slog.
func (f *Formatter) startGroup(group string) { func (f *Formatter) startGroup(name string) {
// Unnamed groups are just inlined. // Unnamed groups are just inlined.
if group == "" { if name == "" {
return return
} }
// Any saved values can no longer be changed. n := len(f.groups)
buf := bytes.NewBuffer(make([]byte, 0, 1024)) f.groups = append(f.groups[:n:n], groupDef{f.groupName, f.valuesStr})
continuing := false
if f.parentValuesStr != "" {
buf.WriteString(f.parentValuesStr)
continuing = true
}
if f.group != "" && f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
}
// NOTE: We don't close the scope here - that's done later, when a log line
// is actually rendered (because we have N scopes to close).
f.parentValuesStr = buf.String()
// Start collecting new values. // Start collecting new values.
f.group = group f.groupName = name
f.groupDepth++
f.valuesStr = "" f.valuesStr = ""
f.values = nil f.values = nil
} }
@ -900,7 +903,7 @@ func (f *Formatter) AddValues(kvList []any) {
// Pre-render values, so we don't have to do it on each Info/Error call. // Pre-render values, so we don't have to do it on each Info/Error call.
buf := bytes.NewBuffer(make([]byte, 0, 1024)) buf := bytes.NewBuffer(make([]byte, 0, 1024))
f.flatten(buf, vals, false, true) // escape user-provided keys f.flatten(buf, vals, true) // escape user-provided keys
f.valuesStr = buf.String() f.valuesStr = buf.String()
} }

View file

@ -73,7 +73,7 @@ go_test(
"@org_golang_google_genproto_googleapis_api//httpbody", "@org_golang_google_genproto_googleapis_api//httpbody",
"@org_golang_google_genproto_googleapis_rpc//errdetails", "@org_golang_google_genproto_googleapis_rpc//errdetails",
"@org_golang_google_genproto_googleapis_rpc//status", "@org_golang_google_genproto_googleapis_rpc//status",
"@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//:grpc",
"@org_golang_google_grpc//codes", "@org_golang_google_grpc//codes",
"@org_golang_google_grpc//health/grpc_health_v1", "@org_golang_google_grpc//health/grpc_health_v1",
"@org_golang_google_grpc//metadata", "@org_golang_google_grpc//metadata",

View file

@ -49,6 +49,7 @@ var malformedHTTPHeaders = map[string]struct{}{
type ( type (
rpcMethodKey struct{} rpcMethodKey struct{}
httpPathPatternKey struct{} httpPathPatternKey struct{}
httpPatternKey struct{}
AnnotateContextOption func(ctx context.Context) context.Context AnnotateContextOption func(ctx context.Context) context.Context
) )
@ -148,6 +149,12 @@ func annotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcM
var pairs []string var pairs []string
for key, vals := range req.Header { for key, vals := range req.Header {
key = textproto.CanonicalMIMEHeaderKey(key) key = textproto.CanonicalMIMEHeaderKey(key)
switch key {
case xForwardedFor, xForwardedHost:
// Handled separately below
continue
}
for _, val := range vals { for _, val := range vals {
// For backwards-compatibility, pass through 'authorization' header with no prefix. // For backwards-compatibility, pass through 'authorization' header with no prefix.
if key == "Authorization" { if key == "Authorization" {
@ -181,18 +188,17 @@ func annotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcM
pairs = append(pairs, strings.ToLower(xForwardedHost), req.Host) pairs = append(pairs, strings.ToLower(xForwardedHost), req.Host)
} }
xff := req.Header.Values(xForwardedFor)
if addr := req.RemoteAddr; addr != "" { if addr := req.RemoteAddr; addr != "" {
if remoteIP, _, err := net.SplitHostPort(addr); err == nil { if remoteIP, _, err := net.SplitHostPort(addr); err == nil {
if fwd := req.Header.Get(xForwardedFor); fwd == "" { xff = append(xff, remoteIP)
pairs = append(pairs, strings.ToLower(xForwardedFor), remoteIP)
} else {
pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP))
} }
} }
if len(xff) > 0 {
pairs = append(pairs, strings.ToLower(xForwardedFor), strings.Join(xff, ", "))
} }
if timeout != 0 { if timeout != 0 {
//nolint:govet // The context outlives this function
ctx, _ = context.WithTimeout(ctx, timeout) ctx, _ = context.WithTimeout(ctx, timeout)
} }
if len(pairs) == 0 { if len(pairs) == 0 {
@ -399,3 +405,13 @@ func HTTPPathPattern(ctx context.Context) (string, bool) {
func withHTTPPathPattern(ctx context.Context, httpPathPattern string) context.Context { func withHTTPPathPattern(ctx context.Context, httpPathPattern string) context.Context {
return context.WithValue(ctx, httpPathPatternKey{}, httpPathPattern) return context.WithValue(ctx, httpPathPatternKey{}, httpPathPattern)
} }
// HTTPPattern returns the HTTP path pattern struct relating to the HTTP handler, if one exists.
func HTTPPattern(ctx context.Context) (Pattern, bool) {
v, ok := ctx.Value(httpPatternKey{}).(Pattern)
return v, ok
}
func withHTTPPattern(ctx context.Context, httpPattern Pattern) context.Context {
return context.WithValue(ctx, httpPatternKey{}, httpPattern)
}

View file

@ -71,7 +71,7 @@ func HTTPStatusFromCode(code codes.Code) int {
case codes.DataLoss: case codes.DataLoss:
return http.StatusInternalServerError return http.StatusInternalServerError
default: default:
grpclog.Infof("Unknown gRPC error code: %v", code) grpclog.Warningf("Unknown gRPC error code: %v", code)
return http.StatusInternalServerError return http.StatusInternalServerError
} }
} }
@ -93,6 +93,7 @@ func HTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.R
func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) { func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
// return Internal when Marshal failed // return Internal when Marshal failed
const fallback = `{"code": 13, "message": "failed to marshal error message"}` const fallback = `{"code": 13, "message": "failed to marshal error message"}`
const fallbackRewriter = `{"code": 13, "message": "failed to rewrite error message"}`
var customStatus *HTTPStatusError var customStatus *HTTPStatusError
if errors.As(err, &customStatus) { if errors.As(err, &customStatus) {
@ -100,31 +101,40 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
} }
s := status.Convert(err) s := status.Convert(err)
pb := s.Proto()
w.Header().Del("Trailer") w.Header().Del("Trailer")
w.Header().Del("Transfer-Encoding") w.Header().Del("Transfer-Encoding")
contentType := marshaler.ContentType(pb) respRw, err := mux.forwardResponseRewriter(ctx, s.Proto())
if err != nil {
grpclog.Errorf("Failed to rewrite error message %q: %v", s, err)
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallbackRewriter); err != nil {
grpclog.Errorf("Failed to write response: %v", err)
}
return
}
contentType := marshaler.ContentType(respRw)
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
if s.Code() == codes.Unauthenticated { if s.Code() == codes.Unauthenticated {
w.Header().Set("WWW-Authenticate", s.Message()) w.Header().Set("WWW-Authenticate", s.Message())
} }
buf, merr := marshaler.Marshal(pb) buf, merr := marshaler.Marshal(respRw)
if merr != nil { if merr != nil {
grpclog.Infof("Failed to marshal error message %q: %v", s, merr) grpclog.Errorf("Failed to marshal error message %q: %v", s, merr)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallback); err != nil { if _, err := io.WriteString(w, fallback); err != nil {
grpclog.Infof("Failed to write response: %v", err) grpclog.Errorf("Failed to write response: %v", err)
} }
return return
} }
md, ok := ServerMetadataFromContext(ctx) md, ok := ServerMetadataFromContext(ctx)
if !ok { if !ok {
grpclog.Infof("Failed to extract ServerMetadata from context") grpclog.Error("Failed to extract ServerMetadata from context")
} }
handleForwardResponseServerMetadata(w, mux, md) handleForwardResponseServerMetadata(w, mux, md)
@ -148,7 +158,7 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
w.WriteHeader(st) w.WriteHeader(st)
if _, err := w.Write(buf); err != nil { if _, err := w.Write(buf); err != nil {
grpclog.Infof("Failed to write response: %v", err) grpclog.Errorf("Failed to write response: %v", err)
} }
if doForwardTrailers { if doForwardTrailers {

View file

@ -3,9 +3,11 @@ package runtime
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/textproto" "net/textproto"
"strconv"
"strings" "strings"
"google.golang.org/genproto/googleapis/api/httpbody" "google.golang.org/genproto/googleapis/api/httpbody"
@ -17,16 +19,10 @@ import (
// ForwardResponseStream forwards the stream from gRPC server to REST client. // ForwardResponseStream forwards the stream from gRPC server to REST client.
func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) { func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
f, ok := w.(http.Flusher) rc := http.NewResponseController(w)
if !ok {
grpclog.Infof("Flush not supported in %T", w)
http.Error(w, "unexpected type of web server", http.StatusInternalServerError)
return
}
md, ok := ServerMetadataFromContext(ctx) md, ok := ServerMetadataFromContext(ctx)
if !ok { if !ok {
grpclog.Infof("Failed to extract ServerMetadata from context") grpclog.Error("Failed to extract ServerMetadata from context")
http.Error(w, "unexpected error", http.StatusInternalServerError) http.Error(w, "unexpected error", http.StatusInternalServerError)
return return
} }
@ -60,20 +56,27 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
return return
} }
respRw, err := mux.forwardResponseRewriter(ctx, resp)
if err != nil {
grpclog.Errorf("Rewrite error: %v", err)
handleForwardResponseStreamError(ctx, wroteHeader, marshaler, w, req, mux, err, delimiter)
return
}
if !wroteHeader { if !wroteHeader {
w.Header().Set("Content-Type", marshaler.ContentType(resp)) w.Header().Set("Content-Type", marshaler.ContentType(respRw))
} }
var buf []byte var buf []byte
httpBody, isHTTPBody := resp.(*httpbody.HttpBody) httpBody, isHTTPBody := respRw.(*httpbody.HttpBody)
switch { switch {
case resp == nil: case respRw == nil:
buf, err = marshaler.Marshal(errorChunk(status.New(codes.Internal, "empty response"))) buf, err = marshaler.Marshal(errorChunk(status.New(codes.Internal, "empty response")))
case isHTTPBody: case isHTTPBody:
buf = httpBody.GetData() buf = httpBody.GetData()
default: default:
result := map[string]interface{}{"result": resp} result := map[string]interface{}{"result": respRw}
if rb, ok := resp.(responseBody); ok { if rb, ok := respRw.(responseBody); ok {
result["result"] = rb.XXX_ResponseBody() result["result"] = rb.XXX_ResponseBody()
} }
@ -81,20 +84,29 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
} }
if err != nil { if err != nil {
grpclog.Infof("Failed to marshal response chunk: %v", err) grpclog.Errorf("Failed to marshal response chunk: %v", err)
handleForwardResponseStreamError(ctx, wroteHeader, marshaler, w, req, mux, err, delimiter) handleForwardResponseStreamError(ctx, wroteHeader, marshaler, w, req, mux, err, delimiter)
return return
} }
if _, err := w.Write(buf); err != nil { if _, err := w.Write(buf); err != nil {
grpclog.Infof("Failed to send response chunk: %v", err) grpclog.Errorf("Failed to send response chunk: %v", err)
return return
} }
wroteHeader = true wroteHeader = true
if _, err := w.Write(delimiter); err != nil { if _, err := w.Write(delimiter); err != nil {
grpclog.Infof("Failed to send delimiter chunk: %v", err) grpclog.Errorf("Failed to send delimiter chunk: %v", err)
return
}
err = rc.Flush()
if err != nil {
if errors.Is(err, http.ErrNotSupported) {
grpclog.Errorf("Flush not supported in %T", w)
http.Error(w, "unexpected type of web server", http.StatusInternalServerError)
return
}
grpclog.Errorf("Failed to flush response to client: %v", err)
return return
} }
f.Flush()
} }
} }
@ -136,7 +148,7 @@ type responseBody interface {
func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) { func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
md, ok := ServerMetadataFromContext(ctx) md, ok := ServerMetadataFromContext(ctx)
if !ok { if !ok {
grpclog.Infof("Failed to extract ServerMetadata from context") grpclog.Error("Failed to extract ServerMetadata from context")
} }
handleForwardResponseServerMetadata(w, mux, md) handleForwardResponseServerMetadata(w, mux, md)
@ -160,21 +172,30 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
HTTPError(ctx, mux, marshaler, w, req, err) HTTPError(ctx, mux, marshaler, w, req, err)
return return
} }
respRw, err := mux.forwardResponseRewriter(ctx, resp)
if err != nil {
grpclog.Errorf("Rewrite error: %v", err)
HTTPError(ctx, mux, marshaler, w, req, err)
return
}
var buf []byte var buf []byte
var err error if rb, ok := respRw.(responseBody); ok {
if rb, ok := resp.(responseBody); ok {
buf, err = marshaler.Marshal(rb.XXX_ResponseBody()) buf, err = marshaler.Marshal(rb.XXX_ResponseBody())
} else { } else {
buf, err = marshaler.Marshal(resp) buf, err = marshaler.Marshal(respRw)
} }
if err != nil { if err != nil {
grpclog.Infof("Marshal error: %v", err) grpclog.Errorf("Marshal error: %v", err)
HTTPError(ctx, mux, marshaler, w, req, err) HTTPError(ctx, mux, marshaler, w, req, err)
return return
} }
if !doForwardTrailers {
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
}
if _, err = w.Write(buf); err != nil { if _, err = w.Write(buf); err != nil {
grpclog.Infof("Failed to write response: %v", err) grpclog.Errorf("Failed to write response: %v", err)
} }
if doForwardTrailers { if doForwardTrailers {
@ -193,8 +214,7 @@ func handleForwardResponseOptions(ctx context.Context, w http.ResponseWriter, re
} }
for _, opt := range opts { for _, opt := range opts {
if err := opt(ctx, w, resp); err != nil { if err := opt(ctx, w, resp); err != nil {
grpclog.Infof("Error handling ForwardResponseOptions: %v", err) return fmt.Errorf("error handling ForwardResponseOptions: %w", err)
return err
} }
} }
return nil return nil
@ -209,15 +229,15 @@ func handleForwardResponseStreamError(ctx context.Context, wroteHeader bool, mar
} }
buf, err := marshaler.Marshal(msg) buf, err := marshaler.Marshal(msg)
if err != nil { if err != nil {
grpclog.Infof("Failed to marshal an error: %v", err) grpclog.Errorf("Failed to marshal an error: %v", err)
return return
} }
if _, err := w.Write(buf); err != nil { if _, err := w.Write(buf); err != nil {
grpclog.Infof("Failed to notify error to client: %v", err) grpclog.Errorf("Failed to notify error to client: %v", err)
return return
} }
if _, err := w.Write(delimiter); err != nil { if _, err := w.Write(delimiter); err != nil {
grpclog.Infof("Failed to send delimiter chunk: %v", err) grpclog.Errorf("Failed to send delimiter chunk: %v", err)
return return
} }
} }

View file

@ -24,6 +24,11 @@ func (j *JSONBuiltin) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v) return json.Marshal(v)
} }
// MarshalIndent is like Marshal but applies Indent to format the output
func (j *JSONBuiltin) MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
return json.MarshalIndent(v, prefix, indent)
}
// Unmarshal unmarshals JSON data into "v". // Unmarshal unmarshals JSON data into "v".
func (j *JSONBuiltin) Unmarshal(data []byte, v interface{}) error { func (j *JSONBuiltin) Unmarshal(data []byte, v interface{}) error {
return json.Unmarshal(data, v) return json.Unmarshal(data, v)

View file

@ -30,10 +30,6 @@ func (*JSONPb) ContentType(_ interface{}) string {
// Marshal marshals "v" into JSON. // Marshal marshals "v" into JSON.
func (j *JSONPb) Marshal(v interface{}) ([]byte, error) { func (j *JSONPb) Marshal(v interface{}) ([]byte, error) {
if _, ok := v.(proto.Message); !ok {
return j.marshalNonProtoField(v)
}
var buf bytes.Buffer var buf bytes.Buffer
if err := j.marshalTo(&buf, v); err != nil { if err := j.marshalTo(&buf, v); err != nil {
return nil, err return nil, err
@ -48,9 +44,17 @@ func (j *JSONPb) marshalTo(w io.Writer, v interface{}) error {
if err != nil { if err != nil {
return err return err
} }
if j.Indent != "" {
b := &bytes.Buffer{}
if err := json.Indent(b, buf, "", j.Indent); err != nil {
return err
}
buf = b.Bytes()
}
_, err = w.Write(buf) _, err = w.Write(buf)
return err return err
} }
b, err := j.MarshalOptions.Marshal(p) b, err := j.MarshalOptions.Marshal(p)
if err != nil { if err != nil {
return err return err
@ -150,9 +154,6 @@ func (j *JSONPb) marshalNonProtoField(v interface{}) ([]byte, error) {
} }
m[fmt.Sprintf("%v", k.Interface())] = (*json.RawMessage)(&buf) m[fmt.Sprintf("%v", k.Interface())] = (*json.RawMessage)(&buf)
} }
if j.Indent != "" {
return json.MarshalIndent(m, "", j.Indent)
}
return json.Marshal(m) return json.Marshal(m)
} }
if enum, ok := rv.Interface().(protoEnum); ok && !j.UseEnumNumbers { if enum, ok := rv.Interface().(protoEnum); ok && !j.UseEnumNumbers {

View file

@ -46,7 +46,7 @@ func MarshalerForRequest(mux *ServeMux, r *http.Request) (inbound Marshaler, out
for _, contentTypeVal := range r.Header[contentTypeHeader] { for _, contentTypeVal := range r.Header[contentTypeHeader] {
contentType, _, err := mime.ParseMediaType(contentTypeVal) contentType, _, err := mime.ParseMediaType(contentTypeVal)
if err != nil { if err != nil {
grpclog.Infof("Failed to parse Content-Type %s: %v", contentTypeVal, err) grpclog.Errorf("Failed to parse Content-Type %s: %v", contentTypeVal, err)
continue continue
} }
if m, ok := mux.marshalers.mimeMap[contentType]; ok { if m, ok := mux.marshalers.mimeMap[contentType]; ok {

View file

@ -48,12 +48,19 @@ var encodedPathSplitter = regexp.MustCompile("(/|%2F)")
// A HandlerFunc handles a specific pair of path pattern and HTTP method. // A HandlerFunc handles a specific pair of path pattern and HTTP method.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string)
// A Middleware handler wraps another HandlerFunc to do some pre- and/or post-processing of the request. This is used as an alternative to gRPC interceptors when using the direct-to-implementation
// registration methods. It is generally recommended to use gRPC client or server interceptors instead
// where possible.
type Middleware func(HandlerFunc) HandlerFunc
// ServeMux is a request multiplexer for grpc-gateway. // ServeMux is a request multiplexer for grpc-gateway.
// It matches http requests to patterns and invokes the corresponding handler. // It matches http requests to patterns and invokes the corresponding handler.
type ServeMux struct { type ServeMux struct {
// handlers maps HTTP method to a list of handlers. // handlers maps HTTP method to a list of handlers.
handlers map[string][]handler handlers map[string][]handler
middlewares []Middleware
forwardResponseOptions []func(context.Context, http.ResponseWriter, proto.Message) error forwardResponseOptions []func(context.Context, http.ResponseWriter, proto.Message) error
forwardResponseRewriter ForwardResponseRewriter
marshalers marshalerRegistry marshalers marshalerRegistry
incomingHeaderMatcher HeaderMatcherFunc incomingHeaderMatcher HeaderMatcherFunc
outgoingHeaderMatcher HeaderMatcherFunc outgoingHeaderMatcher HeaderMatcherFunc
@ -69,6 +76,24 @@ type ServeMux struct {
// ServeMuxOption is an option that can be given to a ServeMux on construction. // ServeMuxOption is an option that can be given to a ServeMux on construction.
type ServeMuxOption func(*ServeMux) type ServeMuxOption func(*ServeMux)
// ForwardResponseRewriter is the signature of a function that is capable of rewriting messages
// before they are forwarded in a unary, stream, or error response.
type ForwardResponseRewriter func(ctx context.Context, response proto.Message) (any, error)
// WithForwardResponseRewriter returns a ServeMuxOption that allows for implementers to insert logic
// that can rewrite the final response before it is forwarded.
//
// The response rewriter function is called during unary message forwarding, stream message
// forwarding and when errors are being forwarded.
//
// NOTE: Using this option will likely make what is generated by `protoc-gen-openapiv2` incorrect.
// Since this option involves making runtime changes to the response shape or type.
func WithForwardResponseRewriter(fwdResponseRewriter ForwardResponseRewriter) ServeMuxOption {
return func(sm *ServeMux) {
sm.forwardResponseRewriter = fwdResponseRewriter
}
}
// WithForwardResponseOption returns a ServeMuxOption representing the forwardResponseOption. // WithForwardResponseOption returns a ServeMuxOption representing the forwardResponseOption.
// //
// forwardResponseOption is an option that will be called on the relevant context.Context, // forwardResponseOption is an option that will be called on the relevant context.Context,
@ -89,6 +114,15 @@ func WithUnescapingMode(mode UnescapingMode) ServeMuxOption {
} }
} }
// WithMiddlewares sets server middleware for all handlers. This is useful as an alternative to gRPC
// interceptors when using the direct-to-implementation registration methods and cannot rely
// on gRPC interceptors. It's recommended to use gRPC interceptors instead if possible.
func WithMiddlewares(middlewares ...Middleware) ServeMuxOption {
return func(serveMux *ServeMux) {
serveMux.middlewares = append(serveMux.middlewares, middlewares...)
}
}
// SetQueryParameterParser sets the query parameter parser, used to populate message from query parameters. // SetQueryParameterParser sets the query parameter parser, used to populate message from query parameters.
// Configuring this will mean the generated OpenAPI output is no longer correct, and it should be // Configuring this will mean the generated OpenAPI output is no longer correct, and it should be
// done with careful consideration. // done with careful consideration.
@ -279,6 +313,7 @@ func NewServeMux(opts ...ServeMuxOption) *ServeMux {
serveMux := &ServeMux{ serveMux := &ServeMux{
handlers: make(map[string][]handler), handlers: make(map[string][]handler),
forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0), forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0),
forwardResponseRewriter: func(ctx context.Context, response proto.Message) (any, error) { return response, nil },
marshalers: makeMarshalerMIMERegistry(), marshalers: makeMarshalerMIMERegistry(),
errorHandler: DefaultHTTPErrorHandler, errorHandler: DefaultHTTPErrorHandler,
streamErrorHandler: DefaultStreamErrorHandler, streamErrorHandler: DefaultStreamErrorHandler,
@ -305,6 +340,9 @@ func NewServeMux(opts ...ServeMuxOption) *ServeMux {
// Handle associates "h" to the pair of HTTP method and path pattern. // Handle associates "h" to the pair of HTTP method and path pattern.
func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) { func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) {
if len(s.middlewares) > 0 {
h = chainMiddlewares(s.middlewares)(h)
}
s.handlers[meth] = append([]handler{{pat: pat, h: h}}, s.handlers[meth]...) s.handlers[meth] = append([]handler{{pat: pat, h: h}}, s.handlers[meth]...)
} }
@ -405,7 +443,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
continue continue
} }
h.h(w, r, pathParams) s.handleHandler(h, w, r, pathParams)
return return
} }
@ -458,7 +496,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.errorHandler(ctx, s, outboundMarshaler, w, r, sterr) s.errorHandler(ctx, s, outboundMarshaler, w, r, sterr)
return return
} }
h.h(w, r, pathParams) s.handleHandler(h, w, r, pathParams)
return return
} }
_, outboundMarshaler := MarshalerForRequest(s, r) _, outboundMarshaler := MarshalerForRequest(s, r)
@ -484,3 +522,16 @@ type handler struct {
pat Pattern pat Pattern
h HandlerFunc h HandlerFunc
} }
func (s *ServeMux) handleHandler(h handler, w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
h.h(w, r.WithContext(withHTTPPattern(r.Context(), h.pat)), pathParams)
}
func chainMiddlewares(mws []Middleware) Middleware {
return func(next HandlerFunc) HandlerFunc {
for i := len(mws); i > 0; i-- {
next = mws[i-1](next)
}
return next
}
}

View file

@ -52,13 +52,13 @@ type Pattern struct {
// It returns an error if the given definition is invalid. // It returns an error if the given definition is invalid.
func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) { func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
if version != 1 { if version != 1 {
grpclog.Infof("unsupported version: %d", version) grpclog.Errorf("unsupported version: %d", version)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
l := len(ops) l := len(ops)
if l%2 != 0 { if l%2 != 0 {
grpclog.Infof("odd number of ops codes: %d", l) grpclog.Errorf("odd number of ops codes: %d", l)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
@ -81,14 +81,14 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
stack++ stack++
case utilities.OpPushM: case utilities.OpPushM:
if pushMSeen { if pushMSeen {
grpclog.Infof("pushM appears twice") grpclog.Error("pushM appears twice")
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
pushMSeen = true pushMSeen = true
stack++ stack++
case utilities.OpLitPush: case utilities.OpLitPush:
if op.operand < 0 || len(pool) <= op.operand { if op.operand < 0 || len(pool) <= op.operand {
grpclog.Infof("negative literal index: %d", op.operand) grpclog.Errorf("negative literal index: %d", op.operand)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
if pushMSeen { if pushMSeen {
@ -97,18 +97,18 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
stack++ stack++
case utilities.OpConcatN: case utilities.OpConcatN:
if op.operand <= 0 { if op.operand <= 0 {
grpclog.Infof("negative concat size: %d", op.operand) grpclog.Errorf("negative concat size: %d", op.operand)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
stack -= op.operand stack -= op.operand
if stack < 0 { if stack < 0 {
grpclog.Info("stack underflow") grpclog.Error("stack underflow")
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
stack++ stack++
case utilities.OpCapture: case utilities.OpCapture:
if op.operand < 0 || len(pool) <= op.operand { if op.operand < 0 || len(pool) <= op.operand {
grpclog.Infof("variable name index out of bound: %d", op.operand) grpclog.Errorf("variable name index out of bound: %d", op.operand)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
v := pool[op.operand] v := pool[op.operand]
@ -116,11 +116,11 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
vars = append(vars, v) vars = append(vars, v)
stack-- stack--
if stack < 0 { if stack < 0 {
grpclog.Infof("stack underflow") grpclog.Error("stack underflow")
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }
default: default:
grpclog.Infof("invalid opcode: %d", op.code) grpclog.Errorf("invalid opcode: %d", op.code)
return Pattern{}, ErrInvalidPattern return Pattern{}, ErrInvalidPattern
} }

View file

@ -5,3 +5,5 @@ collison
consequentially consequentially
ans ans
nam nam
valu
thirdparty

View file

@ -5,6 +5,6 @@ check-filenames =
check-hidden = check-hidden =
ignore-words = .codespellignore ignore-words = .codespellignore
interactive = 1 interactive = 1
skip = .git,go.mod,go.sum,semconv,venv,.tools skip = .git,go.mod,go.sum,go.work,go.work.sum,semconv,venv,.tools
uri-ignore-words-list = * uri-ignore-words-list = *
write = write =

View file

@ -1,3 +0,0 @@
[submodule "opentelemetry-proto"]
path = exporters/otlp/internal/opentelemetry-proto
url = https://github.com/open-telemetry/opentelemetry-proto

View file

@ -9,8 +9,11 @@ linters:
disable-all: true disable-all: true
# Specifically enable linters we want to use. # Specifically enable linters we want to use.
enable: enable:
- asasalint
- bodyclose
- depguard - depguard
- errcheck - errcheck
- errorlint
- godot - godot
- gofumpt - gofumpt
- goimports - goimports
@ -21,8 +24,11 @@ linters:
- misspell - misspell
- revive - revive
- staticcheck - staticcheck
- tenv
- typecheck - typecheck
- unconvert
- unused - unused
- unparam
issues: issues:
# Maximum issues count per one linter. # Maximum issues count per one linter.
@ -124,6 +130,8 @@ linters-settings:
- "**/example/**/*.go" - "**/example/**/*.go"
- "**/trace/*.go" - "**/trace/*.go"
- "**/trace/**/*.go" - "**/trace/**/*.go"
- "**/log/*.go"
- "**/log/**/*.go"
deny: deny:
- pkg: "go.opentelemetry.io/otel/internal$" - pkg: "go.opentelemetry.io/otel/internal$"
desc: Do not use cross-module internal packages. desc: Do not use cross-module internal packages.

View file

@ -8,6 +8,142 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased] ## [Unreleased]
<!-- Released section -->
<!-- Don't change this section unless doing release -->
## [1.29.0/0.51.0/0.5.0] 2024-08-23
This release is the last to support [Go 1.21].
The next release will require at least [Go 1.22].
### Added
- Add MacOS ARM64 platform to the compatibility testing suite. (#5577)
- Add `InstrumentationScope` field to `SpanStub` in `go.opentelemetry.io/otel/sdk/trace/tracetest`, as a replacement for the deprecated `InstrumentationLibrary`. (#5627)
- Make the initial release of `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`.
This new module contains an OTLP exporter that transmits log telemetry using gRPC.
This module is unstable and breaking changes may be introduced.
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. (#5629)
- Add `Walk` function to `TraceState` in `go.opentelemetry.io/otel/trace` to iterate all the key-value pairs. (#5651)
- Bridge the trace state in `go.opentelemetry.io/otel/bridge/opencensus`. (#5651)
- Zero value of `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` no longer panics. (#5665)
- The `FilterProcessor` interface type is added in `go.opentelemetry.io/otel/sdk/log/internal/x`.
This is an optional and experimental interface that log `Processor`s can implement to instruct the `Logger` if a `Record` will be processed or not.
It replaces the existing `Enabled` method that is removed from the `Processor` interface itself.
It does not fall within the scope of the OpenTelemetry Go versioning and stability [policy](./VERSIONING.md) and it may be changed in backwards incompatible ways or removed in feature releases. (#5692)
- Support [Go 1.23]. (#5720)
### Changed
- `NewMemberRaw`, `NewKeyProperty` and `NewKeyValuePropertyRaw` in `go.opentelemetry.io/otel/baggage` allow UTF-8 string in key. (#5132)
- `Processor.OnEmit` in `go.opentelemetry.io/otel/sdk/log` now accepts a pointer to `Record` instead of a value so that the record modifications done in a processor are propagated to subsequent registered processors. (#5636)
- `SimpleProcessor.Enabled` in `go.opentelemetry.io/otel/sdk/log` now returns `false` if the exporter is `nil`. (#5665)
- Update the concurrency requirements of `Exporter` in `go.opentelemetry.io/otel/sdk/log`. (#5666)
- `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` synchronizes `OnEmit` calls. (#5666)
- The `Processor` interface in `go.opentelemetry.io/otel/sdk/log` no longer includes the `Enabled` method.
See the `FilterProcessor` interface type added in `go.opentelemetry.io/otel/sdk/log/internal/x` to continue providing this functionality. (#5692)
- The `SimpleProcessor` type in `go.opentelemetry.io/otel/sdk/log` is no longer comparable. (#5693)
- The `BatchProcessor` type in `go.opentelemetry.io/otel/sdk/log` is no longer comparable. (#5693)
### Fixed
- Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#5584)
- Pass the underlying error rather than a generic retry-able failure in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp` and `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#5541)
- Correct the `Tracer`, `Meter`, and `Logger` names used in `go.opentelemetry.io/otel/example/dice`. (#5612)
- Correct the `Tracer` names used in `go.opentelemetry.io/otel/example/namedtracer`. (#5612)
- Correct the `Tracer` name used in `go.opentelemetry.io/otel/example/opencensus`. (#5612)
- Correct the `Tracer` and `Meter` names used in `go.opentelemetry.io/otel/example/otel-collector`. (#5612)
- Correct the `Tracer` names used in `go.opentelemetry.io/otel/example/passthrough`. (#5612)
- Correct the `Meter` name used in `go.opentelemetry.io/otel/example/prometheus`. (#5612)
- Correct the `Tracer` names used in `go.opentelemetry.io/otel/example/zipkin`. (#5612)
- Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#5641)
- Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#5650)
- Stop percent encoding header environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` (#5705)
- Remove invalid environment variable header keys in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` (#5705)
### Removed
- The `Enabled` method of the `SimpleProcessor` in `go.opentelemetry.io/otel/sdk/log` is removed. (#5692)
- The `Enabled` method of the `BatchProcessor` in `go.opentelemetry.io/otel/sdk/log` is removed. (#5692)
## [1.28.0/0.50.0/0.4.0] 2024-07-02
### Added
- The `IsEmpty` method is added to the `Instrument` type in `go.opentelemetry.io/otel/sdk/metric`.
This method is used to check if an `Instrument` instance is a zero-value. (#5431)
- Store and provide the emitted `context.Context` in `ScopeRecords` of `go.opentelemetry.io/otel/sdk/log/logtest`. (#5468)
- The `go.opentelemetry.io/otel/semconv/v1.26.0` package.
The package contains semantic conventions from the `v1.26.0` version of the OpenTelemetry Semantic Conventions. (#5476)
- The `AssertRecordEqual` method to `go.opentelemetry.io/otel/log/logtest` to allow comparison of two log records in tests. (#5499)
- The `WithHeaders` option to `go.opentelemetry.io/otel/exporters/zipkin` to allow configuring custom http headers while exporting spans. (#5530)
### Changed
- `Tracer.Start` in `go.opentelemetry.io/otel/trace/noop` no longer allocates a span for empty span context. (#5457)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/example/otel-collector`. (#5490)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/example/zipkin`. (#5490)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/exporters/zipkin`. (#5490)
- The exporter no longer exports the deprecated "otel.library.name" or "otel.library.version" attributes.
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/sdk/resource`. (#5490)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/sdk/trace`. (#5490)
- `SimpleProcessor.OnEmit` in `go.opentelemetry.io/otel/sdk/log` no longer allocates a slice which makes it possible to have a zero-allocation log processing using `SimpleProcessor`. (#5493)
- Use non-generic functions in the `Start` method of `"go.opentelemetry.io/otel/sdk/trace".Trace` to reduce memory allocation. (#5497)
- `service.instance.id` is populated for a `Resource` created with `"go.opentelemetry.io/otel/sdk/resource".Default` with a default value when `OTEL_GO_X_RESOURCE` is set. (#5520)
- Improve performance of metric instruments in `go.opentelemetry.io/otel/sdk/metric` by removing unnecessary calls to `time.Now`. (#5545)
### Fixed
- Log a warning to the OpenTelemetry internal logger when a `Record` in `go.opentelemetry.io/otel/sdk/log` drops an attribute due to a limit being reached. (#5376)
- Identify the `Tracer` returned from the global `TracerProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426)
- Identify the `Meter` returned from the global `MeterProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426)
- Log a warning to the OpenTelemetry internal logger when a `Span` in `go.opentelemetry.io/otel/sdk/trace` drops an attribute, event, or link due to a limit being reached. (#5434)
- Document instrument name requirements in `go.opentelemetry.io/otel/metric`. (#5435)
- Prevent random number generation data-race for experimental rand exemplars in `go.opentelemetry.io/otel/sdk/metric`. (#5456)
- Fix counting number of dropped attributes of `Record` in `go.opentelemetry.io/otel/sdk/log`. (#5464)
- Fix panic in baggage creation when a member contains `0x80` char in key or value. (#5494)
- Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`. (#5508)
- Retry trace and span ID generation if it generated an invalid one in `go.opentelemetry.io/otel/sdk/trace`. (#5514)
- Fix stale timestamps reported by the last-value aggregation. (#5517)
- Indicate the `Exporter` in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp` must be created by the `New` method. (#5521)
- Improved performance in all `{Bool,Int64,Float64,String}SliceValue` functions of `go.opentelemetry.io/attributes` by reducing the number of allocations. (#5549)
- Replace invalid percent-encoded octet sequences with replacement char in `go.opentelemetry.io/otel/baggage`. (#5528)
## [1.27.0/0.49.0/0.3.0] 2024-05-21
### Added
- Add example for `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`. (#5242)
- Add `RecordFactory` in `go.opentelemetry.io/otel/sdk/log/logtest` to facilitate testing exporter and processor implementations. (#5258)
- Add `RecordFactory` in `go.opentelemetry.io/otel/log/logtest` to facilitate testing bridge implementations. (#5263)
- The count of dropped records from the `BatchProcessor` in `go.opentelemetry.io/otel/sdk/log` is logged. (#5276)
- Add metrics in the `otel-collector` example. (#5283)
- Add the synchronous gauge instrument to `go.opentelemetry.io/otel/metric`. (#5304)
- An `int64` or `float64` synchronous gauge instrument can now be created from a `Meter`.
- All implementations of the API (`go.opentelemetry.io/otel/metric/noop`, `go.opentelemetry.io/otel/sdk/metric`) are updated to support this instrument.
- Add logs to `go.opentelemetry.io/otel/example/dice`. (#5349)
### Changed
- The `Shutdown` method of `Exporter` in `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` ignores the context cancellation and always returns `nil`. (#5189)
- The `ForceFlush` and `Shutdown` methods of the exporter returned by `New` in `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` ignore the context cancellation and always return `nil`. (#5189)
- Apply the value length limits to `Record` attributes in `go.opentelemetry.io/otel/sdk/log`. (#5230)
- De-duplicate map attributes added to a `Record` in `go.opentelemetry.io/otel/sdk/log`. (#5230)
- `go.opentelemetry.io/otel/exporters/stdout/stdoutlog` won't print timestamps when `WithoutTimestamps` option is set. (#5241)
- The `go.opentelemetry.io/otel/exporters/stdout/stdoutlog` exporter won't print `AttributeValueLengthLimit` and `AttributeCountLimit` fields now, instead it prints the `DroppedAttributes` field. (#5272)
- Improved performance in the `Stringer` implementation of `go.opentelemetry.io/otel/baggage.Member` by reducing the number of allocations. (#5286)
- Set the start time for last-value aggregates in `go.opentelemetry.io/otel/sdk/metric`. (#5305)
- The `Span` in `go.opentelemetry.io/otel/sdk/trace` will record links without span context if either non-empty `TraceState` or attributes are provided. (#5315)
- Upgrade all dependencies of `go.opentelemetry.io/otel/semconv/v1.24.0` to `go.opentelemetry.io/otel/semconv/v1.25.0`. (#5374)
### Fixed
- Comparison of unordered maps for `go.opentelemetry.io/otel/log.KeyValue` and `go.opentelemetry.io/otel/log.Value`. (#5306)
- Fix the empty output of `go.opentelemetry.io/otel/log.Value` in `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`. (#5311)
- Split the behavior of `Recorder` in `go.opentelemetry.io/otel/log/logtest` so it behaves as a `LoggerProvider` only. (#5365)
- Fix wrong package name of the error message when parsing endpoint URL in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#5371)
- Identify the `Logger` returned from the global `LoggerProvider` in `go.opentelemetry.io/otel/log/global` with its schema URL. (#5375)
## [1.26.0/0.48.0/0.2.0-alpha] 2024-04-24 ## [1.26.0/0.48.0/0.2.0-alpha] 2024-04-24
### Added ### Added
@ -33,6 +169,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Update `go.opentelemetry.io/proto/otlp` from v1.1.0 to v1.2.0. (#5177) - Update `go.opentelemetry.io/proto/otlp` from v1.1.0 to v1.2.0. (#5177)
- Improve performance of baggage member character validation in `go.opentelemetry.io/otel/baggage`. (#5214) - Improve performance of baggage member character validation in `go.opentelemetry.io/otel/baggage`. (#5214)
- The `otel-collector` example now uses docker compose to bring up services instead of kubernetes. (#5244)
### Fixed
- Slice attribute values in `go.opentelemetry.io/otel/attribute` are now emitted as their JSON representation. (#5159)
## [1.25.0/0.47.0/0.0.8/0.1.0-alpha] 2024-04-05 ## [1.25.0/0.47.0/0.0.8/0.1.0-alpha] 2024-04-05
@ -93,7 +234,7 @@ The next release will require at least [Go 1.21].
This module includes OpenTelemetry Go's implementation of the Logs Bridge API. This module includes OpenTelemetry Go's implementation of the Logs Bridge API.
This module is in an alpha state, it is subject to breaking changes. This module is in an alpha state, it is subject to breaking changes.
See our [versioning policy](./VERSIONING.md) for more info. (#4961) See our [versioning policy](./VERSIONING.md) for more info. (#4961)
- ARM64 platform to the compatibility testing suite. (#4994) - Add ARM64 platform to the compatibility testing suite. (#4994)
### Fixed ### Fixed
@ -210,7 +351,7 @@ See our [versioning policy](VERSIONING.md) for more information about these stab
## [1.20.0/0.43.0] 2023-11-10 ## [1.20.0/0.43.0] 2023-11-10
This release brings a breaking change for custom trace API implementations. Some interfaces (`TracerProvider`, `Tracer`, `Span`) now embed the `go.opentelemetry.io/otel/trace/embedded` types. Implementors need to update their implementations based on what they want the default behavior to be. See the "API Implementations" section of the [trace API] package documentation for more information about how to accomplish this. This release brings a breaking change for custom trace API implementations. Some interfaces (`TracerProvider`, `Tracer`, `Span`) now embed the `go.opentelemetry.io/otel/trace/embedded` types. Implementers need to update their implementations based on what they want the default behavior to be. See the "API Implementations" section of the [trace API] package documentation for more information about how to accomplish this.
### Added ### Added
@ -242,15 +383,15 @@ This release brings a breaking change for custom trace API implementations. Some
- `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` returns a `*MetricProducer` struct instead of the metric.Producer interface. (#4583) - `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` returns a `*MetricProducer` struct instead of the metric.Producer interface. (#4583)
- The `TracerProvider` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.TracerProvider` type. - The `TracerProvider` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.TracerProvider` type.
This extends the `TracerProvider` interface and is is a breaking change for any existing implementation. This extends the `TracerProvider` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be. Implementers need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620) See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620)
- The `Tracer` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Tracer` type. - The `Tracer` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Tracer` type.
This extends the `Tracer` interface and is is a breaking change for any existing implementation. This extends the `Tracer` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be. Implementers need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620) See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620)
- The `Span` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Span` type. - The `Span` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Span` type.
This extends the `Span` interface and is is a breaking change for any existing implementation. This extends the `Span` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be. Implementers need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620) See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620)
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` does no longer depend on `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#4660) - `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` does no longer depend on `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#4660)
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` does no longer depend on `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#4660) - `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` does no longer depend on `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#4660)
@ -886,7 +1027,7 @@ The next release will require at least [Go 1.19].
- Exported `Status` codes in the `go.opentelemetry.io/otel/exporters/zipkin` exporter are now exported as all upper case values. (#3340) - Exported `Status` codes in the `go.opentelemetry.io/otel/exporters/zipkin` exporter are now exported as all upper case values. (#3340)
- `Aggregation`s from `go.opentelemetry.io/otel/sdk/metric` with no data are not exported. (#3394, #3436) - `Aggregation`s from `go.opentelemetry.io/otel/sdk/metric` with no data are not exported. (#3394, #3436)
- Re-enabled Attribute Filters in the Metric SDK. (#3396) - Re-enabled Attribute Filters in the Metric SDK. (#3396)
- Asynchronous callbacks are only called if they are registered with at least one instrument that does not use drop aggragation. (#3408) - Asynchronous callbacks are only called if they are registered with at least one instrument that does not use drop aggregation. (#3408)
- Do not report empty partial-success responses in the `go.opentelemetry.io/otel/exporters/otlp` exporters. (#3438, #3432) - Do not report empty partial-success responses in the `go.opentelemetry.io/otel/exporters/otlp` exporters. (#3438, #3432)
- Handle partial success responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporters. (#3162, #3440) - Handle partial success responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporters. (#3162, #3440)
- Prevent duplicate Prometheus description, unit, and type. (#3469) - Prevent duplicate Prometheus description, unit, and type. (#3469)
@ -1931,7 +2072,7 @@ with major version 0.
- `NewExporter` from `exporters/otlp` now takes a `ProtocolDriver` as a parameter. (#1369) - `NewExporter` from `exporters/otlp` now takes a `ProtocolDriver` as a parameter. (#1369)
- Many OTLP Exporter options became gRPC ProtocolDriver options. (#1369) - Many OTLP Exporter options became gRPC ProtocolDriver options. (#1369)
- Unify endpoint API that related to OTel exporter. (#1401) - Unify endpoint API that related to OTel exporter. (#1401)
- Optimize metric histogram aggregator to re-use its slice of buckets. (#1435) - Optimize metric histogram aggregator to reuse its slice of buckets. (#1435)
- Metric aggregator Count() and histogram Bucket.Counts are consistently `uint64`. (1430) - Metric aggregator Count() and histogram Bucket.Counts are consistently `uint64`. (1430)
- Histogram aggregator accepts functional options, uses default boundaries if none given. (#1434) - Histogram aggregator accepts functional options, uses default boundaries if none given. (#1434)
- `SamplingResult` now passed a `Tracestate` from the parent `SpanContext` (#1432) - `SamplingResult` now passed a `Tracestate` from the parent `SpanContext` (#1432)
@ -2921,7 +3062,10 @@ It contains api and sdk for trace and meter.
- CircleCI build CI manifest files. - CircleCI build CI manifest files.
- CODEOWNERS file to track owners of this project. - CODEOWNERS file to track owners of this project.
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.26.0...HEAD [Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.29.0...HEAD
[1.29.0/0.51.0/0.5.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.29.0
[1.28.0/0.50.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.28.0
[1.27.0/0.49.0/0.3.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.27.0
[1.26.0/0.48.0/0.2.0-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.26.0 [1.26.0/0.48.0/0.2.0-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.26.0
[1.25.0/0.47.0/0.0.8/0.1.0-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.25.0 [1.25.0/0.47.0/0.0.8/0.1.0-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.25.0
[1.24.0/0.46.0/0.0.1-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.24.0 [1.24.0/0.46.0/0.0.1-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.24.0
@ -3002,6 +3146,9 @@ It contains api and sdk for trace and meter.
[0.1.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.1 [0.1.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.1
[0.1.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.0 [0.1.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.0
<!-- Released section ended -->
[Go 1.23]: https://go.dev/doc/go1.23
[Go 1.22]: https://go.dev/doc/go1.22 [Go 1.22]: https://go.dev/doc/go1.22
[Go 1.21]: https://go.dev/doc/go1.21 [Go 1.21]: https://go.dev/doc/go1.21
[Go 1.20]: https://go.dev/doc/go1.20 [Go 1.20]: https://go.dev/doc/go1.20

View file

@ -5,13 +5,13 @@
##################################################### #####################################################
# #
# Learn about membership in OpenTelemetry community: # Learn about membership in OpenTelemetry community:
# https://github.com/open-telemetry/community/blob/main/community-membership.md # https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md
# #
# #
# Learn about CODEOWNERS file format: # Learn about CODEOWNERS file format:
# https://help.github.com/en/articles/about-code-owners # https://help.github.com/en/articles/about-code-owners
# #
* @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu * @MrAlias @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu
CODEOWNERS @MrAlias @MadVikingGod @pellared @dashpole CODEOWNERS @MrAlias @MadVikingGod @pellared @dashpole @XSAM @dmathieu

View file

@ -570,6 +570,9 @@ functionality should be added, each one will need their own super-set
interfaces and will duplicate the pattern. For this reason, the simple targeted interfaces and will duplicate the pattern. For this reason, the simple targeted
interface that defines the specific functionality should be preferred. interface that defines the specific functionality should be preferred.
See also:
[Keeping Your Modules Compatible: Working with interfaces](https://go.dev/blog/module-compatibility#working-with-interfaces).
### Testing ### Testing
The tests should never leak goroutines. The tests should never leak goroutines.
@ -625,17 +628,15 @@ should be canceled.
### Approvers ### Approvers
- [Evan Torrie](https://github.com/evantorrie), Verizon Media
- [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics
- [Chester Cheung](https://github.com/hanyuancheung), Tencent - [Chester Cheung](https://github.com/hanyuancheung), Tencent
- [Damien Mathieu](https://github.com/dmathieu), Elastic
- [Anthony Mirabella](https://github.com/Aneurysm9), AWS
### Maintainers ### Maintainers
- [David Ashpole](https://github.com/dashpole), Google
- [Aaron Clawson](https://github.com/MadVikingGod), LightStep - [Aaron Clawson](https://github.com/MadVikingGod), LightStep
- [Damien Mathieu](https://github.com/dmathieu), Elastic
- [David Ashpole](https://github.com/dashpole), Google
- [Robert Pająk](https://github.com/pellared), Splunk - [Robert Pająk](https://github.com/pellared), Splunk
- [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics
- [Tyler Yahn](https://github.com/MrAlias), Splunk - [Tyler Yahn](https://github.com/MrAlias), Splunk
### Emeritus ### Emeritus
@ -643,11 +644,13 @@ should be canceled.
- [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb - [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb
- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep - [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep
- [Josh MacDonald](https://github.com/jmacd), LightStep - [Josh MacDonald](https://github.com/jmacd), LightStep
- [Anthony Mirabella](https://github.com/Aneurysm9), AWS
- [Evan Torrie](https://github.com/evantorrie), Yahoo
### Become an Approver or a Maintainer ### Become an Approver or a Maintainer
See the [community membership document in OpenTelemetry community See the [community membership document in OpenTelemetry community
repo](https://github.com/open-telemetry/community/blob/main/community-membership.md). repo](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md).
[Approver]: #approvers [Approver]: #approvers
[Maintainer]: #maintainers [Maintainer]: #maintainers

View file

@ -14,8 +14,8 @@ TIMEOUT = 60
.DEFAULT_GOAL := precommit .DEFAULT_GOAL := precommit
.PHONY: precommit ci .PHONY: precommit ci
precommit: generate dependabot-generate license-check misspell go-mod-tidy golangci-lint-fix verify-readmes test-default precommit: generate license-check misspell go-mod-tidy golangci-lint-fix verify-readmes verify-mods test-default
ci: generate dependabot-check license-check lint vanity-import-check verify-readmes build test-default check-clean-work-tree test-coverage ci: generate license-check lint vanity-import-check verify-readmes verify-mods build test-default check-clean-work-tree test-coverage
# Tools # Tools
@ -39,9 +39,6 @@ $(TOOLS)/crosslink: PACKAGE=go.opentelemetry.io/build-tools/crosslink
SEMCONVKIT = $(TOOLS)/semconvkit SEMCONVKIT = $(TOOLS)/semconvkit
$(TOOLS)/semconvkit: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/semconvkit $(TOOLS)/semconvkit: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/semconvkit
DBOTCONF = $(TOOLS)/dbotconf
$(TOOLS)/dbotconf: PACKAGE=go.opentelemetry.io/build-tools/dbotconf
GOLANGCI_LINT = $(TOOLS)/golangci-lint GOLANGCI_LINT = $(TOOLS)/golangci-lint
$(TOOLS)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint $(TOOLS)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint
@ -70,7 +67,7 @@ GOVULNCHECK = $(TOOLS)/govulncheck
$(TOOLS)/govulncheck: PACKAGE=golang.org/x/vuln/cmd/govulncheck $(TOOLS)/govulncheck: PACKAGE=golang.org/x/vuln/cmd/govulncheck
.PHONY: tools .PHONY: tools
tools: $(CROSSLINK) $(DBOTCONF) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE) tools: $(CROSSLINK) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE)
# Virtualized python tools via docker # Virtualized python tools via docker
@ -181,17 +178,14 @@ test-coverage: $(GOCOVMERGE)
done; \ done; \
$(GOCOVMERGE) $$(find . -name coverage.out) > coverage.txt $(GOCOVMERGE) $$(find . -name coverage.out) > coverage.txt
# Adding a directory will include all benchmarks in that directory if a filter is not specified.
BENCHMARK_TARGETS := sdk/trace
.PHONY: benchmark .PHONY: benchmark
benchmark: $(BENCHMARK_TARGETS:%=benchmark/%) benchmark: $(OTEL_GO_MOD_DIRS:%=benchmark/%)
BENCHMARK_FILTER = .
# You can override the filter for a particular directory by adding a rule here.
benchmark/sdk/trace: BENCHMARK_FILTER = SpanWithAttributes_8/AlwaysSample
benchmark/%: benchmark/%:
@echo "$(GO) test -timeout $(TIMEOUT)s -run=xxxxxMatchNothingxxxxx -bench=$(BENCHMARK_FILTER) $*..." \ @echo "$(GO) test -run=xxxxxMatchNothingxxxxx -bench=. $*..." \
&& cd $* \ && cd $* \
$(foreach filter, $(BENCHMARK_FILTER), && $(GO) test -timeout $(TIMEOUT)s -run=xxxxxMatchNothingxxxxx -bench=$(filter)) && $(GO) list ./... \
| grep -v third_party \
| xargs $(GO) test -run=xxxxxMatchNothingxxxxx -bench=.
.PHONY: golangci-lint golangci-lint-fix .PHONY: golangci-lint golangci-lint-fix
golangci-lint-fix: ARGS=--fix golangci-lint-fix: ARGS=--fix
@ -252,15 +246,6 @@ license-check:
exit 1; \ exit 1; \
fi fi
DEPENDABOT_CONFIG = .github/dependabot.yml
.PHONY: dependabot-check
dependabot-check: $(DBOTCONF)
@$(DBOTCONF) verify $(DEPENDABOT_CONFIG) || ( echo "(run: make dependabot-generate)"; exit 1 )
.PHONY: dependabot-generate
dependabot-generate: $(DBOTCONF)
@$(DBOTCONF) generate > $(DEPENDABOT_CONFIG)
.PHONY: check-clean-work-tree .PHONY: check-clean-work-tree
check-clean-work-tree: check-clean-work-tree:
@if ! git diff --quiet; then \ @if ! git diff --quiet; then \
@ -276,10 +261,7 @@ SEMCONVPKG ?= "semconv/"
semconv-generate: $(SEMCONVGEN) $(SEMCONVKIT) semconv-generate: $(SEMCONVGEN) $(SEMCONVKIT)
[ "$(TAG)" ] || ( echo "TAG unset: missing opentelemetry semantic-conventions tag"; exit 1 ) [ "$(TAG)" ] || ( echo "TAG unset: missing opentelemetry semantic-conventions tag"; exit 1 )
[ "$(OTEL_SEMCONV_REPO)" ] || ( echo "OTEL_SEMCONV_REPO unset: missing path to opentelemetry semantic-conventions repo"; exit 1 ) [ "$(OTEL_SEMCONV_REPO)" ] || ( echo "OTEL_SEMCONV_REPO unset: missing path to opentelemetry semantic-conventions repo"; exit 1 )
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=span -p conventionType=trace -f trace.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=attribute_group -p conventionType=trace -f attribute_group.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)" $(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=attribute_group -p conventionType=trace -f attribute_group.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=event -p conventionType=event -f event.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=resource -p conventionType=resource -f resource.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=metric -f metric.go -t "$(SEMCONVPKG)/metric_template.j2" -s "$(TAG)" $(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=metric -f metric.go -t "$(SEMCONVPKG)/metric_template.j2" -s "$(TAG)"
$(SEMCONVKIT) -output "$(SEMCONVPKG)/$(TAG)" -tag "$(TAG)" $(SEMCONVKIT) -output "$(SEMCONVPKG)/$(TAG)" -tag "$(TAG)"
@ -292,16 +274,20 @@ gorelease/%:| $(GORELEASE)
&& $(GORELEASE) \ && $(GORELEASE) \
|| echo "" || echo ""
.PHONY: verify-mods
verify-mods: $(MULTIMOD)
$(MULTIMOD) verify
.PHONY: prerelease .PHONY: prerelease
prerelease: $(MULTIMOD) prerelease: verify-mods
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
$(MULTIMOD) verify && $(MULTIMOD) prerelease -m ${MODSET} $(MULTIMOD) prerelease -m ${MODSET}
COMMIT ?= "HEAD" COMMIT ?= "HEAD"
.PHONY: add-tags .PHONY: add-tags
add-tags: $(MULTIMOD) add-tags: verify-mods
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
$(MULTIMOD) verify && $(MULTIMOD) tag -m ${MODSET} -c ${COMMIT} $(MULTIMOD) tag -m ${MODSET} -c ${COMMIT}
.PHONY: lint-markdown .PHONY: lint-markdown
lint-markdown: lint-markdown:

View file

@ -15,7 +15,7 @@ It provides a set of APIs to directly measure performance and behavior of your s
|---------|--------------------| |---------|--------------------|
| Traces | Stable | | Traces | Stable |
| Metrics | Stable | | Metrics | Stable |
| Logs | In development[^1] | | Logs | Beta[^1] |
Progress and status specific to this repository is tracked in our Progress and status specific to this repository is tracked in our
[project boards](https://github.com/open-telemetry/opentelemetry-go/projects) [project boards](https://github.com/open-telemetry/opentelemetry-go/projects)
@ -48,17 +48,26 @@ stop ensuring compatibility with these versions in the following manner:
Currently, this project supports the following environments. Currently, this project supports the following environments.
| OS | Go Version | Architecture | | OS | Go Version | Architecture |
|---------|------------|--------------| |----------|------------|--------------|
| Ubuntu | 1.23 | amd64 |
| Ubuntu | 1.22 | amd64 | | Ubuntu | 1.22 | amd64 |
| Ubuntu | 1.21 | amd64 | | Ubuntu | 1.21 | amd64 |
| Ubuntu | 1.23 | 386 |
| Ubuntu | 1.22 | 386 | | Ubuntu | 1.22 | 386 |
| Ubuntu | 1.21 | 386 | | Ubuntu | 1.21 | 386 |
| Linux | 1.23 | arm64 |
| Linux | 1.22 | arm64 | | Linux | 1.22 | arm64 |
| Linux | 1.21 | arm64 | | Linux | 1.21 | arm64 |
| MacOS | 1.22 | amd64 | | macOS 13 | 1.23 | amd64 |
| MacOS | 1.21 | amd64 | | macOS 13 | 1.22 | amd64 |
| macOS 13 | 1.21 | amd64 |
| macOS | 1.23 | arm64 |
| macOS | 1.22 | arm64 |
| macOS | 1.21 | arm64 |
| Windows | 1.23 | amd64 |
| Windows | 1.22 | amd64 | | Windows | 1.22 | amd64 |
| Windows | 1.21 | amd64 | | Windows | 1.21 | amd64 |
| Windows | 1.23 | 386 |
| Windows | 1.22 | 386 | | Windows | 1.22 | 386 |
| Windows | 1.21 | 386 | | Windows | 1.21 | 386 |
@ -97,12 +106,12 @@ export pipeline to send that telemetry to an observability platform.
All officially supported exporters for the OpenTelemetry project are contained in the [exporters directory](./exporters). All officially supported exporters for the OpenTelemetry project are contained in the [exporters directory](./exporters).
| Exporter | Metrics | Traces | | Exporter | Logs | Metrics | Traces |
|---------------------------------------|:-------:|:------:| |---------------------------------------|:----:|:-------:|:------:|
| [OTLP](./exporters/otlp/) | ✓ | ✓ | | [OTLP](./exporters/otlp/) | ✓ | ✓ | ✓ |
| [Prometheus](./exporters/prometheus/) | ✓ | | | [Prometheus](./exporters/prometheus/) | | ✓ | |
| [stdout](./exporters/stdout/) | ✓ | ✓ | | [stdout](./exporters/stdout/) | ✓ | ✓ | ✓ |
| [Zipkin](./exporters/zipkin/) | | ✓ | | [Zipkin](./exporters/zipkin/) | | | ✓ |
## Contributing ## Contributing

View file

@ -27,6 +27,12 @@ You can run `make gorelease` that runs [gorelease](https://pkg.go.dev/golang.org
You can check/report problems with `gorelease` [here](https://golang.org/issues/26420). You can check/report problems with `gorelease` [here](https://golang.org/issues/26420).
## Verify changes for contrib repository
If the changes in the main repository are going to affect the contrib repository, it is important to verify that the changes are compatible with the contrib repository.
Follow [the steps](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/RELEASING.md#verify-otel-changes) in the contrib repository to verify OTel changes.
## Pre-Release ## Pre-Release
First, decide which module sets will be released and update their versions First, decide which module sets will be released and update their versions
@ -63,6 +69,7 @@ Update go.mod for submodules to depend on the new release which will happen in t
``` ```
- Move all the `Unreleased` changes into a new section following the title scheme (`[<new tag>] - <date of release>`). - Move all the `Unreleased` changes into a new section following the title scheme (`[<new tag>] - <date of release>`).
- Make sure the new section is under the comment for released section, like `<!-- Released section -->`, so it is protected from being overwritten in the future.
- Update all the appropriate links at the bottom. - Update all the appropriate links at the bottom.
4. Push the changes to upstream and create a Pull Request on GitHub. 4. Push the changes to upstream and create a Pull Request on GitHub.

View file

@ -231,15 +231,27 @@ func (v Value) Emit() string {
case BOOL: case BOOL:
return strconv.FormatBool(v.AsBool()) return strconv.FormatBool(v.AsBool())
case INT64SLICE: case INT64SLICE:
return fmt.Sprint(v.asInt64Slice()) j, err := json.Marshal(v.asInt64Slice())
if err != nil {
return fmt.Sprintf("invalid: %v", v.asInt64Slice())
}
return string(j)
case INT64: case INT64:
return strconv.FormatInt(v.AsInt64(), 10) return strconv.FormatInt(v.AsInt64(), 10)
case FLOAT64SLICE: case FLOAT64SLICE:
return fmt.Sprint(v.asFloat64Slice()) j, err := json.Marshal(v.asFloat64Slice())
if err != nil {
return fmt.Sprintf("invalid: %v", v.asFloat64Slice())
}
return string(j)
case FLOAT64: case FLOAT64:
return fmt.Sprint(v.AsFloat64()) return fmt.Sprint(v.AsFloat64())
case STRINGSLICE: case STRINGSLICE:
return fmt.Sprint(v.asStringSlice()) j, err := json.Marshal(v.asStringSlice())
if err != nil {
return fmt.Sprintf("invalid: %v", v.asStringSlice())
}
return string(j)
case STRING: case STRING:
return v.stringly return v.stringly
default: default:

View file

@ -44,9 +44,15 @@ type Property struct {
// NewKeyProperty returns a new Property for key. // NewKeyProperty returns a new Property for key.
// //
// The passed key must be valid, non-empty UTF-8 string.
// If key is invalid, an error will be returned. // If key is invalid, an error will be returned.
// However, the specific Propagators that are used to transmit baggage entries across
// component boundaries may impose their own restrictions on Property key.
// For example, the W3C Baggage specification restricts the Property keys to strings that
// satisfy the token definition from RFC7230, Section 3.2.6.
// For maximum compatibility, alpha-numeric value are strongly recommended to be used as Property key.
func NewKeyProperty(key string) (Property, error) { func NewKeyProperty(key string) (Property, error) {
if !validateKey(key) { if !validateBaggageName(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
} }
@ -62,6 +68,10 @@ func NewKeyProperty(key string) (Property, error) {
// Notice: Consider using [NewKeyValuePropertyRaw] instead // Notice: Consider using [NewKeyValuePropertyRaw] instead
// that does not require percent-encoding of the value. // that does not require percent-encoding of the value.
func NewKeyValueProperty(key, value string) (Property, error) { func NewKeyValueProperty(key, value string) (Property, error) {
if !validateKey(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
if !validateValue(value) { if !validateValue(value) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value) return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
} }
@ -74,11 +84,20 @@ func NewKeyValueProperty(key, value string) (Property, error) {
// NewKeyValuePropertyRaw returns a new Property for key with value. // NewKeyValuePropertyRaw returns a new Property for key with value.
// //
// The passed key must be compliant with W3C Baggage specification. // The passed key must be valid, non-empty UTF-8 string.
// The passed value must be valid UTF-8 string.
// However, the specific Propagators that are used to transmit baggage entries across
// component boundaries may impose their own restrictions on Property key.
// For example, the W3C Baggage specification restricts the Property keys to strings that
// satisfy the token definition from RFC7230, Section 3.2.6.
// For maximum compatibility, alpha-numeric value are strongly recommended to be used as Property key.
func NewKeyValuePropertyRaw(key, value string) (Property, error) { func NewKeyValuePropertyRaw(key, value string) (Property, error) {
if !validateKey(key) { if !validateBaggageName(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
} }
if !validateBaggageValue(value) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
p := Property{ p := Property{
key: key, key: key,
@ -115,12 +134,15 @@ func (p Property) validate() error {
return fmt.Errorf("invalid property: %w", err) return fmt.Errorf("invalid property: %w", err)
} }
if !validateKey(p.key) { if !validateBaggageName(p.key) {
return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key)) return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
} }
if !p.hasValue && p.value != "" { if !p.hasValue && p.value != "" {
return errFunc(errors.New("inconsistent value")) return errFunc(errors.New("inconsistent value"))
} }
if p.hasValue && !validateBaggageValue(p.value) {
return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
}
return nil return nil
} }
@ -138,7 +160,15 @@ func (p Property) Value() (string, bool) {
// String encodes Property into a header string compliant with the W3C Baggage // String encodes Property into a header string compliant with the W3C Baggage
// specification. // specification.
// It would return empty string if the key is invalid with the W3C Baggage
// specification. This could happen for a UTF-8 key, as it may contain
// invalid characters.
func (p Property) String() string { func (p Property) String() string {
// W3C Baggage specification does not allow percent-encoded keys.
if !validateKey(p.key) {
return ""
}
if p.hasValue { if p.hasValue {
return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, valueEscape(p.value)) return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, valueEscape(p.value))
} }
@ -203,9 +233,14 @@ func (p properties) validate() error {
// String encodes properties into a header string compliant with the W3C Baggage // String encodes properties into a header string compliant with the W3C Baggage
// specification. // specification.
func (p properties) String() string { func (p properties) String() string {
props := make([]string, len(p)) props := make([]string, 0, len(p))
for i, prop := range p { for _, prop := range p {
props[i] = prop.String() s := prop.String()
// Ignored empty properties.
if s != "" {
props = append(props, s)
}
} }
return strings.Join(props, propertyDelimiter) return strings.Join(props, propertyDelimiter)
} }
@ -230,6 +265,10 @@ type Member struct {
// Notice: Consider using [NewMemberRaw] instead // Notice: Consider using [NewMemberRaw] instead
// that does not require percent-encoding of the value. // that does not require percent-encoding of the value.
func NewMember(key, value string, props ...Property) (Member, error) { func NewMember(key, value string, props ...Property) (Member, error) {
if !validateKey(key) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
if !validateValue(value) { if !validateValue(value) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
} }
@ -242,7 +281,13 @@ func NewMember(key, value string, props ...Property) (Member, error) {
// NewMemberRaw returns a new Member from the passed arguments. // NewMemberRaw returns a new Member from the passed arguments.
// //
// The passed key must be compliant with W3C Baggage specification. // The passed key must be valid, non-empty UTF-8 string.
// The passed value must be valid UTF-8 string.
// However, the specific Propagators that are used to transmit baggage entries across
// component boundaries may impose their own restrictions on baggage key.
// For example, the W3C Baggage specification restricts the baggage keys to strings that
// satisfy the token definition from RFC7230, Section 3.2.6.
// For maximum compatibility, alpha-numeric value are strongly recommended to be used as baggage key.
func NewMemberRaw(key, value string, props ...Property) (Member, error) { func NewMemberRaw(key, value string, props ...Property) (Member, error) {
m := Member{ m := Member{
key: key, key: key,
@ -294,19 +339,45 @@ func parseMember(member string) (Member, error) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key) return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
} }
val := strings.TrimSpace(v) rawVal := strings.TrimSpace(v)
if !validateValue(val) { if !validateValue(rawVal) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, v) return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, v)
} }
// Decode a percent-encoded value. // Decode a percent-encoded value.
value, err := url.PathUnescape(val) unescapeVal, err := url.PathUnescape(rawVal)
if err != nil { if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %v", errInvalidValue, err) return newInvalidMember(), fmt.Errorf("%w: %w", errInvalidValue, err)
} }
value := replaceInvalidUTF8Sequences(len(rawVal), unescapeVal)
return Member{key: key, value: value, properties: props, hasData: true}, nil return Member{key: key, value: value, properties: props, hasData: true}, nil
} }
// replaceInvalidUTF8Sequences replaces invalid UTF-8 sequences with '<27>'.
func replaceInvalidUTF8Sequences(cap int, unescapeVal string) string {
if utf8.ValidString(unescapeVal) {
return unescapeVal
}
// W3C baggage spec:
// https://github.com/w3c/baggage/blob/8c215efbeebd3fa4b1aceb937a747e56444f22f3/baggage/HTTP_HEADER_FORMAT.md?plain=1#L69
var b strings.Builder
b.Grow(cap)
for i := 0; i < len(unescapeVal); {
r, size := utf8.DecodeRuneInString(unescapeVal[i:])
if r == utf8.RuneError && size == 1 {
// Invalid UTF-8 sequence found, replace it with '<27>'
_, _ = b.WriteString("<22>")
} else {
_, _ = b.WriteRune(r)
}
i += size
}
return b.String()
}
// validate ensures m conforms to the W3C Baggage specification. // validate ensures m conforms to the W3C Baggage specification.
// A key must be an ASCII string, returning an error otherwise. // A key must be an ASCII string, returning an error otherwise.
func (m Member) validate() error { func (m Member) validate() error {
@ -314,9 +385,12 @@ func (m Member) validate() error {
return fmt.Errorf("%w: %q", errInvalidMember, m) return fmt.Errorf("%w: %q", errInvalidMember, m)
} }
if !validateKey(m.key) { if !validateBaggageName(m.key) {
return fmt.Errorf("%w: %q", errInvalidKey, m.key) return fmt.Errorf("%w: %q", errInvalidKey, m.key)
} }
if !validateBaggageValue(m.value) {
return fmt.Errorf("%w: %q", errInvalidValue, m.value)
}
return m.properties.validate() return m.properties.validate()
} }
@ -331,13 +405,18 @@ func (m Member) Properties() []Property { return m.properties.Copy() }
// String encodes Member into a header string compliant with the W3C Baggage // String encodes Member into a header string compliant with the W3C Baggage
// specification. // specification.
// It would return empty string if the key is invalid with the W3C Baggage
// specification. This could happen for a UTF-8 key, as it may contain
// invalid characters.
func (m Member) String() string { func (m Member) String() string {
// A key is just an ASCII string. A value is restricted to be // W3C Baggage specification does not allow percent-encoded keys.
// US-ASCII characters excluding CTLs, whitespace, if !validateKey(m.key) {
// DQUOTE, comma, semicolon, and backslash. return ""
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, valueEscape(m.value)) }
s := m.key + keyValueDelimiter + valueEscape(m.value)
if len(m.properties) > 0 { if len(m.properties) > 0 {
s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String()) s += propertyDelimiter + m.properties.String()
} }
return s return s
} }
@ -448,7 +527,7 @@ func (b Baggage) Member(key string) Member {
} }
// Members returns all the baggage list-members. // Members returns all the baggage list-members.
// The order of the returned list-members does not have significance. // The order of the returned list-members is not significant.
// //
// The returned members are not validated, as we assume the validation happened // The returned members are not validated, as we assume the validation happened
// when they were added to the Baggage. // when they were added to the Baggage.
@ -469,8 +548,8 @@ func (b Baggage) Members() []Member {
return members return members
} }
// SetMember returns a copy the Baggage with the member included. If the // SetMember returns a copy of the Baggage with the member included. If the
// baggage contains a Member with the same key the existing Member is // baggage contains a Member with the same key, the existing Member is
// replaced. // replaced.
// //
// If member is invalid according to the W3C Baggage specification, an error // If member is invalid according to the W3C Baggage specification, an error
@ -528,14 +607,22 @@ func (b Baggage) Len() int {
// String encodes Baggage into a header string compliant with the W3C Baggage // String encodes Baggage into a header string compliant with the W3C Baggage
// specification. // specification.
// It would ignore members where the member key is invalid with the W3C Baggage
// specification. This could happen for a UTF-8 key, as it may contain
// invalid characters.
func (b Baggage) String() string { func (b Baggage) String() string {
members := make([]string, 0, len(b.list)) members := make([]string, 0, len(b.list))
for k, v := range b.list { for k, v := range b.list {
members = append(members, Member{ s := Member{
key: k, key: k,
value: v.Value, value: v.Value,
properties: fromInternalProperties(v.Properties), properties: fromInternalProperties(v.Properties),
}.String()) }.String()
// Ignored empty members.
if s != "" {
members = append(members, s)
}
} }
return strings.Join(members, listDelimiter) return strings.Join(members, listDelimiter)
} }
@ -607,10 +694,12 @@ func parsePropertyInternal(s string) (p Property, ok bool) {
} }
// Decode a percent-encoded value. // Decode a percent-encoded value.
value, err := url.PathUnescape(s[valueStart:valueEnd]) rawVal := s[valueStart:valueEnd]
unescapeVal, err := url.PathUnescape(rawVal)
if err != nil { if err != nil {
return return
} }
value := replaceInvalidUTF8Sequences(len(rawVal), unescapeVal)
ok = true ok = true
p.key = s[keyStart:keyEnd] p.key = s[keyStart:keyEnd]
@ -720,6 +809,24 @@ var safeKeyCharset = [utf8.RuneSelf]bool{
'~': true, '~': true,
} }
// validateBaggageName checks if the string is a valid OpenTelemetry Baggage name.
// Baggage name is a valid, non-empty UTF-8 string.
func validateBaggageName(s string) bool {
if len(s) == 0 {
return false
}
return utf8.ValidString(s)
}
// validateBaggageValue checks if the string is a valid OpenTelemetry Baggage value.
// Baggage value is a valid UTF-8 strings.
// Empty string is also a valid UTF-8 string.
func validateBaggageValue(s string) bool {
return utf8.ValidString(s)
}
// validateKey checks if the string is a valid W3C Baggage key.
func validateKey(s string) bool { func validateKey(s string) bool {
if len(s) == 0 { if len(s) == 0 {
return false return false
@ -735,9 +842,10 @@ func validateKey(s string) bool {
} }
func validateKeyChar(c int32) bool { func validateKeyChar(c int32) bool {
return c >= 0 && c <= int32(utf8.RuneSelf) && safeKeyCharset[c] return c >= 0 && c < int32(utf8.RuneSelf) && safeKeyCharset[c]
} }
// validateValue checks if the string is a valid W3C Baggage value.
func validateValue(s string) bool { func validateValue(s string) bool {
for _, c := range s { for _, c := range s {
if !validateValueChar(c) { if !validateValueChar(c) {
@ -850,7 +958,7 @@ var safeValueCharset = [utf8.RuneSelf]bool{
} }
func validateValueChar(c int32) bool { func validateValueChar(c int32) bool {
return c >= 0 && c <= int32(utf8.RuneSelf) && safeValueCharset[c] return c >= 0 && c < int32(utf8.RuneSelf) && safeValueCharset[c]
} }
// valueEscape escapes the string so it can be safely placed inside a baggage value, // valueEscape escapes the string so it can be safely placed inside a baggage value,

View file

@ -83,7 +83,7 @@ func (c *Code) UnmarshalJSON(b []byte) error {
return fmt.Errorf("invalid code: %q", ci) return fmt.Errorf("invalid code: %q", ci)
} }
*c = Code(ci) *c = Code(ci) // nolint: gosec // Bit size of 32 check above.
return nil return nil
} }
return fmt.Errorf("invalid code: %q", string(b)) return fmt.Errorf("invalid code: %q", string(b))

View file

@ -17,6 +17,8 @@ To read more about tracing, see go.opentelemetry.io/otel/trace.
To read more about metrics, see go.opentelemetry.io/otel/metric. To read more about metrics, see go.opentelemetry.io/otel/metric.
To read more about logs, see go.opentelemetry.io/otel/log.
To read more about propagation, see go.opentelemetry.io/otel/propagation and To read more about propagation, see go.opentelemetry.io/otel/propagation and
go.opentelemetry.io/otel/baggage. go.opentelemetry.io/otel/baggage.
*/ */

View file

@ -4,6 +4,8 @@
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform" package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
import ( import (
"math"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/instrumentation"
@ -102,9 +104,9 @@ func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
Name: sd.Name(), Name: sd.Name(),
Attributes: KeyValues(sd.Attributes()), Attributes: KeyValues(sd.Attributes()),
Events: spanEvents(sd.Events()), Events: spanEvents(sd.Events()),
DroppedAttributesCount: uint32(sd.DroppedAttributes()), DroppedAttributesCount: clampUint32(sd.DroppedAttributes()),
DroppedEventsCount: uint32(sd.DroppedEvents()), DroppedEventsCount: clampUint32(sd.DroppedEvents()),
DroppedLinksCount: uint32(sd.DroppedLinks()), DroppedLinksCount: clampUint32(sd.DroppedLinks()),
} }
if psid := sd.Parent().SpanID(); psid.IsValid() { if psid := sd.Parent().SpanID(); psid.IsValid() {
@ -115,6 +117,16 @@ func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
return s return s
} }
func clampUint32(v int) uint32 {
if v < 0 {
return 0
}
if int64(v) > math.MaxUint32 {
return math.MaxUint32
}
return uint32(v) // nolint: gosec // Overflow/Underflow checked.
}
// status transform a span code and message into an OTLP span status. // status transform a span code and message into an OTLP span status.
func status(status codes.Code, message string) *tracepb.Status { func status(status codes.Code, message string) *tracepb.Status {
var c tracepb.Status_StatusCode var c tracepb.Status_StatusCode
@ -153,7 +165,7 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
TraceId: tid[:], TraceId: tid[:],
SpanId: sid[:], SpanId: sid[:],
Attributes: KeyValues(otLink.Attributes), Attributes: KeyValues(otLink.Attributes),
DroppedAttributesCount: uint32(otLink.DroppedAttributeCount), DroppedAttributesCount: clampUint32(otLink.DroppedAttributeCount),
Flags: flags, Flags: flags,
}) })
} }
@ -182,7 +194,7 @@ func spanEvents(es []tracesdk.Event) []*tracepb.Span_Event {
Name: es[i].Name, Name: es[i].Name,
TimeUnixNano: uint64(es[i].Time.UnixNano()), TimeUnixNano: uint64(es[i].Time.UnixNano()),
Attributes: KeyValues(es[i].Attributes), Attributes: KeyValues(es[i].Attributes),
DroppedAttributesCount: uint32(es[i].DroppedAttributeCount), DroppedAttributesCount: clampUint32(es[i].DroppedAttributeCount),
} }
} }
return events return events

View file

@ -12,9 +12,8 @@ The environment variables described below can be used for configuration.
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (default: "https://localhost:4317") - OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (default: "https://localhost:4317") -
target to which the exporter sends telemetry. target to which the exporter sends telemetry.
The target syntax is defined in https://github.com/grpc/grpc/blob/master/doc/naming.md. The target syntax is defined in https://github.com/grpc/grpc/blob/master/doc/naming.md.
The value must contain a host. The value must contain a scheme ("http" or "https") and host.
The value may additionally a port, a scheme, and a path. The value may additionally contain a port, and a path.
The value accepts "http" and "https" scheme.
The value should not contain a query string or fragment. The value should not contain a query string or fragment.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT.
The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options. The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options.

View file

@ -15,6 +15,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/internal/global"
) )
@ -163,12 +164,16 @@ func stringToHeader(value string) map[string]string {
global.Error(errors.New("missing '="), "parse headers", "input", header) global.Error(errors.New("missing '="), "parse headers", "input", header)
continue continue
} }
name, err := url.PathUnescape(n)
if err != nil { trimmedName := strings.TrimSpace(n)
global.Error(err, "escape header key", "key", n)
// Validate the key.
if !isValidHeaderKey(trimmedName) {
global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName)
continue continue
} }
trimmedName := strings.TrimSpace(name)
// Only decode the value.
value, err := url.PathUnescape(v) value, err := url.PathUnescape(v)
if err != nil { if err != nil {
global.Error(err, "escape header value", "value", v) global.Error(err, "escape header value", "value", v)
@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) {
} }
return cp, nil return cp, nil
} }
func isValidHeaderKey(key string) bool {
if key == "" {
return false
}
for _, c := range key {
if !isTokenChar(c) {
return false
}
}
return true
}
func isTokenChar(c rune) bool {
return c <= unicode.MaxASCII && (unicode.IsLetter(c) ||
unicode.IsDigit(c) ||
c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~')
}

View file

@ -256,6 +256,9 @@ func NewGRPCOption(fn func(cfg Config) Config) GRPCOption {
// Generic Options // Generic Options
// WithEndpoint configures the trace host and port only; endpoint should
// resemble "example.com" or "localhost:4317". To configure the scheme and path,
// use WithEndpointURL.
func WithEndpoint(endpoint string) GenericOption { func WithEndpoint(endpoint string) GenericOption {
return newGenericOption(func(cfg Config) Config { return newGenericOption(func(cfg Config) Config {
cfg.Traces.Endpoint = endpoint cfg.Traces.Endpoint = endpoint
@ -263,6 +266,8 @@ func WithEndpoint(endpoint string) GenericOption {
}) })
} }
// WithEndpointURL configures the trace scheme, host, port, and path; the
// provided value should resemble "https://example.com:4318/v1/traces".
func WithEndpointURL(v string) GenericOption { func WithEndpointURL(v string) GenericOption {
return newGenericOption(func(cfg Config) Config { return newGenericOption(func(cfg Config) Config {
u, err := url.Parse(v) u, err := url.Parse(v)

View file

@ -112,7 +112,7 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
} }
if ctxErr := waitFunc(ctx, delay); ctxErr != nil { if ctxErr := waitFunc(ctx, delay); ctxErr != nil {
return fmt.Errorf("%w: %s", ctxErr, err) return fmt.Errorf("%w: %w", ctxErr, err)
} }
} }
} }

View file

@ -53,12 +53,15 @@ func WithInsecure() Option {
return wrappedOption{otlpconfig.WithInsecure()} return wrappedOption{otlpconfig.WithInsecure()}
} }
// WithEndpoint sets the target endpoint the Exporter will connect to. // WithEndpoint sets the target endpoint (host and port) the Exporter will
// connect to. The provided endpoint should resemble "example.com:4317" (no
// scheme or path).
// //
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT // If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// environment variable is set, and this option is not passed, that variable // environment variable is set, and this option is not passed, that variable
// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT // value will be used. If both environment variables are set,
// will take precedence. // OTEL_EXPORTER_OTLP_TRACES_ENDPOINT will take precedence. If an environment
// variable is set, and this option is passed, this option will take precedence.
// //
// If both this option and WithEndpointURL are used, the last used option will // If both this option and WithEndpointURL are used, the last used option will
// take precedence. // take precedence.
@ -71,12 +74,15 @@ func WithEndpoint(endpoint string) Option {
return wrappedOption{otlpconfig.WithEndpoint(endpoint)} return wrappedOption{otlpconfig.WithEndpoint(endpoint)}
} }
// WithEndpointURL sets the target endpoint URL the Exporter will connect to. // WithEndpointURL sets the target endpoint URL (scheme, host, port, path)
// the Exporter will connect to. The provided endpoint URL should resemble
// "https://example.com:4318/v1/traces".
// //
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT // If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// environment variable is set, and this option is not passed, that variable // environment variable is set, and this option is not passed, that variable
// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT // value will be used. If both environment variables are set,
// will take precedence. // OTEL_EXPORTER_OTLP_TRACES_ENDPOINT will take precedence. If an environment
// variable is set, and this option is passed, this option will take precedence.
// //
// If both this option and WithEndpoint are used, the last used option will // If both this option and WithEndpoint are used, the last used option will
// take precedence. // take precedence.
@ -84,7 +90,7 @@ func WithEndpoint(endpoint string) Option {
// If an invalid URL is provided, the default value will be kept. // If an invalid URL is provided, the default value will be kept.
// //
// By default, if an environment variable is not set, and this option is not // By default, if an environment variable is not set, and this option is not
// passed, "localhost:4317" will be used. // passed, "https://localhost:4317/v1/traces" will be used.
// //
// This option has no effect if WithGRPCConn is used. // This option has no effect if WithGRPCConn is used.
func WithEndpointURL(u string) Option { func WithEndpointURL(u string) Option {

View file

@ -0,0 +1,3 @@
# OTLP Trace HTTP Exporter
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp)](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp)

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlptracehttp // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" package otlptracehttp // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
@ -25,6 +14,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
@ -85,10 +75,17 @@ func NewClient(opts ...Option) otlptrace.Client {
Transport: ourTransport, Transport: ourTransport,
Timeout: cfg.Traces.Timeout, Timeout: cfg.Traces.Timeout,
} }
if cfg.Traces.TLSCfg != nil || cfg.Traces.Proxy != nil {
clonedTransport := ourTransport.Clone()
httpClient.Transport = clonedTransport
if cfg.Traces.TLSCfg != nil { if cfg.Traces.TLSCfg != nil {
transport := ourTransport.Clone() clonedTransport.TLSClientConfig = cfg.Traces.TLSCfg
transport.TLSClientConfig = cfg.Traces.TLSCfg }
httpClient.Transport = transport if cfg.Traces.Proxy != nil {
clonedTransport.Proxy = cfg.Traces.Proxy
}
} }
stopCh := make(chan struct{}) stopCh := make(chan struct{})
@ -155,7 +152,7 @@ func (d *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
resp, err := d.client.Do(request.Request) resp, err := d.client.Do(request.Request)
var urlErr *url.Error var urlErr *url.Error
if errors.As(err, &urlErr) && urlErr.Temporary() { if errors.As(err, &urlErr) && urlErr.Temporary() {
return newResponseError(http.Header{}) return newResponseError(http.Header{}, err)
} }
if err != nil { if err != nil {
return err return err
@ -202,11 +199,27 @@ func (d *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
sc == http.StatusBadGateway, sc == http.StatusBadGateway,
sc == http.StatusServiceUnavailable, sc == http.StatusServiceUnavailable,
sc == http.StatusGatewayTimeout: sc == http.StatusGatewayTimeout:
// Retry-able failures. Drain the body to reuse the connection. // Retry-able failures.
if _, err := io.Copy(io.Discard, resp.Body); err != nil { rErr := newResponseError(resp.Header, nil)
otel.Handle(err)
// server may return a message with the response
// body, so we read it to include in the error
// message to be returned. It will help in
// debugging the actual issue.
var respData bytes.Buffer
if _, err := io.Copy(&respData, resp.Body); err != nil {
_ = resp.Body.Close()
return err
} }
return newResponseError(resp.Header)
// overwrite the error message with the response body
// if it is not empty
if respStr := strings.TrimSpace(respData.String()); respStr != "" {
// Include response for context.
e := errors.New(respStr)
rErr = newResponseError(resp.Header, e)
}
return rErr
default: default:
return fmt.Errorf("failed to send to %s: %s", request.URL, resp.Status) return fmt.Errorf("failed to send to %s: %s", request.URL, resp.Status)
} }
@ -247,7 +260,7 @@ func (d *client) newRequest(body []byte) (request, error) {
if _, err := gz.Write(body); err != nil { if _, err := gz.Write(body); err != nil {
return req, err return req, err
} }
// Close needs to be called to ensure body if fully written. // Close needs to be called to ensure body is fully written.
if err := gz.Close(); err != nil { if err := gz.Close(); err != nil {
return req, err return req, err
} }
@ -295,24 +308,50 @@ func (r *request) reset(ctx context.Context) {
// retryableError represents a request failure that can be retried. // retryableError represents a request failure that can be retried.
type retryableError struct { type retryableError struct {
throttle int64 throttle int64
err error
} }
// newResponseError returns a retryableError and will extract any explicit // newResponseError returns a retryableError and will extract any explicit
// throttle delay contained in headers. // throttle delay contained in headers. The returned error wraps wrapped
func newResponseError(header http.Header) error { // if it is not nil.
func newResponseError(header http.Header, wrapped error) error {
var rErr retryableError var rErr retryableError
if s, ok := header["Retry-After"]; ok { if s, ok := header["Retry-After"]; ok {
if t, err := strconv.ParseInt(s[0], 10, 64); err == nil { if t, err := strconv.ParseInt(s[0], 10, 64); err == nil {
rErr.throttle = t rErr.throttle = t
} }
} }
rErr.err = wrapped
return rErr return rErr
} }
func (e retryableError) Error() string { func (e retryableError) Error() string {
if e.err != nil {
return fmt.Sprintf("retry-able request failure: %s", e.err.Error())
}
return "retry-able request failure" return "retry-able request failure"
} }
func (e retryableError) Unwrap() error {
return e.err
}
func (e retryableError) As(target interface{}) bool {
if e.err == nil {
return false
}
switch v := target.(type) {
case **retryableError:
*v = &e
return true
default:
return false
}
}
// evaluate returns if err is retry-able. If it is and it includes an explicit // evaluate returns if err is retry-able. If it is and it includes an explicit
// throttling delay, that delay is also returned. // throttling delay, that delay is also returned.
func evaluate(err error) (bool, time.Duration) { func evaluate(err error) (bool, time.Duration) {
@ -320,7 +359,10 @@ func evaluate(err error) (bool, time.Duration) {
return false, 0 return false, 0
} }
rErr, ok := err.(retryableError) // Do not use errors.As here, this should only be flattened one layer. If
// there are several chained errors, all the errors above it will be
// discarded if errors.As is used instead.
rErr, ok := err.(retryableError) //nolint:errorlint
if !ok { if !ok {
return false, 0 return false, 0
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* /*
Package otlptracehttp provides an OTLP span exporter using HTTP with protobuf payloads. Package otlptracehttp provides an OTLP span exporter using HTTP with protobuf payloads.
@ -37,7 +26,7 @@ The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WitnI
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS (default: none) - OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS (default: none) -
key-value pairs used as headers associated with HTTP requests. key-value pairs used as headers associated with HTTP requests.
The value is expected to be represented in a format matching to the [W3C Baggage HTTP Header Content Format], The value is expected to be represented in a format matching the [W3C Baggage HTTP Header Content Format],
except that additional semi-colon delimited metadata is not supported. except that additional semi-colon delimited metadata is not supported.
Example value: "key1=value1,key2=value2". Example value: "key1=value1,key2=value2".
OTEL_EXPORTER_OTLP_TRACES_HEADERS takes precedence over OTEL_EXPORTER_OTLP_HEADERS. OTEL_EXPORTER_OTLP_TRACES_HEADERS takes precedence over OTEL_EXPORTER_OTLP_HEADERS.
@ -60,12 +49,12 @@ OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE takes precedence over OTEL_EXPORTER_OTLP_C
The configuration can be overridden by [WithTLSClientConfig] option. The configuration can be overridden by [WithTLSClientConfig] option.
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE (default: none) - OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE (default: none) -
the filepath to the client certificate/chain trust for clients private key to use in mTLS communication in PEM format. the filepath to the client certificate/chain trust for client's private key to use in mTLS communication in PEM format.
OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE takes precedence over OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE. OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE takes precedence over OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE.
The configuration can be overridden by [WithTLSClientConfig] option. The configuration can be overridden by [WithTLSClientConfig] option.
OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY (default: none) - OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY (default: none) -
the filepath to the clients private key to use in mTLS communication in PEM format. the filepath to the client's private key to use in mTLS communication in PEM format.
OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY takes precedence over OTEL_EXPORTER_OTLP_CLIENT_KEY. OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY takes precedence over OTEL_EXPORTER_OTLP_CLIENT_KEY.
The configuration can be overridden by [WithTLSClientConfig] option. The configuration can be overridden by [WithTLSClientConfig] option.

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlptracehttp // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" package otlptracehttp // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"

View file

@ -2,18 +2,7 @@
// source: internal/shared/otlp/envconfig/envconfig.go.tmpl // source: internal/shared/otlp/envconfig/envconfig.go.tmpl
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig" package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig"
@ -26,6 +15,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/internal/global"
) )
@ -174,12 +164,16 @@ func stringToHeader(value string) map[string]string {
global.Error(errors.New("missing '="), "parse headers", "input", header) global.Error(errors.New("missing '="), "parse headers", "input", header)
continue continue
} }
name, err := url.PathUnescape(n)
if err != nil { trimmedName := strings.TrimSpace(n)
global.Error(err, "escape header key", "key", n)
// Validate the key.
if !isValidHeaderKey(trimmedName) {
global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName)
continue continue
} }
trimmedName := strings.TrimSpace(name)
// Only decode the value.
value, err := url.PathUnescape(v) value, err := url.PathUnescape(v)
if err != nil { if err != nil {
global.Error(err, "escape header value", "value", v) global.Error(err, "escape header value", "value", v)
@ -200,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) {
} }
return cp, nil return cp, nil
} }
func isValidHeaderKey(key string) bool {
if key == "" {
return false
}
for _, c := range key {
if !isTokenChar(c) {
return false
}
}
return true
}
func isTokenChar(c rune) bool {
return c <= unicode.MaxASCII && (unicode.IsLetter(c) ||
unicode.IsDigit(c) ||
c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~')
}

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal" package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal"

View file

@ -2,18 +2,7 @@
// source: internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl // source: internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig" package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig"

View file

@ -2,24 +2,14 @@
// source: internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl // source: internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig" package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig"
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"path" "path"
"strings" "strings"
@ -46,6 +36,10 @@ const (
) )
type ( type (
// HTTPTransportProxyFunc is a function that resolves which URL to use as proxy for a given request.
// This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client.
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
SignalConfig struct { SignalConfig struct {
Endpoint string Endpoint string
Insecure bool Insecure bool
@ -57,6 +51,8 @@ type (
// gRPC configurations // gRPC configurations
GRPCCredentials credentials.TransportCredentials GRPCCredentials credentials.TransportCredentials
Proxy HTTPTransportProxyFunc
} }
Config struct { Config struct {
@ -260,6 +256,9 @@ func NewGRPCOption(fn func(cfg Config) Config) GRPCOption {
// Generic Options // Generic Options
// WithEndpoint configures the trace host and port only; endpoint should
// resemble "example.com" or "localhost:4317". To configure the scheme and path,
// use WithEndpointURL.
func WithEndpoint(endpoint string) GenericOption { func WithEndpoint(endpoint string) GenericOption {
return newGenericOption(func(cfg Config) Config { return newGenericOption(func(cfg Config) Config {
cfg.Traces.Endpoint = endpoint cfg.Traces.Endpoint = endpoint
@ -267,6 +266,8 @@ func WithEndpoint(endpoint string) GenericOption {
}) })
} }
// WithEndpointURL configures the trace scheme, host, port, and path; the
// provided value should resemble "https://example.com:4318/v1/traces".
func WithEndpointURL(v string) GenericOption { func WithEndpointURL(v string) GenericOption {
return newGenericOption(func(cfg Config) Config { return newGenericOption(func(cfg Config) Config {
u, err := url.Parse(v) u, err := url.Parse(v)
@ -343,3 +344,10 @@ func WithTimeout(duration time.Duration) GenericOption {
return cfg return cfg
}) })
} }
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
return newGenericOption(func(cfg Config) Config {
cfg.Traces.Proxy = pf
return cfg
})
}

View file

@ -2,18 +2,7 @@
// source: internal/shared/otlp/otlptrace/otlpconfig/optiontypes.go.tmpl // source: internal/shared/otlp/otlptrace/otlpconfig/optiontypes.go.tmpl
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig" package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig"

View file

@ -2,18 +2,7 @@
// source: internal/shared/otlp/otlptrace/otlpconfig/tls.go.tmpl // source: internal/shared/otlp/otlptrace/otlpconfig/tls.go.tmpl
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig" package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig"

View file

@ -2,18 +2,7 @@
// source: internal/shared/otlp/partialsuccess.go // source: internal/shared/otlp/partialsuccess.go
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal" package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal"

View file

@ -2,18 +2,7 @@
// source: internal/shared/otlp/retry/retry.go.tmpl // source: internal/shared/otlp/retry/retry.go.tmpl
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package retry provides request retry functionality that can perform // Package retry provides request retry functionality that can perform
// configurable exponential backoff for transient errors and honor any // configurable exponential backoff for transient errors and honor any
@ -123,7 +112,7 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
} }
if ctxErr := waitFunc(ctx, delay); ctxErr != nil { if ctxErr := waitFunc(ctx, delay); ctxErr != nil {
return fmt.Errorf("%w: %s", ctxErr, err) return fmt.Errorf("%w: %w", ctxErr, err)
} }
} }
} }

View file

@ -1,21 +1,12 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package otlptracehttp // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" package otlptracehttp // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
import ( import (
"crypto/tls" "crypto/tls"
"net/http"
"net/url"
"time" "time"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig"
@ -26,6 +17,11 @@ import (
// collector. // collector.
type Compression otlpconfig.Compression type Compression otlpconfig.Compression
// HTTPTransportProxyFunc is a function that resolves which URL to use as proxy for a given request.
// This type is compatible with http.Transport.Proxy and can be used to set a custom proxy function
// to the OTLP HTTP client.
type HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
const ( const (
// NoCompression tells the driver to send payloads without // NoCompression tells the driver to send payloads without
// compression. // compression.
@ -60,30 +56,37 @@ func (w wrappedOption) applyHTTPOption(cfg otlpconfig.Config) otlpconfig.Config
return w.ApplyHTTPOption(cfg) return w.ApplyHTTPOption(cfg)
} }
// WithEndpoint sets the target endpoint the Exporter will connect to. // WithEndpoint sets the target endpoint (host and port) the Exporter will
// connect to. The provided endpoint should resemble "example.com:4318" (no
// scheme or path).
// //
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT // If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// environment variable is set, and this option is not passed, that variable // environment variable is set, and this option is not passed, that variable
// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT // value will be used. If both environment variables are set,
// will take precedence. // OTEL_EXPORTER_OTLP_TRACES_ENDPOINT will take precedence. If an environment
// variable is set, and this option is passed, this option will take precedence.
// Note, both environment variables include the full
// scheme and path, while WithEndpoint sets only the host and port.
// //
// If both this option and WithEndpointURL are used, the last used option will // If both this option and WithEndpointURL are used, the last used option will
// take precedence. // take precedence.
// //
// By default, if an environment variable is not set, and this option is not // By default, if an environment variable is not set, and this option is not
// passed, "localhost:4317" will be used. // passed, "localhost:4318" will be used.
// //
// This option has no effect if WithGRPCConn is used. // This option has no effect if WithGRPCConn is used.
func WithEndpoint(endpoint string) Option { func WithEndpoint(endpoint string) Option {
return wrappedOption{otlpconfig.WithEndpoint(endpoint)} return wrappedOption{otlpconfig.WithEndpoint(endpoint)}
} }
// WithEndpointURL sets the target endpoint URL the Exporter will connect to. // WithEndpointURL sets the target endpoint URL (scheme, host, port, path) the
// Exporter will connect to.
// //
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT // If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
// environment variable is set, and this option is not passed, that variable // environment variable is set, and this option is not passed, that variable
// value will be used. If both are set, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT // value will be used. If both environment variables are set,
// will take precedence. // OTEL_EXPORTER_OTLP_TRACES_ENDPOINT will take precedence. If an environment
// variable is set, and this option is passed, this option will take precedence.
// //
// If both this option and WithEndpoint are used, the last used option will // If both this option and WithEndpoint are used, the last used option will
// take precedence. // take precedence.
@ -91,7 +94,7 @@ func WithEndpoint(endpoint string) Option {
// If an invalid URL is provided, the default value will be kept. // If an invalid URL is provided, the default value will be kept.
// //
// By default, if an environment variable is not set, and this option is not // By default, if an environment variable is not set, and this option is not
// passed, "localhost:4317" will be used. // passed, "localhost:4318" will be used.
// //
// This option has no effect if WithGRPCConn is used. // This option has no effect if WithGRPCConn is used.
func WithEndpointURL(u string) Option { func WithEndpointURL(u string) Option {
@ -143,3 +146,10 @@ func WithTimeout(duration time.Duration) Option {
func WithRetry(rc RetryConfig) Option { func WithRetry(rc RetryConfig) Option {
return wrappedOption{otlpconfig.WithRetry(retry.Config(rc))} return wrappedOption{otlpconfig.WithRetry(retry.Config(rc))}
} }
// WithProxy sets the Proxy function the client will use to determine the
// proxy to use for an HTTP request. If this option is not used, the client
// will use [http.ProxyFromEnvironment].
func WithProxy(pf HTTPTransportProxyFunc) Option {
return wrappedOption{otlpconfig.WithProxy(otlpconfig.HTTPTransportProxyFunc(pf))}
}

View file

@ -5,5 +5,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use. // Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
func Version() string { func Version() string {
return "1.26.0" return "1.29.0"
} }

View file

@ -0,0 +1,3 @@
# Prometheus Exporter
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/exporters/prometheus)](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/prometheus)

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus" package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package prometheus provides a Prometheus Exporter that converts // Package prometheus provides a Prometheus Exporter that converts
// OTLP metrics into the Prometheus exposition format and implements // OTLP metrics into the Prometheus exposition format and implements

View file

@ -1,24 +1,14 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus" package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"
import ( import (
"context" "context"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"sort" "slices"
"strings" "strings"
"sync" "sync"
"unicode" "unicode"
@ -43,6 +33,9 @@ const (
scopeInfoMetricName = "otel_scope_info" scopeInfoMetricName = "otel_scope_info"
scopeInfoDescription = "Instrumentation Scope metadata" scopeInfoDescription = "Instrumentation Scope metadata"
traceIDExemplarKey = "trace_id"
spanIDExemplarKey = "span_id"
) )
var ( var (
@ -199,7 +192,7 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
if !c.disableScopeInfo { if !c.disableScopeInfo {
scopeInfo, err := c.scopeInfo(scopeMetrics.Scope) scopeInfo, err := c.scopeInfo(scopeMetrics.Scope)
if err == errScopeInvalid { if errors.Is(err, errScopeInvalid) {
// Do not report the same error multiple times. // Do not report the same error multiple times.
continue continue
} }
@ -249,7 +242,6 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
} }
func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogram metricdata.Histogram[N], m metricdata.Metrics, ks, vs [2]string, name string, resourceKV keyVals) { func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogram metricdata.Histogram[N], m metricdata.Metrics, ks, vs [2]string, name string, resourceKV keyVals) {
// TODO(https://github.com/open-telemetry/opentelemetry-go/issues/3163): support exemplars
for _, dp := range histogram.DataPoints { for _, dp := range histogram.DataPoints {
keys, values := getAttrs(dp.Attributes, ks, vs, resourceKV) keys, values := getAttrs(dp.Attributes, ks, vs, resourceKV)
@ -266,6 +258,7 @@ func addHistogramMetric[N int64 | float64](ch chan<- prometheus.Metric, histogra
otel.Handle(err) otel.Handle(err)
continue continue
} }
m = addExemplars(m, dp.Exemplars)
ch <- m ch <- m
} }
} }
@ -285,6 +278,7 @@ func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata
otel.Handle(err) otel.Handle(err)
continue continue
} }
m = addExemplars(m, dp.Exemplars)
ch <- m ch <- m
} }
} }
@ -324,9 +318,7 @@ func getAttrs(attrs attribute.Set, ks, vs [2]string, resourceKV keyVals) ([]stri
values := make([]string, 0, attrs.Len()) values := make([]string, 0, attrs.Len())
for key, vals := range keysMap { for key, vals := range keysMap {
keys = append(keys, key) keys = append(keys, key)
sort.Slice(vals, func(i, j int) bool { slices.Sort(vals)
return i < j
})
values = append(values, strings.Join(vals, ";")) values = append(values, strings.Join(vals, ";"))
} }
@ -562,3 +554,37 @@ func (c *collector) validateMetrics(name, description string, metricType *dto.Me
return false, "" return false, ""
} }
func addExemplars[N int64 | float64](m prometheus.Metric, exemplars []metricdata.Exemplar[N]) prometheus.Metric {
if len(exemplars) == 0 {
return m
}
promExemplars := make([]prometheus.Exemplar, len(exemplars))
for i, exemplar := range exemplars {
labels := attributesToLabels(exemplar.FilteredAttributes)
// Overwrite any existing trace ID or span ID attributes
labels[traceIDExemplarKey] = hex.EncodeToString(exemplar.TraceID[:])
labels[spanIDExemplarKey] = hex.EncodeToString(exemplar.SpanID[:])
promExemplars[i] = prometheus.Exemplar{
Value: float64(exemplar.Value),
Timestamp: exemplar.Time,
Labels: labels,
}
}
metricWithExemplar, err := prometheus.NewMetricWithExemplars(m, promExemplars...)
if err != nil {
// If there are errors creating the metric with exemplars, just warn
// and return the metric without exemplars.
otel.Handle(err)
return m
}
return metricWithExemplar
}
func attributesToLabels(attrs []attribute.KeyValue) prometheus.Labels {
labels := make(map[string]string)
for _, attr := range attrs {
labels[string(attr.Key)] = attr.Value.Emit()
}
return labels
}

View file

@ -14,33 +14,33 @@ import (
// BoolSliceValue converts a bool slice into an array with same elements as slice. // BoolSliceValue converts a bool slice into an array with same elements as slice.
func BoolSliceValue(v []bool) interface{} { func BoolSliceValue(v []bool) interface{} {
var zero bool var zero bool
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))) cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
copy(cp.Elem().Slice(0, len(v)).Interface().([]bool), v) reflect.Copy(cp, reflect.ValueOf(v))
return cp.Elem().Interface() return cp.Interface()
} }
// Int64SliceValue converts an int64 slice into an array with same elements as slice. // Int64SliceValue converts an int64 slice into an array with same elements as slice.
func Int64SliceValue(v []int64) interface{} { func Int64SliceValue(v []int64) interface{} {
var zero int64 var zero int64
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))) cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
copy(cp.Elem().Slice(0, len(v)).Interface().([]int64), v) reflect.Copy(cp, reflect.ValueOf(v))
return cp.Elem().Interface() return cp.Interface()
} }
// Float64SliceValue converts a float64 slice into an array with same elements as slice. // Float64SliceValue converts a float64 slice into an array with same elements as slice.
func Float64SliceValue(v []float64) interface{} { func Float64SliceValue(v []float64) interface{} {
var zero float64 var zero float64
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))) cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
copy(cp.Elem().Slice(0, len(v)).Interface().([]float64), v) reflect.Copy(cp, reflect.ValueOf(v))
return cp.Elem().Interface() return cp.Interface()
} }
// StringSliceValue converts a string slice into an array with same elements as slice. // StringSliceValue converts a string slice into an array with same elements as slice.
func StringSliceValue(v []string) interface{} { func StringSliceValue(v []string) interface{} {
var zero string var zero string
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))) cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeOf(zero))).Elem()
copy(cp.Elem().Slice(0, len(v)).Interface().([]string), v) reflect.Copy(cp, reflect.ValueOf(v))
return cp.Elem().Interface() return cp.Interface()
} }
// AsBoolSlice converts a bool array into a slice into with same elements as array. // AsBoolSlice converts a bool array into a slice into with same elements as array.

View file

@ -281,6 +281,32 @@ func (i *sfHistogram) Record(ctx context.Context, x float64, opts ...metric.Reco
} }
} }
type sfGauge struct {
embedded.Float64Gauge
name string
opts []metric.Float64GaugeOption
delegate atomic.Value // metric.Float64Gauge
}
var _ metric.Float64Gauge = (*sfGauge)(nil)
func (i *sfGauge) setDelegate(m metric.Meter) {
ctr, err := m.Float64Gauge(i.name, i.opts...)
if err != nil {
GetErrorHandler().Handle(err)
return
}
i.delegate.Store(ctr)
}
func (i *sfGauge) Record(ctx context.Context, x float64, opts ...metric.RecordOption) {
if ctr := i.delegate.Load(); ctr != nil {
ctr.(metric.Float64Gauge).Record(ctx, x, opts...)
}
}
type siCounter struct { type siCounter struct {
embedded.Int64Counter embedded.Int64Counter
@ -358,3 +384,29 @@ func (i *siHistogram) Record(ctx context.Context, x int64, opts ...metric.Record
ctr.(metric.Int64Histogram).Record(ctx, x, opts...) ctr.(metric.Int64Histogram).Record(ctx, x, opts...)
} }
} }
type siGauge struct {
embedded.Int64Gauge
name string
opts []metric.Int64GaugeOption
delegate atomic.Value // metric.Int64Gauge
}
var _ metric.Int64Gauge = (*siGauge)(nil)
func (i *siGauge) setDelegate(m metric.Meter) {
ctr, err := m.Int64Gauge(i.name, i.opts...)
if err != nil {
GetErrorHandler().Handle(err)
return
}
i.delegate.Store(ctr)
}
func (i *siGauge) Record(ctx context.Context, x int64, opts ...metric.RecordOption) {
if ctr := i.delegate.Load(); ctr != nil {
ctr.(metric.Int64Gauge).Record(ctx, x, opts...)
}
}

View file

@ -65,6 +65,7 @@ func (p *meterProvider) Meter(name string, opts ...metric.MeterOption) metric.Me
key := il{ key := il{
name: name, name: name,
version: c.InstrumentationVersion(), version: c.InstrumentationVersion(),
schema: c.SchemaURL(),
} }
if p.meters == nil { if p.meters == nil {
@ -164,6 +165,17 @@ func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOpti
return i, nil return i, nil
} }
func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (metric.Int64Gauge, error) {
if del, ok := m.delegate.Load().(metric.Meter); ok {
return del.Int64Gauge(name, options...)
}
m.mtx.Lock()
defer m.mtx.Unlock()
i := &siGauge{name: name, opts: options}
m.instruments = append(m.instruments, i)
return i, nil
}
func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { func (m *meter) Int64ObservableCounter(name string, options ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) {
if del, ok := m.delegate.Load().(metric.Meter); ok { if del, ok := m.delegate.Load().(metric.Meter); ok {
return del.Int64ObservableCounter(name, options...) return del.Int64ObservableCounter(name, options...)
@ -230,6 +242,17 @@ func (m *meter) Float64Histogram(name string, options ...metric.Float64Histogram
return i, nil return i, nil
} }
func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption) (metric.Float64Gauge, error) {
if del, ok := m.delegate.Load().(metric.Meter); ok {
return del.Float64Gauge(name, options...)
}
m.mtx.Lock()
defer m.mtx.Unlock()
i := &sfGauge{name: name, opts: options}
m.instruments = append(m.instruments, i)
return i, nil
}
func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { func (m *meter) Float64ObservableCounter(name string, options ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) {
if del, ok := m.delegate.Load().(metric.Meter); ok { if del, ok := m.delegate.Load().(metric.Meter); ok {
return del.Float64ObservableCounter(name, options...) return del.Float64ObservableCounter(name, options...)

View file

@ -86,6 +86,7 @@ func (p *tracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T
key := il{ key := il{
name: name, name: name,
version: c.InstrumentationVersion(), version: c.InstrumentationVersion(),
schema: c.SchemaURL(),
} }
if p.tracers == nil { if p.tracers == nil {
@ -101,10 +102,7 @@ func (p *tracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T
return t return t
} }
type il struct { type il struct{ name, version, schema string }
name string
version string
}
// tracer is a placeholder for a trace.Tracer. // tracer is a placeholder for a trace.Tracer.
// //

View file

@ -24,7 +24,8 @@ func Int64ToRaw(i int64) uint64 {
} }
func RawToInt64(r uint64) int64 { func RawToInt64(r uint64) int64 {
return int64(r) // Assumes original was a valid int64 (overflow not checked).
return int64(r) // nolint: gosec
} }
func Float64ToRaw(f float64) uint64 { func Float64ToRaw(f float64) uint64 {
@ -36,9 +37,11 @@ func RawToFloat64(r uint64) float64 {
} }
func RawPtrToFloat64Ptr(r *uint64) *float64 { func RawPtrToFloat64Ptr(r *uint64) *float64 {
return (*float64)(unsafe.Pointer(r)) // Assumes original was a valid *float64 (overflow not checked).
return (*float64)(unsafe.Pointer(r)) // nolint: gosec
} }
func RawPtrToInt64Ptr(r *uint64) *int64 { func RawPtrToInt64Ptr(r *uint64) *int64 {
return (*int64)(unsafe.Pointer(r)) // Assumes original was a valid *int64 (overflow not checked).
return (*int64)(unsafe.Pointer(r)) // nolint: gosec
} }

View file

@ -39,7 +39,7 @@ type Float64ObservableCounter interface {
} }
// Float64ObservableCounterConfig contains options for asynchronous counter // Float64ObservableCounterConfig contains options for asynchronous counter
// instruments that record int64 values. // instruments that record float64 values.
type Float64ObservableCounterConfig struct { type Float64ObservableCounterConfig struct {
description string description string
unit string unit string
@ -97,7 +97,7 @@ type Float64ObservableUpDownCounter interface {
} }
// Float64ObservableUpDownCounterConfig contains options for asynchronous // Float64ObservableUpDownCounterConfig contains options for asynchronous
// counter instruments that record int64 values. // counter instruments that record float64 values.
type Float64ObservableUpDownCounterConfig struct { type Float64ObservableUpDownCounterConfig struct {
description string description string
unit string unit string
@ -154,7 +154,7 @@ type Float64ObservableGauge interface {
} }
// Float64ObservableGaugeConfig contains options for asynchronous counter // Float64ObservableGaugeConfig contains options for asynchronous counter
// instruments that record int64 values. // instruments that record float64 values.
type Float64ObservableGaugeConfig struct { type Float64ObservableGaugeConfig struct {
description string description string
unit string unit string

View file

@ -57,6 +57,23 @@ asynchronous measurement, a Gauge ([Int64ObservableGauge] and
See the [OpenTelemetry documentation] for more information about instruments See the [OpenTelemetry documentation] for more information about instruments
and their intended use. and their intended use.
# Instrument Name
OpenTelemetry defines an [instrument name syntax] that restricts what
instrument names are allowed.
Instrument names should ...
- Not be empty.
- Have an alphabetic character as their first letter.
- Have any letter after the first be an alphanumeric character, _, .,
-, or /.
- Have a maximum length of 255 letters.
To ensure compatibility with observability platforms, all instruments created
need to conform to this syntax. Not all implementations of the API will validate
these names, it is the callers responsibility to ensure compliance.
# Measurements # Measurements
Measurements are made by recording values and information about the values with Measurements are made by recording values and information about the values with
@ -153,6 +170,7 @@ It is strongly recommended that authors only embed
That implementation is the only one OpenTelemetry authors can guarantee will That implementation is the only one OpenTelemetry authors can guarantee will
fully implement all the API interfaces when a user updates their API. fully implement all the API interfaces when a user updates their API.
[instrument name syntax]: https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument-name-syntax
[OpenTelemetry documentation]: https://opentelemetry.io/docs/concepts/signals/metrics/ [OpenTelemetry documentation]: https://opentelemetry.io/docs/concepts/signals/metrics/
[GetMeterProvider]: https://pkg.go.dev/go.opentelemetry.io/otel#GetMeterProvider [GetMeterProvider]: https://pkg.go.dev/go.opentelemetry.io/otel#GetMeterProvider
*/ */

View file

@ -102,6 +102,16 @@ type Float64Counter interface{ float64Counter() }
// the API package). // the API package).
type Float64Histogram interface{ float64Histogram() } type Float64Histogram interface{ float64Histogram() }
// Float64Gauge is embedded in [go.opentelemetry.io/otel/metric.Float64Gauge].
//
// Embed this interface in your implementation of the
// [go.opentelemetry.io/otel/metric.Float64Gauge] if you want users to
// experience a compilation error, signaling they need to update to your latest
// implementation, when the [go.opentelemetry.io/otel/metric.Float64Gauge]
// interface is extended (which is something that can happen without a major
// version bump of the API package).
type Float64Gauge interface{ float64Gauge() }
// Float64ObservableCounter is embedded in // Float64ObservableCounter is embedded in
// [go.opentelemetry.io/otel/metric.Float64ObservableCounter]. // [go.opentelemetry.io/otel/metric.Float64ObservableCounter].
// //
@ -174,6 +184,16 @@ type Int64Counter interface{ int64Counter() }
// the API package). // the API package).
type Int64Histogram interface{ int64Histogram() } type Int64Histogram interface{ int64Histogram() }
// Int64Gauge is embedded in [go.opentelemetry.io/otel/metric.Int64Gauge].
//
// Embed this interface in your implementation of the
// [go.opentelemetry.io/otel/metric.Int64Gauge] if you want users to experience
// a compilation error, signaling they need to update to your latest
// implementation, when the [go.opentelemetry.io/otel/metric.Int64Gauge]
// interface is extended (which is something that can happen without a major
// version bump of the API package).
type Int64Gauge interface{ int64Gauge() }
// Int64ObservableCounter is embedded in // Int64ObservableCounter is embedded in
// [go.opentelemetry.io/otel/metric.Int64ObservableCounter]. // [go.opentelemetry.io/otel/metric.Int64ObservableCounter].
// //

View file

@ -16,6 +16,7 @@ type InstrumentOption interface {
Int64CounterOption Int64CounterOption
Int64UpDownCounterOption Int64UpDownCounterOption
Int64HistogramOption Int64HistogramOption
Int64GaugeOption
Int64ObservableCounterOption Int64ObservableCounterOption
Int64ObservableUpDownCounterOption Int64ObservableUpDownCounterOption
Int64ObservableGaugeOption Int64ObservableGaugeOption
@ -23,6 +24,7 @@ type InstrumentOption interface {
Float64CounterOption Float64CounterOption
Float64UpDownCounterOption Float64UpDownCounterOption
Float64HistogramOption Float64HistogramOption
Float64GaugeOption
Float64ObservableCounterOption Float64ObservableCounterOption
Float64ObservableUpDownCounterOption Float64ObservableUpDownCounterOption
Float64ObservableGaugeOption Float64ObservableGaugeOption
@ -51,6 +53,11 @@ func (o descOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64Histogra
return c return c
} }
func (o descOpt) applyFloat64Gauge(c Float64GaugeConfig) Float64GaugeConfig {
c.description = string(o)
return c
}
func (o descOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig { func (o descOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig {
c.description = string(o) c.description = string(o)
return c return c
@ -81,6 +88,11 @@ func (o descOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfi
return c return c
} }
func (o descOpt) applyInt64Gauge(c Int64GaugeConfig) Int64GaugeConfig {
c.description = string(o)
return c
}
func (o descOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig { func (o descOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig {
c.description = string(o) c.description = string(o)
return c return c
@ -116,6 +128,11 @@ func (o unitOpt) applyFloat64Histogram(c Float64HistogramConfig) Float64Histogra
return c return c
} }
func (o unitOpt) applyFloat64Gauge(c Float64GaugeConfig) Float64GaugeConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig { func (o unitOpt) applyFloat64ObservableCounter(c Float64ObservableCounterConfig) Float64ObservableCounterConfig {
c.unit = string(o) c.unit = string(o)
return c return c
@ -146,6 +163,11 @@ func (o unitOpt) applyInt64Histogram(c Int64HistogramConfig) Int64HistogramConfi
return c return c
} }
func (o unitOpt) applyInt64Gauge(c Int64GaugeConfig) Int64GaugeConfig {
c.unit = string(o)
return c
}
func (o unitOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig { func (o unitOpt) applyInt64ObservableCounter(c Int64ObservableCounterConfig) Int64ObservableCounterConfig {
c.unit = string(o) c.unit = string(o)
return c return c

View file

@ -47,17 +47,41 @@ type Meter interface {
// Int64Counter returns a new Int64Counter instrument identified by name // Int64Counter returns a new Int64Counter instrument identified by name
// and configured with options. The instrument is used to synchronously // and configured with options. The instrument is used to synchronously
// record increasing int64 measurements during a computational operation. // record increasing int64 measurements during a computational operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Int64Counter(name string, options ...Int64CounterOption) (Int64Counter, error) Int64Counter(name string, options ...Int64CounterOption) (Int64Counter, error)
// Int64UpDownCounter returns a new Int64UpDownCounter instrument // Int64UpDownCounter returns a new Int64UpDownCounter instrument
// identified by name and configured with options. The instrument is used // identified by name and configured with options. The instrument is used
// to synchronously record int64 measurements during a computational // to synchronously record int64 measurements during a computational
// operation. // operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Int64UpDownCounter(name string, options ...Int64UpDownCounterOption) (Int64UpDownCounter, error) Int64UpDownCounter(name string, options ...Int64UpDownCounterOption) (Int64UpDownCounter, error)
// Int64Histogram returns a new Int64Histogram instrument identified by // Int64Histogram returns a new Int64Histogram instrument identified by
// name and configured with options. The instrument is used to // name and configured with options. The instrument is used to
// synchronously record the distribution of int64 measurements during a // synchronously record the distribution of int64 measurements during a
// computational operation. // computational operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Int64Histogram(name string, options ...Int64HistogramOption) (Int64Histogram, error) Int64Histogram(name string, options ...Int64HistogramOption) (Int64Histogram, error)
// Int64Gauge returns a new Int64Gauge instrument identified by name and
// configured with options. The instrument is used to synchronously record
// instantaneous int64 measurements during a computational operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Int64Gauge(name string, options ...Int64GaugeOption) (Int64Gauge, error)
// Int64ObservableCounter returns a new Int64ObservableCounter identified // Int64ObservableCounter returns a new Int64ObservableCounter identified
// by name and configured with options. The instrument is used to // by name and configured with options. The instrument is used to
// asynchronously record increasing int64 measurements once per a // asynchronously record increasing int64 measurements once per a
@ -67,7 +91,12 @@ type Meter interface {
// the WithInt64Callback option to register the callback here, or use the // the WithInt64Callback option to register the callback here, or use the
// RegisterCallback method of this Meter to register one later. See the // RegisterCallback method of this Meter to register one later. See the
// Measurements section of the package documentation for more information. // Measurements section of the package documentation for more information.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Int64ObservableCounter(name string, options ...Int64ObservableCounterOption) (Int64ObservableCounter, error) Int64ObservableCounter(name string, options ...Int64ObservableCounterOption) (Int64ObservableCounter, error)
// Int64ObservableUpDownCounter returns a new Int64ObservableUpDownCounter // Int64ObservableUpDownCounter returns a new Int64ObservableUpDownCounter
// instrument identified by name and configured with options. The // instrument identified by name and configured with options. The
// instrument is used to asynchronously record int64 measurements once per // instrument is used to asynchronously record int64 measurements once per
@ -77,7 +106,12 @@ type Meter interface {
// the WithInt64Callback option to register the callback here, or use the // the WithInt64Callback option to register the callback here, or use the
// RegisterCallback method of this Meter to register one later. See the // RegisterCallback method of this Meter to register one later. See the
// Measurements section of the package documentation for more information. // Measurements section of the package documentation for more information.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Int64ObservableUpDownCounter(name string, options ...Int64ObservableUpDownCounterOption) (Int64ObservableUpDownCounter, error) Int64ObservableUpDownCounter(name string, options ...Int64ObservableUpDownCounterOption) (Int64ObservableUpDownCounter, error)
// Int64ObservableGauge returns a new Int64ObservableGauge instrument // Int64ObservableGauge returns a new Int64ObservableGauge instrument
// identified by name and configured with options. The instrument is used // identified by name and configured with options. The instrument is used
// to asynchronously record instantaneous int64 measurements once per a // to asynchronously record instantaneous int64 measurements once per a
@ -87,23 +121,51 @@ type Meter interface {
// the WithInt64Callback option to register the callback here, or use the // the WithInt64Callback option to register the callback here, or use the
// RegisterCallback method of this Meter to register one later. See the // RegisterCallback method of this Meter to register one later. See the
// Measurements section of the package documentation for more information. // Measurements section of the package documentation for more information.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Int64ObservableGauge(name string, options ...Int64ObservableGaugeOption) (Int64ObservableGauge, error) Int64ObservableGauge(name string, options ...Int64ObservableGaugeOption) (Int64ObservableGauge, error)
// Float64Counter returns a new Float64Counter instrument identified by // Float64Counter returns a new Float64Counter instrument identified by
// name and configured with options. The instrument is used to // name and configured with options. The instrument is used to
// synchronously record increasing float64 measurements during a // synchronously record increasing float64 measurements during a
// computational operation. // computational operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Float64Counter(name string, options ...Float64CounterOption) (Float64Counter, error) Float64Counter(name string, options ...Float64CounterOption) (Float64Counter, error)
// Float64UpDownCounter returns a new Float64UpDownCounter instrument // Float64UpDownCounter returns a new Float64UpDownCounter instrument
// identified by name and configured with options. The instrument is used // identified by name and configured with options. The instrument is used
// to synchronously record float64 measurements during a computational // to synchronously record float64 measurements during a computational
// operation. // operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Float64UpDownCounter(name string, options ...Float64UpDownCounterOption) (Float64UpDownCounter, error) Float64UpDownCounter(name string, options ...Float64UpDownCounterOption) (Float64UpDownCounter, error)
// Float64Histogram returns a new Float64Histogram instrument identified by // Float64Histogram returns a new Float64Histogram instrument identified by
// name and configured with options. The instrument is used to // name and configured with options. The instrument is used to
// synchronously record the distribution of float64 measurements during a // synchronously record the distribution of float64 measurements during a
// computational operation. // computational operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Float64Histogram(name string, options ...Float64HistogramOption) (Float64Histogram, error) Float64Histogram(name string, options ...Float64HistogramOption) (Float64Histogram, error)
// Float64Gauge returns a new Float64Gauge instrument identified by name and
// configured with options. The instrument is used to synchronously record
// instantaneous float64 measurements during a computational operation.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Float64Gauge(name string, options ...Float64GaugeOption) (Float64Gauge, error)
// Float64ObservableCounter returns a new Float64ObservableCounter // Float64ObservableCounter returns a new Float64ObservableCounter
// instrument identified by name and configured with options. The // instrument identified by name and configured with options. The
// instrument is used to asynchronously record increasing float64 // instrument is used to asynchronously record increasing float64
@ -113,7 +175,12 @@ type Meter interface {
// the WithFloat64Callback option to register the callback here, or use the // the WithFloat64Callback option to register the callback here, or use the
// RegisterCallback method of this Meter to register one later. See the // RegisterCallback method of this Meter to register one later. See the
// Measurements section of the package documentation for more information. // Measurements section of the package documentation for more information.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Float64ObservableCounter(name string, options ...Float64ObservableCounterOption) (Float64ObservableCounter, error) Float64ObservableCounter(name string, options ...Float64ObservableCounterOption) (Float64ObservableCounter, error)
// Float64ObservableUpDownCounter returns a new // Float64ObservableUpDownCounter returns a new
// Float64ObservableUpDownCounter instrument identified by name and // Float64ObservableUpDownCounter instrument identified by name and
// configured with options. The instrument is used to asynchronously record // configured with options. The instrument is used to asynchronously record
@ -123,7 +190,12 @@ type Meter interface {
// the WithFloat64Callback option to register the callback here, or use the // the WithFloat64Callback option to register the callback here, or use the
// RegisterCallback method of this Meter to register one later. See the // RegisterCallback method of this Meter to register one later. See the
// Measurements section of the package documentation for more information. // Measurements section of the package documentation for more information.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Float64ObservableUpDownCounter(name string, options ...Float64ObservableUpDownCounterOption) (Float64ObservableUpDownCounter, error) Float64ObservableUpDownCounter(name string, options ...Float64ObservableUpDownCounterOption) (Float64ObservableUpDownCounter, error)
// Float64ObservableGauge returns a new Float64ObservableGauge instrument // Float64ObservableGauge returns a new Float64ObservableGauge instrument
// identified by name and configured with options. The instrument is used // identified by name and configured with options. The instrument is used
// to asynchronously record instantaneous float64 measurements once per a // to asynchronously record instantaneous float64 measurements once per a
@ -133,6 +205,10 @@ type Meter interface {
// the WithFloat64Callback option to register the callback here, or use the // the WithFloat64Callback option to register the callback here, or use the
// RegisterCallback method of this Meter to register one later. See the // RegisterCallback method of this Meter to register one later. See the
// Measurements section of the package documentation for more information. // Measurements section of the package documentation for more information.
//
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
Float64ObservableGauge(name string, options ...Float64ObservableGaugeOption) (Float64ObservableGauge, error) Float64ObservableGauge(name string, options ...Float64ObservableGaugeOption) (Float64ObservableGauge, error)
// RegisterCallback registers f to be called during the collection of a // RegisterCallback registers f to be called during the collection of a
@ -178,6 +254,7 @@ type Observer interface {
// ObserveFloat64 records the float64 value for obsrv. // ObserveFloat64 records the float64 value for obsrv.
ObserveFloat64(obsrv Float64Observable, value float64, opts ...ObserveOption) ObserveFloat64(obsrv Float64Observable, value float64, opts ...ObserveOption)
// ObserveInt64 records the int64 value for obsrv. // ObserveInt64 records the int64 value for obsrv.
ObserveInt64(obsrv Int64Observable, value int64, opts ...ObserveOption) ObserveInt64(obsrv Int64Observable, value int64, opts ...ObserveOption)
} }

View file

@ -32,6 +32,8 @@ var (
_ metric.Float64UpDownCounter = Float64UpDownCounter{} _ metric.Float64UpDownCounter = Float64UpDownCounter{}
_ metric.Int64Histogram = Int64Histogram{} _ metric.Int64Histogram = Int64Histogram{}
_ metric.Float64Histogram = Float64Histogram{} _ metric.Float64Histogram = Float64Histogram{}
_ metric.Int64Gauge = Int64Gauge{}
_ metric.Float64Gauge = Float64Gauge{}
_ metric.Int64ObservableCounter = Int64ObservableCounter{} _ metric.Int64ObservableCounter = Int64ObservableCounter{}
_ metric.Float64ObservableCounter = Float64ObservableCounter{} _ metric.Float64ObservableCounter = Float64ObservableCounter{}
_ metric.Int64ObservableGauge = Int64ObservableGauge{} _ metric.Int64ObservableGauge = Int64ObservableGauge{}
@ -76,6 +78,12 @@ func (Meter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int6
return Int64Histogram{}, nil return Int64Histogram{}, nil
} }
// Int64Gauge returns a Gauge used to record int64 measurements that
// produces no telemetry.
func (Meter) Int64Gauge(string, ...metric.Int64GaugeOption) (metric.Int64Gauge, error) {
return Int64Gauge{}, nil
}
// Int64ObservableCounter returns an ObservableCounter used to record int64 // Int64ObservableCounter returns an ObservableCounter used to record int64
// measurements that produces no telemetry. // measurements that produces no telemetry.
func (Meter) Int64ObservableCounter(string, ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) { func (Meter) Int64ObservableCounter(string, ...metric.Int64ObservableCounterOption) (metric.Int64ObservableCounter, error) {
@ -112,6 +120,12 @@ func (Meter) Float64Histogram(string, ...metric.Float64HistogramOption) (metric.
return Float64Histogram{}, nil return Float64Histogram{}, nil
} }
// Float64Gauge returns a Gauge used to record float64 measurements that
// produces no telemetry.
func (Meter) Float64Gauge(string, ...metric.Float64GaugeOption) (metric.Float64Gauge, error) {
return Float64Gauge{}, nil
}
// Float64ObservableCounter returns an ObservableCounter used to record int64 // Float64ObservableCounter returns an ObservableCounter used to record int64
// measurements that produces no telemetry. // measurements that produces no telemetry.
func (Meter) Float64ObservableCounter(string, ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) { func (Meter) Float64ObservableCounter(string, ...metric.Float64ObservableCounterOption) (metric.Float64ObservableCounter, error) {
@ -197,6 +211,20 @@ type Float64Histogram struct{ embedded.Float64Histogram }
// Record performs no operation. // Record performs no operation.
func (Float64Histogram) Record(context.Context, float64, ...metric.RecordOption) {} func (Float64Histogram) Record(context.Context, float64, ...metric.RecordOption) {}
// Int64Gauge is an OpenTelemetry Gauge used to record instantaneous int64
// measurements. It produces no telemetry.
type Int64Gauge struct{ embedded.Int64Gauge }
// Record performs no operation.
func (Int64Gauge) Record(context.Context, int64, ...metric.RecordOption) {}
// Float64Gauge is an OpenTelemetry Gauge used to record instantaneous float64
// measurements. It produces no telemetry.
type Float64Gauge struct{ embedded.Float64Gauge }
// Record performs no operation.
func (Float64Gauge) Record(context.Context, float64, ...metric.RecordOption) {}
// Int64ObservableCounter is an OpenTelemetry ObservableCounter used to record // Int64ObservableCounter is an OpenTelemetry ObservableCounter used to record
// int64 measurements. It produces no telemetry. // int64 measurements. It produces no telemetry.
type Int64ObservableCounter struct { type Int64ObservableCounter struct {

View file

@ -28,7 +28,7 @@ type Float64Counter interface {
} }
// Float64CounterConfig contains options for synchronous counter instruments that // Float64CounterConfig contains options for synchronous counter instruments that
// record int64 values. // record float64 values.
type Float64CounterConfig struct { type Float64CounterConfig struct {
description string description string
unit string unit string
@ -81,7 +81,7 @@ type Float64UpDownCounter interface {
} }
// Float64UpDownCounterConfig contains options for synchronous counter // Float64UpDownCounterConfig contains options for synchronous counter
// instruments that record int64 values. // instruments that record float64 values.
type Float64UpDownCounterConfig struct { type Float64UpDownCounterConfig struct {
description string description string
unit string unit string
@ -133,8 +133,8 @@ type Float64Histogram interface {
Record(ctx context.Context, incr float64, options ...RecordOption) Record(ctx context.Context, incr float64, options ...RecordOption)
} }
// Float64HistogramConfig contains options for synchronous counter instruments // Float64HistogramConfig contains options for synchronous histogram
// that record int64 values. // instruments that record float64 values.
type Float64HistogramConfig struct { type Float64HistogramConfig struct {
description string description string
unit string unit string
@ -172,3 +172,55 @@ func (c Float64HistogramConfig) ExplicitBucketBoundaries() []float64 {
type Float64HistogramOption interface { type Float64HistogramOption interface {
applyFloat64Histogram(Float64HistogramConfig) Float64HistogramConfig applyFloat64Histogram(Float64HistogramConfig) Float64HistogramConfig
} }
// Float64Gauge is an instrument that records instantaneous float64 values.
//
// Warning: Methods may be added to this interface in minor releases. See
// package documentation on API implementation for information on how to set
// default behavior for unimplemented methods.
type Float64Gauge interface {
// Users of the interface can ignore this. This embedded type is only used
// by implementations of this interface. See the "API Implementations"
// section of the package documentation for more information.
embedded.Float64Gauge
// Record records the instantaneous value.
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Record(ctx context.Context, value float64, options ...RecordOption)
}
// Float64GaugeConfig contains options for synchronous gauge instruments that
// record float64 values.
type Float64GaugeConfig struct {
description string
unit string
}
// NewFloat64GaugeConfig returns a new [Float64GaugeConfig] with all opts
// applied.
func NewFloat64GaugeConfig(opts ...Float64GaugeOption) Float64GaugeConfig {
var config Float64GaugeConfig
for _, o := range opts {
config = o.applyFloat64Gauge(config)
}
return config
}
// Description returns the configured description.
func (c Float64GaugeConfig) Description() string {
return c.description
}
// Unit returns the configured unit.
func (c Float64GaugeConfig) Unit() string {
return c.unit
}
// Float64GaugeOption applies options to a [Float64GaugeConfig]. See
// [InstrumentOption] for other options that can be used as a
// Float64GaugeOption.
type Float64GaugeOption interface {
applyFloat64Gauge(Float64GaugeConfig) Float64GaugeConfig
}

View file

@ -133,7 +133,7 @@ type Int64Histogram interface {
Record(ctx context.Context, incr int64, options ...RecordOption) Record(ctx context.Context, incr int64, options ...RecordOption)
} }
// Int64HistogramConfig contains options for synchronous counter instruments // Int64HistogramConfig contains options for synchronous histogram instruments
// that record int64 values. // that record int64 values.
type Int64HistogramConfig struct { type Int64HistogramConfig struct {
description string description string
@ -172,3 +172,55 @@ func (c Int64HistogramConfig) ExplicitBucketBoundaries() []float64 {
type Int64HistogramOption interface { type Int64HistogramOption interface {
applyInt64Histogram(Int64HistogramConfig) Int64HistogramConfig applyInt64Histogram(Int64HistogramConfig) Int64HistogramConfig
} }
// Int64Gauge is an instrument that records instantaneous int64 values.
//
// Warning: Methods may be added to this interface in minor releases. See
// package documentation on API implementation for information on how to set
// default behavior for unimplemented methods.
type Int64Gauge interface {
// Users of the interface can ignore this. This embedded type is only used
// by implementations of this interface. See the "API Implementations"
// section of the package documentation for more information.
embedded.Int64Gauge
// Record records the instantaneous value.
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
Record(ctx context.Context, value int64, options ...RecordOption)
}
// Int64GaugeConfig contains options for synchronous gauge instruments that
// record int64 values.
type Int64GaugeConfig struct {
description string
unit string
}
// NewInt64GaugeConfig returns a new [Int64GaugeConfig] with all opts
// applied.
func NewInt64GaugeConfig(opts ...Int64GaugeOption) Int64GaugeConfig {
var config Int64GaugeConfig
for _, o := range opts {
config = o.applyInt64Gauge(config)
}
return config
}
// Description returns the configured description.
func (c Int64GaugeConfig) Description() string {
return c.description
}
// Unit returns the configured unit.
func (c Int64GaugeConfig) Unit() string {
return c.unit
}
// Int64GaugeOption applies options to a [Int64GaugeConfig]. See
// [InstrumentOption] for other options that can be used as a
// Int64GaugeOption.
type Int64GaugeOption interface {
applyInt64Gauge(Int64GaugeConfig) Int64GaugeConfig
}

24
vendor/go.opentelemetry.io/otel/renovate.json generated vendored Normal file
View file

@ -0,0 +1,24 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"ignorePaths": [],
"labels": ["Skip Changelog", "dependencies"],
"postUpdateOptions" : [
"gomodTidy"
],
"packageRules": [
{
"matchManagers": ["gomod"],
"matchDepTypes": ["indirect"],
"enabled": true
},
{
"matchFileNames": ["internal/tools/**"],
"matchManagers": ["gomod"],
"matchDepTypes": ["indirect"],
"enabled": false
}
]
}

View file

@ -1 +1 @@
codespell==2.2.6 codespell==2.3.0

View file

@ -4,5 +4,6 @@
package instrumentation // import "go.opentelemetry.io/otel/sdk/instrumentation" package instrumentation // import "go.opentelemetry.io/otel/sdk/instrumentation"
// Library represents the instrumentation library. // Library represents the instrumentation library.
// Deprecated: please use Scope instead. //
// Deprecated: use [Scope] instead.
type Library = Scope type Library = Scope

View file

@ -22,7 +22,7 @@ const (
BatchSpanProcessorMaxQueueSizeKey = "OTEL_BSP_MAX_QUEUE_SIZE" BatchSpanProcessorMaxQueueSizeKey = "OTEL_BSP_MAX_QUEUE_SIZE"
// BatchSpanProcessorMaxExportBatchSizeKey is the maximum batch size (i.e. // BatchSpanProcessorMaxExportBatchSizeKey is the maximum batch size (i.e.
// 512). Note: it must be less than or equal to // 512). Note: it must be less than or equal to
// EnvBatchSpanProcessorMaxQueueSize. // BatchSpanProcessorMaxQueueSize.
BatchSpanProcessorMaxExportBatchSizeKey = "OTEL_BSP_MAX_EXPORT_BATCH_SIZE" BatchSpanProcessorMaxExportBatchSizeKey = "OTEL_BSP_MAX_EXPORT_BATCH_SIZE"
// AttributeValueLengthKey is the maximum allowed attribute value size. // AttributeValueLengthKey is the maximum allowed attribute value size.

View file

@ -1,18 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/otel/sdk/internal"
//go:generate gotmpl --body=../../internal/shared/matchers/expectation.go.tmpl "--data={}" --out=matchers/expectation.go
//go:generate gotmpl --body=../../internal/shared/matchers/expecter.go.tmpl "--data={}" --out=matchers/expecter.go
//go:generate gotmpl --body=../../internal/shared/matchers/temporal_matcher.go.tmpl "--data={}" --out=matchers/temporal_matcher.go
//go:generate gotmpl --body=../../internal/shared/internaltest/alignment.go.tmpl "--data={}" --out=internaltest/alignment.go
//go:generate gotmpl --body=../../internal/shared/internaltest/env.go.tmpl "--data={}" --out=internaltest/env.go
//go:generate gotmpl --body=../../internal/shared/internaltest/env_test.go.tmpl "--data={}" --out=internaltest/env_test.go
//go:generate gotmpl --body=../../internal/shared/internaltest/errors.go.tmpl "--data={}" --out=internaltest/errors.go
//go:generate gotmpl --body=../../internal/shared/internaltest/harness.go.tmpl "--data={\"matchersImportPath\": \"go.opentelemetry.io/otel/sdk/internal/matchers\"}" --out=internaltest/harness.go
//go:generate gotmpl --body=../../internal/shared/internaltest/text_map_carrier.go.tmpl "--data={}" --out=internaltest/text_map_carrier.go
//go:generate gotmpl --body=../../internal/shared/internaltest/text_map_carrier_test.go.tmpl "--data={}" --out=internaltest/text_map_carrier_test.go
//go:generate gotmpl --body=../../internal/shared/internaltest/text_map_propagator.go.tmpl "--data={}" --out=internaltest/text_map_propagator.go
//go:generate gotmpl --body=../../internal/shared/internaltest/text_map_propagator_test.go.tmpl "--data={}" --out=internaltest/text_map_propagator_test.go

View file

@ -1,17 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/otel/sdk/internal"
import "time"
// MonotonicEndTime returns the end time at present
// but offset from start, monotonically.
//
// The monotonic clock is used in subtractions hence
// the duration since start added back to start gives
// end as a monotonic time.
// See https://golang.org/pkg/time/#hdr-Monotonic_Clocks
func MonotonicEndTime(start time.Time) time.Time {
return start.Add(time.Since(start))
}

View file

@ -0,0 +1,46 @@
# Experimental Features
The SDK contains features that have not yet stabilized in the OpenTelemetry specification.
These features are added to the OpenTelemetry Go SDK prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
These feature may change in backwards incompatible ways as feedback is applied.
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
## Features
- [Resource](#resource)
### Resource
[OpenTelemetry resource semantic conventions] include many attribute definitions that are defined as experimental.
To have experimental semantic conventions be added by [resource detectors] set the `OTEL_GO_X_RESOURCE` environment variable.
The value set must be the case-insensitive string of `"true"` to enable the feature.
All other values are ignored.
<!-- TODO: document what attributes are added by which detector -->
[OpenTelemetry resource semantic conventions]: https://opentelemetry.io/docs/specs/semconv/resource/
[resource detectors]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/resource#Detector
#### Examples
Enable experimental resource semantic conventions.
```console
export OTEL_GO_X_RESOURCE=true
```
Disable experimental resource semantic conventions.
```console
unset OTEL_GO_X_RESOURCE
```
## 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.

66
vendor/go.opentelemetry.io/otel/sdk/internal/x/x.go generated vendored Normal file
View file

@ -0,0 +1,66 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package x contains support for OTel SDK experimental features.
//
// This package should only be used for features defined in the specification.
// It should not be used for experiments or new project ideas.
package x // import "go.opentelemetry.io/otel/sdk/internal/x"
import (
"os"
"strings"
)
// Resource is an experimental feature flag that defines if resource detectors
// should be included experimental semantic conventions.
//
// To enable this feature set the OTEL_GO_X_RESOURCE environment variable
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
// will also enable this).
var Resource = newFeature("RESOURCE", func(v string) (string, bool) {
if strings.ToLower(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
parse func(v string) (T, bool)
}
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
const envKeyRoot = "OTEL_GO_X_"
return Feature[T]{
key: envKeyRoot + suffix,
parse: parse,
}
}
// Key returns the environment variable key that needs to be set to enable the
// feature.
func (f Feature[T]) Key() string { return f.key }
// 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.
vRaw := os.Getenv(f.key)
if vRaw == "" {
return v, ok
}
return f.parse(vRaw)
}
// Enabled returns if the feature is enabled.
func (f Feature[T]) Enabled() bool {
_, ok := f.Lookup()
return ok
}

3
vendor/go.opentelemetry.io/otel/sdk/metric/README.md generated vendored Normal file
View file

@ -0,0 +1,3 @@
# Metric SDK
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/sdk/metric)](https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric)

View file

@ -1,22 +1,12 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric" package metric // import "go.opentelemetry.io/otel/sdk/metric"
import ( import (
"errors" "errors"
"fmt" "fmt"
"slices"
) )
// errAgg is wrapped by misconfigured aggregations. // errAgg is wrapped by misconfigured aggregations.
@ -141,10 +131,8 @@ func (h AggregationExplicitBucketHistogram) err() error {
// copy returns a deep copy of h. // copy returns a deep copy of h.
func (h AggregationExplicitBucketHistogram) copy() Aggregation { func (h AggregationExplicitBucketHistogram) copy() Aggregation {
b := make([]float64, len(h.Boundaries))
copy(b, h.Boundaries)
return AggregationExplicitBucketHistogram{ return AggregationExplicitBucketHistogram{
Boundaries: b, Boundaries: slices.Clone(h.Boundaries),
NoMinMax: h.NoMinMax, NoMinMax: h.NoMinMax,
} }
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric" package metric // import "go.opentelemetry.io/otel/sdk/metric"

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric" package metric // import "go.opentelemetry.io/otel/sdk/metric"
@ -133,7 +122,7 @@ func WithReader(r Reader) Option {
}) })
} }
// WithView associates views a MeterProvider. // WithView associates views with a MeterProvider.
// //
// Views are appended to existing ones in a MeterProvider if this option is // Views are appended to existing ones in a MeterProvider if this option is
// used multiple times. // used multiple times.

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package metric provides an implementation of the OpenTelemetry metrics SDK. // Package metric provides an implementation of the OpenTelemetry metrics SDK.
// //
@ -42,6 +31,14 @@
// is being run on. That way when multiple instances of the code are collected // is being run on. That way when multiple instances of the code are collected
// at a single endpoint their origin is decipherable. // at a single endpoint their origin is decipherable.
// //
// To avoid leaking memory, the SDK returns the same instrument for calls to
// create new instruments with the same Name, Unit, and Description.
// Importantly, callbacks provided using metric.WithFloat64Callback or
// metric.WithInt64Callback will only apply for the first instrument created
// with a given Name, Unit, and Description. Instead, use
// Meter.RegisterCallback and Registration.Unregister to add and remove
// callbacks without leaking memory.
//
// See [go.opentelemetry.io/otel/metric] for more information about // See [go.opentelemetry.io/otel/metric] for more information about
// the metric API. // the metric API.
// //

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric" package metric // import "go.opentelemetry.io/otel/sdk/metric"

View file

@ -1,22 +1,12 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric" package metric // import "go.opentelemetry.io/otel/sdk/metric"
import ( import (
"os" "os"
"runtime" "runtime"
"slices"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar" "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/internal/x" "go.opentelemetry.io/otel/sdk/metric/internal/x"
@ -29,22 +19,35 @@ import (
// Note: This will only return non-nil values when the experimental exemplar // Note: This will only return non-nil values when the experimental exemplar
// feature is enabled and the OTEL_METRICS_EXEMPLAR_FILTER environment variable // feature is enabled and the OTEL_METRICS_EXEMPLAR_FILTER environment variable
// is not set to always_off. // is not set to always_off.
func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.Reservoir[N] { func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.FilteredReservoir[N] {
if !x.Exemplars.Enabled() { if !x.Exemplars.Enabled() {
return nil return nil
} }
// https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/configuration/sdk-environment-variables.md#exemplar
const filterEnvKey = "OTEL_METRICS_EXEMPLAR_FILTER"
var filter exemplar.Filter
switch os.Getenv(filterEnvKey) {
case "always_on":
filter = exemplar.AlwaysOnFilter
case "always_off":
return exemplar.Drop
case "trace_based":
fallthrough
default:
filter = exemplar.SampledFilter
}
// https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/metrics/sdk.md#exemplar-defaults // https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/metrics/sdk.md#exemplar-defaults
resF := func() func() exemplar.Reservoir[N] {
// Explicit bucket histogram aggregation with more than 1 bucket will // Explicit bucket histogram aggregation with more than 1 bucket will
// use AlignedHistogramBucketExemplarReservoir. // use AlignedHistogramBucketExemplarReservoir.
a, ok := agg.(AggregationExplicitBucketHistogram) a, ok := agg.(AggregationExplicitBucketHistogram)
if ok && len(a.Boundaries) > 0 { if ok && len(a.Boundaries) > 0 {
cp := make([]float64, len(a.Boundaries)) cp := slices.Clone(a.Boundaries)
copy(cp, a.Boundaries) return func() exemplar.FilteredReservoir[N] {
return func() exemplar.Reservoir[N] {
bounds := cp bounds := cp
return exemplar.Histogram[N](bounds) return exemplar.NewFilteredReservoir[N](filter, exemplar.Histogram(bounds))
} }
} }
@ -72,25 +75,7 @@ func reservoirFunc[N int64 | float64](agg Aggregation) func() exemplar.Reservoir
} }
} }
return func() exemplar.Reservoir[N] { return func() exemplar.FilteredReservoir[N] {
return exemplar.FixedSize[N](n) return exemplar.NewFilteredReservoir[N](filter, exemplar.FixedSize(n))
}
}
// https://github.com/open-telemetry/opentelemetry-specification/blob/d4b241f451674e8f611bb589477680341006ad2b/specification/configuration/sdk-environment-variables.md#exemplar
const filterEnvKey = "OTEL_METRICS_EXEMPLAR_FILTER"
switch os.Getenv(filterEnvKey) {
case "always_on":
return resF()
case "always_off":
return exemplar.Drop[N]
case "trace_based":
fallthrough
default:
newR := resF()
return func() exemplar.Reservoir[N] {
return exemplar.SampledFilter(newR())
}
} }
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric // import "go.opentelemetry.io/otel/sdk/metric" package metric // import "go.opentelemetry.io/otel/sdk/metric"

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:generate stringer -type=InstrumentKind -trimprefix=InstrumentKind //go:generate stringer -type=InstrumentKind -trimprefix=InstrumentKind
@ -29,10 +18,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric/internal/aggregate" "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
) )
var ( var zeroScope instrumentation.Scope
zeroInstrumentKind InstrumentKind
zeroScope instrumentation.Scope
)
// InstrumentKind is the identifier of a group of instruments that all // InstrumentKind is the identifier of a group of instruments that all
// performing the same function. // performing the same function.
@ -41,28 +27,32 @@ type InstrumentKind uint8
const ( const (
// instrumentKindUndefined is an undefined instrument kind, it should not // instrumentKindUndefined is an undefined instrument kind, it should not
// be used by any initialized type. // be used by any initialized type.
instrumentKindUndefined InstrumentKind = iota // nolint:deadcode,varcheck,unused instrumentKindUndefined InstrumentKind = 0 // nolint:deadcode,varcheck,unused
// InstrumentKindCounter identifies a group of instruments that record // InstrumentKindCounter identifies a group of instruments that record
// increasing values synchronously with the code path they are measuring. // increasing values synchronously with the code path they are measuring.
InstrumentKindCounter InstrumentKindCounter InstrumentKind = 1
// InstrumentKindUpDownCounter identifies a group of instruments that // InstrumentKindUpDownCounter identifies a group of instruments that
// record increasing and decreasing values synchronously with the code path // record increasing and decreasing values synchronously with the code path
// they are measuring. // they are measuring.
InstrumentKindUpDownCounter InstrumentKindUpDownCounter InstrumentKind = 2
// InstrumentKindHistogram identifies a group of instruments that record a // InstrumentKindHistogram identifies a group of instruments that record a
// distribution of values synchronously with the code path they are // distribution of values synchronously with the code path they are
// measuring. // measuring.
InstrumentKindHistogram InstrumentKindHistogram InstrumentKind = 3
// InstrumentKindObservableCounter identifies a group of instruments that // InstrumentKindObservableCounter identifies a group of instruments that
// record increasing values in an asynchronous callback. // record increasing values in an asynchronous callback.
InstrumentKindObservableCounter InstrumentKindObservableCounter InstrumentKind = 4
// InstrumentKindObservableUpDownCounter identifies a group of instruments // InstrumentKindObservableUpDownCounter identifies a group of instruments
// that record increasing and decreasing values in an asynchronous // that record increasing and decreasing values in an asynchronous
// callback. // callback.
InstrumentKindObservableUpDownCounter InstrumentKindObservableUpDownCounter InstrumentKind = 5
// InstrumentKindObservableGauge identifies a group of instruments that // InstrumentKindObservableGauge identifies a group of instruments that
// record current values in an asynchronous callback. // record current values in an asynchronous callback.
InstrumentKindObservableGauge InstrumentKindObservableGauge InstrumentKind = 6
// InstrumentKindGauge identifies a group of instruments that record
// instantaneous values synchronously with the code path they are
// measuring.
InstrumentKindGauge InstrumentKind = 7
) )
type nonComparable [0]func() // nolint: unused // This is indeed used. type nonComparable [0]func() // nolint: unused // This is indeed used.
@ -84,11 +74,11 @@ type Instrument struct {
nonComparable // nolint: unused nonComparable // nolint: unused
} }
// empty returns if all fields of i are their zero-value. // IsEmpty returns if all Instrument fields are their zero-value.
func (i Instrument) empty() bool { func (i Instrument) IsEmpty() bool {
return i.Name == "" && return i.Name == "" &&
i.Description == "" && i.Description == "" &&
i.Kind == zeroInstrumentKind && i.Kind == instrumentKindUndefined &&
i.Unit == "" && i.Unit == "" &&
i.Scope == zeroScope i.Scope == zeroScope
} }
@ -119,7 +109,7 @@ func (i Instrument) matchesDescription(other Instrument) bool {
// matchesKind returns true if the Kind of i is its zero-value or it equals the // matchesKind returns true if the Kind of i is its zero-value or it equals the
// Kind of other, otherwise false. // Kind of other, otherwise false.
func (i Instrument) matchesKind(other Instrument) bool { func (i Instrument) matchesKind(other Instrument) bool {
return i.Kind == zeroInstrumentKind || i.Kind == other.Kind return i.Kind == instrumentKindUndefined || i.Kind == other.Kind
} }
// matchesUnit returns true if the Unit of i is its zero-value or it equals the // matchesUnit returns true if the Unit of i is its zero-value or it equals the
@ -186,12 +176,14 @@ type int64Inst struct {
embedded.Int64Counter embedded.Int64Counter
embedded.Int64UpDownCounter embedded.Int64UpDownCounter
embedded.Int64Histogram embedded.Int64Histogram
embedded.Int64Gauge
} }
var ( var (
_ metric.Int64Counter = (*int64Inst)(nil) _ metric.Int64Counter = (*int64Inst)(nil)
_ metric.Int64UpDownCounter = (*int64Inst)(nil) _ metric.Int64UpDownCounter = (*int64Inst)(nil)
_ metric.Int64Histogram = (*int64Inst)(nil) _ metric.Int64Histogram = (*int64Inst)(nil)
_ metric.Int64Gauge = (*int64Inst)(nil)
) )
func (i *int64Inst) Add(ctx context.Context, val int64, opts ...metric.AddOption) { func (i *int64Inst) Add(ctx context.Context, val int64, opts ...metric.AddOption) {
@ -216,12 +208,14 @@ type float64Inst struct {
embedded.Float64Counter embedded.Float64Counter
embedded.Float64UpDownCounter embedded.Float64UpDownCounter
embedded.Float64Histogram embedded.Float64Histogram
embedded.Float64Gauge
} }
var ( var (
_ metric.Float64Counter = (*float64Inst)(nil) _ metric.Float64Counter = (*float64Inst)(nil)
_ metric.Float64UpDownCounter = (*float64Inst)(nil) _ metric.Float64UpDownCounter = (*float64Inst)(nil)
_ metric.Float64Histogram = (*float64Inst)(nil) _ metric.Float64Histogram = (*float64Inst)(nil)
_ metric.Float64Gauge = (*float64Inst)(nil)
) )
func (i *float64Inst) Add(ctx context.Context, val float64, opts ...metric.AddOption) { func (i *float64Inst) Add(ctx context.Context, val float64, opts ...metric.AddOption) {

View file

@ -15,11 +15,12 @@ func _() {
_ = x[InstrumentKindObservableCounter-4] _ = x[InstrumentKindObservableCounter-4]
_ = x[InstrumentKindObservableUpDownCounter-5] _ = x[InstrumentKindObservableUpDownCounter-5]
_ = x[InstrumentKindObservableGauge-6] _ = x[InstrumentKindObservableGauge-6]
_ = x[InstrumentKindGauge-7]
} }
const _InstrumentKind_name = "instrumentKindUndefinedCounterUpDownCounterHistogramObservableCounterObservableUpDownCounterObservableGauge" const _InstrumentKind_name = "instrumentKindUndefinedCounterUpDownCounterHistogramObservableCounterObservableUpDownCounterObservableGaugeGauge"
var _InstrumentKind_index = [...]uint8{0, 23, 30, 43, 52, 69, 92, 107} var _InstrumentKind_index = [...]uint8{0, 23, 30, 43, 52, 69, 92, 107, 112}
func (i InstrumentKind) String() string { func (i InstrumentKind) String() string {
if i >= InstrumentKind(len(_InstrumentKind_index)-1) { if i >= InstrumentKind(len(_InstrumentKind_index)-1) {

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
@ -50,7 +39,7 @@ type Builder[N int64 | float64] struct {
// //
// If this is not provided a default factory function that returns an // If this is not provided a default factory function that returns an
// exemplar.Drop reservoir will be used. // exemplar.Drop reservoir will be used.
ReservoirFunc func() exemplar.Reservoir[N] ReservoirFunc func() exemplar.FilteredReservoir[N]
// AggregationLimit is the cardinality limit of measurement attributes. Any // AggregationLimit is the cardinality limit of measurement attributes. Any
// measurement for new attributes once the limit has been reached will be // measurement for new attributes once the limit has been reached will be
// aggregated into a single aggregate for the "otel.metric.overflow" // aggregated into a single aggregate for the "otel.metric.overflow"
@ -61,12 +50,12 @@ type Builder[N int64 | float64] struct {
AggregationLimit int AggregationLimit int
} }
func (b Builder[N]) resFunc() func() exemplar.Reservoir[N] { func (b Builder[N]) resFunc() func() exemplar.FilteredReservoir[N] {
if b.ReservoirFunc != nil { if b.ReservoirFunc != nil {
return b.ReservoirFunc return b.ReservoirFunc
} }
return exemplar.Drop[N] return exemplar.Drop
} }
type fltrMeasure[N int64 | float64] func(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) type fltrMeasure[N int64 | float64] func(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue)
@ -85,21 +74,26 @@ func (b Builder[N]) filter(f fltrMeasure[N]) Measure[N] {
} }
// LastValue returns a last-value aggregate function input and output. // LastValue returns a last-value aggregate function input and output.
//
// The Builder.Temporality is ignored and delta is use always.
func (b Builder[N]) LastValue() (Measure[N], ComputeAggregation) { func (b Builder[N]) LastValue() (Measure[N], ComputeAggregation) {
// Delta temporality is the only temporality that makes semantic sense for
// a last-value aggregate.
lv := newLastValue[N](b.AggregationLimit, b.resFunc()) lv := newLastValue[N](b.AggregationLimit, b.resFunc())
switch b.Temporality {
case metricdata.DeltaTemporality:
return b.filter(lv.measure), lv.delta
default:
return b.filter(lv.measure), lv.cumulative
}
}
return b.filter(lv.measure), func(dest *metricdata.Aggregation) int { // PrecomputedLastValue returns a last-value aggregate function input and
// Ignore if dest is not a metricdata.Gauge. The chance for memory // output. The aggregation returned from the returned ComputeAggregation
// reuse of the DataPoints is missed (better luck next time). // function will always only return values from the previous collection cycle.
gData, _ := (*dest).(metricdata.Gauge[N]) func (b Builder[N]) PrecomputedLastValue() (Measure[N], ComputeAggregation) {
lv.computeAggregation(&gData.DataPoints) lv := newPrecomputedLastValue[N](b.AggregationLimit, b.resFunc())
*dest = gData switch b.Temporality {
case metricdata.DeltaTemporality:
return len(gData.DataPoints) return b.filter(lv.measure), lv.delta
default:
return b.filter(lv.measure), lv.cumulative
} }
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package aggregate provides aggregate types used compute aggregations and // Package aggregate provides aggregate types used compute aggregations and
// cycle the state of metric measurements made by the SDK. These types and // cycle the state of metric measurements made by the SDK. These types and

View file

@ -0,0 +1,42 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
import (
"sync"
"go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
var exemplarPool = sync.Pool{
New: func() any { return new([]exemplar.Exemplar) },
}
func collectExemplars[N int64 | float64](out *[]metricdata.Exemplar[N], f func(*[]exemplar.Exemplar)) {
dest := exemplarPool.Get().(*[]exemplar.Exemplar)
defer func() {
*dest = (*dest)[:0]
exemplarPool.Put(dest)
}()
*dest = reset(*dest, len(*out), cap(*out))
f(dest)
*out = reset(*out, len(*dest), cap(*dest))
for i, e := range *dest {
(*out)[i].FilteredAttributes = e.FilteredAttributes
(*out)[i].Time = e.Time
(*out)[i].SpanID = e.SpanID
(*out)[i].TraceID = e.TraceID
switch e.Value.Type() {
case exemplar.Int64ValueType:
(*out)[i].Value = N(e.Value.Int64())
case exemplar.Float64ValueType:
(*out)[i].Value = N(e.Value.Float64())
}
}
}

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
@ -41,7 +30,8 @@ const (
// expoHistogramDataPoint is a single data point in an exponential histogram. // expoHistogramDataPoint is a single data point in an exponential histogram.
type expoHistogramDataPoint[N int64 | float64] struct { type expoHistogramDataPoint[N int64 | float64] struct {
res exemplar.Reservoir[N] attrs attribute.Set
res exemplar.FilteredReservoir[N]
count uint64 count uint64
min N min N
@ -52,14 +42,14 @@ type expoHistogramDataPoint[N int64 | float64] struct {
noMinMax bool noMinMax bool
noSum bool noSum bool
scale int scale int32
posBuckets expoBuckets posBuckets expoBuckets
negBuckets expoBuckets negBuckets expoBuckets
zeroCount uint64 zeroCount uint64
} }
func newExpoHistogramDataPoint[N int64 | float64](maxSize, maxScale int, noMinMax, noSum bool) *expoHistogramDataPoint[N] { func newExpoHistogramDataPoint[N int64 | float64](attrs attribute.Set, maxSize int, maxScale int32, noMinMax, noSum bool) *expoHistogramDataPoint[N] {
f := math.MaxFloat64 f := math.MaxFloat64
max := N(f) // if N is int64, max will overflow to -9223372036854775808 max := N(f) // if N is int64, max will overflow to -9223372036854775808
min := N(-f) min := N(-f)
@ -68,6 +58,7 @@ func newExpoHistogramDataPoint[N int64 | float64](maxSize, maxScale int, noMinMa
min = N(minInt64) min = N(minInt64)
} }
return &expoHistogramDataPoint[N]{ return &expoHistogramDataPoint[N]{
attrs: attrs,
min: max, min: max,
max: min, max: min,
maxSize: maxSize, maxSize: maxSize,
@ -128,11 +119,13 @@ func (p *expoHistogramDataPoint[N]) record(v N) {
} }
// getBin returns the bin v should be recorded into. // getBin returns the bin v should be recorded into.
func (p *expoHistogramDataPoint[N]) getBin(v float64) int { func (p *expoHistogramDataPoint[N]) getBin(v float64) int32 {
frac, exp := math.Frexp(v) frac, expInt := math.Frexp(v)
// 11-bit exponential.
exp := int32(expInt) // nolint: gosec
if p.scale <= 0 { if p.scale <= 0 {
// Because of the choice of fraction is always 1 power of two higher than we want. // Because of the choice of fraction is always 1 power of two higher than we want.
correction := 1 var correction int32 = 1
if frac == .5 { if frac == .5 {
// If v is an exact power of two the frac will be .5 and the exp // If v is an exact power of two the frac will be .5 and the exp
// will be one higher than we want. // will be one higher than we want.
@ -140,7 +133,7 @@ func (p *expoHistogramDataPoint[N]) getBin(v float64) int {
} }
return (exp - correction) >> (-p.scale) return (exp - correction) >> (-p.scale)
} }
return exp<<p.scale + int(math.Log(frac)*scaleFactors[p.scale]) - 1 return exp<<p.scale + int32(math.Log(frac)*scaleFactors[p.scale]) - 1
} }
// scaleFactors are constants used in calculating the logarithm index. They are // scaleFactors are constants used in calculating the logarithm index. They are
@ -171,20 +164,20 @@ var scaleFactors = [21]float64{
// scaleChange returns the magnitude of the scale change needed to fit bin in // scaleChange returns the magnitude of the scale change needed to fit bin in
// the bucket. If no scale change is needed 0 is returned. // the bucket. If no scale change is needed 0 is returned.
func (p *expoHistogramDataPoint[N]) scaleChange(bin, startBin, length int) int { func (p *expoHistogramDataPoint[N]) scaleChange(bin, startBin int32, length int) int32 {
if length == 0 { if length == 0 {
// No need to rescale if there are no buckets. // No need to rescale if there are no buckets.
return 0 return 0
} }
low := startBin low := int(startBin)
high := bin high := int(bin)
if startBin >= bin { if startBin >= bin {
low = bin low = int(bin)
high = startBin + length - 1 high = int(startBin) + length - 1
} }
count := 0 var count int32
for high-low >= p.maxSize { for high-low >= p.maxSize {
low = low >> 1 low = low >> 1
high = high >> 1 high = high >> 1
@ -198,39 +191,39 @@ func (p *expoHistogramDataPoint[N]) scaleChange(bin, startBin, length int) int {
// expoBuckets is a set of buckets in an exponential histogram. // expoBuckets is a set of buckets in an exponential histogram.
type expoBuckets struct { type expoBuckets struct {
startBin int startBin int32
counts []uint64 counts []uint64
} }
// record increments the count for the given bin, and expands the buckets if needed. // record increments the count for the given bin, and expands the buckets if needed.
// Size changes must be done before calling this function. // Size changes must be done before calling this function.
func (b *expoBuckets) record(bin int) { func (b *expoBuckets) record(bin int32) {
if len(b.counts) == 0 { if len(b.counts) == 0 {
b.counts = []uint64{1} b.counts = []uint64{1}
b.startBin = bin b.startBin = bin
return return
} }
endBin := b.startBin + len(b.counts) - 1 endBin := int(b.startBin) + len(b.counts) - 1
// if the new bin is inside the current range // if the new bin is inside the current range
if bin >= b.startBin && bin <= endBin { if bin >= b.startBin && int(bin) <= endBin {
b.counts[bin-b.startBin]++ b.counts[bin-b.startBin]++
return return
} }
// if the new bin is before the current start add spaces to the counts // if the new bin is before the current start add spaces to the counts
if bin < b.startBin { if bin < b.startBin {
origLen := len(b.counts) origLen := len(b.counts)
newLength := endBin - bin + 1 newLength := endBin - int(bin) + 1
shift := b.startBin - bin shift := b.startBin - bin
if newLength > cap(b.counts) { if newLength > cap(b.counts) {
b.counts = append(b.counts, make([]uint64, newLength-len(b.counts))...) b.counts = append(b.counts, make([]uint64, newLength-len(b.counts))...)
} }
copy(b.counts[shift:origLen+shift], b.counts[:]) copy(b.counts[shift:origLen+int(shift)], b.counts[:])
b.counts = b.counts[:newLength] b.counts = b.counts[:newLength]
for i := 1; i < shift; i++ { for i := 1; i < int(shift); i++ {
b.counts[i] = 0 b.counts[i] = 0
} }
b.startBin = bin b.startBin = bin
@ -238,17 +231,17 @@ func (b *expoBuckets) record(bin int) {
return return
} }
// if the new is after the end add spaces to the end // if the new is after the end add spaces to the end
if bin > endBin { if int(bin) > endBin {
if bin-b.startBin < cap(b.counts) { if int(bin-b.startBin) < cap(b.counts) {
b.counts = b.counts[:bin-b.startBin+1] b.counts = b.counts[:bin-b.startBin+1]
for i := endBin + 1 - b.startBin; i < len(b.counts); i++ { for i := endBin + 1 - int(b.startBin); i < len(b.counts); i++ {
b.counts[i] = 0 b.counts[i] = 0
} }
b.counts[bin-b.startBin] = 1 b.counts[bin-b.startBin] = 1
return return
} }
end := make([]uint64, bin-b.startBin-len(b.counts)+1) end := make([]uint64, int(bin-b.startBin)-len(b.counts)+1)
b.counts = append(b.counts, end...) b.counts = append(b.counts, end...)
b.counts[bin-b.startBin] = 1 b.counts[bin-b.startBin] = 1
} }
@ -256,7 +249,7 @@ func (b *expoBuckets) record(bin int) {
// downscale shrinks a bucket by a factor of 2*s. It will sum counts into the // downscale shrinks a bucket by a factor of 2*s. It will sum counts into the
// correct lower resolution bucket. // correct lower resolution bucket.
func (b *expoBuckets) downscale(delta int) { func (b *expoBuckets) downscale(delta int32) {
// Example // Example
// delta = 2 // delta = 2
// Original offset: -6 // Original offset: -6
@ -271,19 +264,19 @@ func (b *expoBuckets) downscale(delta int) {
return return
} }
steps := 1 << delta steps := int32(1) << delta
offset := b.startBin % steps offset := b.startBin % steps
offset = (offset + steps) % steps // to make offset positive offset = (offset + steps) % steps // to make offset positive
for i := 1; i < len(b.counts); i++ { for i := 1; i < len(b.counts); i++ {
idx := i + offset idx := i + int(offset)
if idx%steps == 0 { if idx%int(steps) == 0 {
b.counts[idx/steps] = b.counts[i] b.counts[idx/int(steps)] = b.counts[i]
continue continue
} }
b.counts[idx/steps] += b.counts[i] b.counts[idx/int(steps)] += b.counts[i]
} }
lastIdx := (len(b.counts) - 1 + offset) / steps lastIdx := (len(b.counts) - 1 + int(offset)) / int(steps)
b.counts = b.counts[:lastIdx+1] b.counts = b.counts[:lastIdx+1]
b.startBin = b.startBin >> delta b.startBin = b.startBin >> delta
} }
@ -291,16 +284,16 @@ func (b *expoBuckets) downscale(delta int) {
// newExponentialHistogram returns an Aggregator that summarizes a set of // newExponentialHistogram returns an Aggregator that summarizes a set of
// measurements as an exponential histogram. Each histogram is scoped by attributes // measurements as an exponential histogram. Each histogram is scoped by attributes
// and the aggregation cycle the measurements were made in. // and the aggregation cycle the measurements were made in.
func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir[N]) *expoHistogram[N] { func newExponentialHistogram[N int64 | float64](maxSize, maxScale int32, noMinMax, noSum bool, limit int, r func() exemplar.FilteredReservoir[N]) *expoHistogram[N] {
return &expoHistogram[N]{ return &expoHistogram[N]{
noSum: noSum, noSum: noSum,
noMinMax: noMinMax, noMinMax: noMinMax,
maxSize: int(maxSize), maxSize: int(maxSize),
maxScale: int(maxScale), maxScale: maxScale,
newRes: r, newRes: r,
limit: newLimiter[*expoHistogramDataPoint[N]](limit), limit: newLimiter[*expoHistogramDataPoint[N]](limit),
values: make(map[attribute.Set]*expoHistogramDataPoint[N]), values: make(map[attribute.Distinct]*expoHistogramDataPoint[N]),
start: now(), start: now(),
} }
@ -312,11 +305,11 @@ type expoHistogram[N int64 | float64] struct {
noSum bool noSum bool
noMinMax bool noMinMax bool
maxSize int maxSize int
maxScale int maxScale int32
newRes func() exemplar.Reservoir[N] newRes func() exemplar.FilteredReservoir[N]
limit limiter[*expoHistogramDataPoint[N]] limit limiter[*expoHistogramDataPoint[N]]
values map[attribute.Set]*expoHistogramDataPoint[N] values map[attribute.Distinct]*expoHistogramDataPoint[N]
valuesMu sync.Mutex valuesMu sync.Mutex
start time.Time start time.Time
@ -328,21 +321,19 @@ func (e *expoHistogram[N]) measure(ctx context.Context, value N, fltrAttr attrib
return return
} }
t := now()
e.valuesMu.Lock() e.valuesMu.Lock()
defer e.valuesMu.Unlock() defer e.valuesMu.Unlock()
attr := e.limit.Attributes(fltrAttr, e.values) attr := e.limit.Attributes(fltrAttr, e.values)
v, ok := e.values[attr] v, ok := e.values[attr.Equivalent()]
if !ok { if !ok {
v = newExpoHistogramDataPoint[N](e.maxSize, e.maxScale, e.noMinMax, e.noSum) v = newExpoHistogramDataPoint[N](attr, e.maxSize, e.maxScale, e.noMinMax, e.noSum)
v.res = e.newRes() v.res = e.newRes()
e.values[attr] = v e.values[attr.Equivalent()] = v
} }
v.record(value) v.record(value)
v.res.Offer(ctx, t, value, droppedAttr) v.res.Offer(ctx, value, droppedAttr)
} }
func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int { func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int {
@ -360,36 +351,38 @@ func (e *expoHistogram[N]) delta(dest *metricdata.Aggregation) int {
hDPts := reset(h.DataPoints, n, n) hDPts := reset(h.DataPoints, n, n)
var i int var i int
for a, b := range e.values { for _, val := range e.values {
hDPts[i].Attributes = a hDPts[i].Attributes = val.attrs
hDPts[i].StartTime = e.start hDPts[i].StartTime = e.start
hDPts[i].Time = t hDPts[i].Time = t
hDPts[i].Count = b.count hDPts[i].Count = val.count
hDPts[i].Scale = int32(b.scale) hDPts[i].Scale = val.scale
hDPts[i].ZeroCount = b.zeroCount hDPts[i].ZeroCount = val.zeroCount
hDPts[i].ZeroThreshold = 0.0 hDPts[i].ZeroThreshold = 0.0
hDPts[i].PositiveBucket.Offset = int32(b.posBuckets.startBin) hDPts[i].PositiveBucket.Offset = val.posBuckets.startBin
hDPts[i].PositiveBucket.Counts = reset(hDPts[i].PositiveBucket.Counts, len(b.posBuckets.counts), len(b.posBuckets.counts)) hDPts[i].PositiveBucket.Counts = reset(hDPts[i].PositiveBucket.Counts, len(val.posBuckets.counts), len(val.posBuckets.counts))
copy(hDPts[i].PositiveBucket.Counts, b.posBuckets.counts) copy(hDPts[i].PositiveBucket.Counts, val.posBuckets.counts)
hDPts[i].NegativeBucket.Offset = int32(b.negBuckets.startBin) hDPts[i].NegativeBucket.Offset = val.negBuckets.startBin
hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(b.negBuckets.counts), len(b.negBuckets.counts)) hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(val.negBuckets.counts), len(val.negBuckets.counts))
copy(hDPts[i].NegativeBucket.Counts, b.negBuckets.counts) copy(hDPts[i].NegativeBucket.Counts, val.negBuckets.counts)
if !e.noSum { if !e.noSum {
hDPts[i].Sum = b.sum hDPts[i].Sum = val.sum
} }
if !e.noMinMax { if !e.noMinMax {
hDPts[i].Min = metricdata.NewExtrema(b.min) hDPts[i].Min = metricdata.NewExtrema(val.min)
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(val.max)
} }
b.res.Collect(&hDPts[i].Exemplars) collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
delete(e.values, a)
i++ i++
} }
// Unused attribute sets do not report.
clear(e.values)
e.start = t e.start = t
h.DataPoints = hDPts h.DataPoints = hDPts
*dest = h *dest = h
@ -411,32 +404,32 @@ func (e *expoHistogram[N]) cumulative(dest *metricdata.Aggregation) int {
hDPts := reset(h.DataPoints, n, n) hDPts := reset(h.DataPoints, n, n)
var i int var i int
for a, b := range e.values { for _, val := range e.values {
hDPts[i].Attributes = a hDPts[i].Attributes = val.attrs
hDPts[i].StartTime = e.start hDPts[i].StartTime = e.start
hDPts[i].Time = t hDPts[i].Time = t
hDPts[i].Count = b.count hDPts[i].Count = val.count
hDPts[i].Scale = int32(b.scale) hDPts[i].Scale = val.scale
hDPts[i].ZeroCount = b.zeroCount hDPts[i].ZeroCount = val.zeroCount
hDPts[i].ZeroThreshold = 0.0 hDPts[i].ZeroThreshold = 0.0
hDPts[i].PositiveBucket.Offset = int32(b.posBuckets.startBin) hDPts[i].PositiveBucket.Offset = val.posBuckets.startBin
hDPts[i].PositiveBucket.Counts = reset(hDPts[i].PositiveBucket.Counts, len(b.posBuckets.counts), len(b.posBuckets.counts)) hDPts[i].PositiveBucket.Counts = reset(hDPts[i].PositiveBucket.Counts, len(val.posBuckets.counts), len(val.posBuckets.counts))
copy(hDPts[i].PositiveBucket.Counts, b.posBuckets.counts) copy(hDPts[i].PositiveBucket.Counts, val.posBuckets.counts)
hDPts[i].NegativeBucket.Offset = int32(b.negBuckets.startBin) hDPts[i].NegativeBucket.Offset = val.negBuckets.startBin
hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(b.negBuckets.counts), len(b.negBuckets.counts)) hDPts[i].NegativeBucket.Counts = reset(hDPts[i].NegativeBucket.Counts, len(val.negBuckets.counts), len(val.negBuckets.counts))
copy(hDPts[i].NegativeBucket.Counts, b.negBuckets.counts) copy(hDPts[i].NegativeBucket.Counts, val.negBuckets.counts)
if !e.noSum { if !e.noSum {
hDPts[i].Sum = b.sum hDPts[i].Sum = val.sum
} }
if !e.noMinMax { if !e.noMinMax {
hDPts[i].Min = metricdata.NewExtrema(b.min) hDPts[i].Min = metricdata.NewExtrema(val.min)
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(val.max)
} }
b.res.Collect(&hDPts[i].Exemplars) collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
i++ i++
// TODO (#3006): This will use an unbounded amount of memory if there // TODO (#3006): This will use an unbounded amount of memory if there

View file

@ -1,21 +1,11 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
import ( import (
"context" "context"
"slices"
"sort" "sort"
"sync" "sync"
"time" "time"
@ -26,7 +16,8 @@ import (
) )
type buckets[N int64 | float64] struct { type buckets[N int64 | float64] struct {
res exemplar.Reservoir[N] attrs attribute.Set
res exemplar.FilteredReservoir[N]
counts []uint64 counts []uint64
count uint64 count uint64
@ -35,8 +26,8 @@ type buckets[N int64 | float64] struct {
} }
// newBuckets returns buckets with n bins. // newBuckets returns buckets with n bins.
func newBuckets[N int64 | float64](n int) *buckets[N] { func newBuckets[N int64 | float64](attrs attribute.Set, n int) *buckets[N] {
return &buckets[N]{counts: make([]uint64, n)} return &buckets[N]{attrs: attrs, counts: make([]uint64, n)}
} }
func (b *buckets[N]) sum(value N) { b.total += value } func (b *buckets[N]) sum(value N) { b.total += value }
@ -57,26 +48,25 @@ type histValues[N int64 | float64] struct {
noSum bool noSum bool
bounds []float64 bounds []float64
newRes func() exemplar.Reservoir[N] newRes func() exemplar.FilteredReservoir[N]
limit limiter[*buckets[N]] limit limiter[*buckets[N]]
values map[attribute.Set]*buckets[N] values map[attribute.Distinct]*buckets[N]
valuesMu sync.Mutex valuesMu sync.Mutex
} }
func newHistValues[N int64 | float64](bounds []float64, noSum bool, limit int, r func() exemplar.Reservoir[N]) *histValues[N] { func newHistValues[N int64 | float64](bounds []float64, noSum bool, limit int, r func() exemplar.FilteredReservoir[N]) *histValues[N] {
// The responsibility of keeping all buckets correctly associated with the // The responsibility of keeping all buckets correctly associated with the
// passed boundaries is ultimately this type's responsibility. Make a copy // passed boundaries is ultimately this type's responsibility. Make a copy
// here so we can always guarantee this. Or, in the case of failure, have // here so we can always guarantee this. Or, in the case of failure, have
// complete control over the fix. // complete control over the fix.
b := make([]float64, len(bounds)) b := slices.Clone(bounds)
copy(b, bounds) slices.Sort(b)
sort.Float64s(b)
return &histValues[N]{ return &histValues[N]{
noSum: noSum, noSum: noSum,
bounds: b, bounds: b,
newRes: r, newRes: r,
limit: newLimiter[*buckets[N]](limit), limit: newLimiter[*buckets[N]](limit),
values: make(map[attribute.Set]*buckets[N]), values: make(map[attribute.Distinct]*buckets[N]),
} }
} }
@ -90,13 +80,11 @@ func (s *histValues[N]) measure(ctx context.Context, value N, fltrAttr attribute
// (s.bounds[len(s.bounds)-1], +∞). // (s.bounds[len(s.bounds)-1], +∞).
idx := sort.SearchFloat64s(s.bounds, float64(value)) idx := sort.SearchFloat64s(s.bounds, float64(value))
t := now()
s.valuesMu.Lock() s.valuesMu.Lock()
defer s.valuesMu.Unlock() defer s.valuesMu.Unlock()
attr := s.limit.Attributes(fltrAttr, s.values) attr := s.limit.Attributes(fltrAttr, s.values)
b, ok := s.values[attr] b, ok := s.values[attr.Equivalent()]
if !ok { if !ok {
// N+1 buckets. For example: // N+1 buckets. For example:
// //
@ -105,23 +93,23 @@ func (s *histValues[N]) measure(ctx context.Context, value N, fltrAttr attribute
// Then, // Then,
// //
// buckets = (-∞, 0], (0, 5.0], (5.0, 10.0], (10.0, +∞) // buckets = (-∞, 0], (0, 5.0], (5.0, 10.0], (10.0, +∞)
b = newBuckets[N](len(s.bounds) + 1) b = newBuckets[N](attr, len(s.bounds)+1)
b.res = s.newRes() b.res = s.newRes()
// Ensure min and max are recorded values (not zero), for new buckets. // Ensure min and max are recorded values (not zero), for new buckets.
b.min, b.max = value, value b.min, b.max = value, value
s.values[attr] = b s.values[attr.Equivalent()] = b
} }
b.bin(idx, value) b.bin(idx, value)
if !s.noSum { if !s.noSum {
b.sum(value) b.sum(value)
} }
b.res.Offer(ctx, t, value, droppedAttr) b.res.Offer(ctx, value, droppedAttr)
} }
// newHistogram returns an Aggregator that summarizes a set of measurements as // newHistogram returns an Aggregator that summarizes a set of measurements as
// an histogram. // an histogram.
func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool, limit int, r func() exemplar.Reservoir[N]) *histogram[N] { func newHistogram[N int64 | float64](boundaries []float64, noMinMax, noSum bool, limit int, r func() exemplar.FilteredReservoir[N]) *histogram[N] {
return &histogram[N]{ return &histogram[N]{
histValues: newHistValues[N](boundaries, noSum, limit, r), histValues: newHistValues[N](boundaries, noSum, limit, r),
noMinMax: noMinMax, noMinMax: noMinMax,
@ -150,36 +138,35 @@ func (s *histogram[N]) delta(dest *metricdata.Aggregation) int {
defer s.valuesMu.Unlock() defer s.valuesMu.Unlock()
// Do not allow modification of our copy of bounds. // Do not allow modification of our copy of bounds.
bounds := make([]float64, len(s.bounds)) bounds := slices.Clone(s.bounds)
copy(bounds, s.bounds)
n := len(s.values) n := len(s.values)
hDPts := reset(h.DataPoints, n, n) hDPts := reset(h.DataPoints, n, n)
var i int var i int
for a, b := range s.values { for _, val := range s.values {
hDPts[i].Attributes = a hDPts[i].Attributes = val.attrs
hDPts[i].StartTime = s.start hDPts[i].StartTime = s.start
hDPts[i].Time = t hDPts[i].Time = t
hDPts[i].Count = b.count hDPts[i].Count = val.count
hDPts[i].Bounds = bounds hDPts[i].Bounds = bounds
hDPts[i].BucketCounts = b.counts hDPts[i].BucketCounts = val.counts
if !s.noSum { if !s.noSum {
hDPts[i].Sum = b.total hDPts[i].Sum = val.total
} }
if !s.noMinMax { if !s.noMinMax {
hDPts[i].Min = metricdata.NewExtrema(b.min) hDPts[i].Min = metricdata.NewExtrema(val.min)
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(val.max)
} }
b.res.Collect(&hDPts[i].Exemplars) collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
// Unused attribute sets do not report.
delete(s.values, a)
i++ i++
} }
// Unused attribute sets do not report.
clear(s.values)
// The delta collection cycle resets. // The delta collection cycle resets.
s.start = t s.start = t
@ -201,39 +188,36 @@ func (s *histogram[N]) cumulative(dest *metricdata.Aggregation) int {
defer s.valuesMu.Unlock() defer s.valuesMu.Unlock()
// Do not allow modification of our copy of bounds. // Do not allow modification of our copy of bounds.
bounds := make([]float64, len(s.bounds)) bounds := slices.Clone(s.bounds)
copy(bounds, s.bounds)
n := len(s.values) n := len(s.values)
hDPts := reset(h.DataPoints, n, n) hDPts := reset(h.DataPoints, n, n)
var i int var i int
for a, b := range s.values { for _, val := range s.values {
hDPts[i].Attributes = val.attrs
hDPts[i].StartTime = s.start
hDPts[i].Time = t
hDPts[i].Count = val.count
hDPts[i].Bounds = bounds
// The HistogramDataPoint field values returned need to be copies of // The HistogramDataPoint field values returned need to be copies of
// the buckets value as we will keep updating them. // the buckets value as we will keep updating them.
// //
// TODO (#3047): Making copies for bounds and counts incurs a large // TODO (#3047): Making copies for bounds and counts incurs a large
// memory allocation footprint. Alternatives should be explored. // memory allocation footprint. Alternatives should be explored.
counts := make([]uint64, len(b.counts)) hDPts[i].BucketCounts = slices.Clone(val.counts)
copy(counts, b.counts)
hDPts[i].Attributes = a
hDPts[i].StartTime = s.start
hDPts[i].Time = t
hDPts[i].Count = b.count
hDPts[i].Bounds = bounds
hDPts[i].BucketCounts = counts
if !s.noSum { if !s.noSum {
hDPts[i].Sum = b.total hDPts[i].Sum = val.total
} }
if !s.noMinMax { if !s.noMinMax {
hDPts[i].Min = metricdata.NewExtrema(b.min) hDPts[i].Min = metricdata.NewExtrema(val.min)
hDPts[i].Max = metricdata.NewExtrema(b.max) hDPts[i].Max = metricdata.NewExtrema(val.max)
} }
b.res.Collect(&hDPts[i].Exemplars) collectExemplars(&hDPts[i].Exemplars, val.res.Collect)
i++ i++
// TODO (#3006): This will use an unbounded amount of memory if there // TODO (#3006): This will use an unbounded amount of memory if there

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
@ -26,16 +15,17 @@ import (
// datapoint is timestamped measurement data. // datapoint is timestamped measurement data.
type datapoint[N int64 | float64] struct { type datapoint[N int64 | float64] struct {
timestamp time.Time attrs attribute.Set
value N value N
res exemplar.Reservoir[N] res exemplar.FilteredReservoir[N]
} }
func newLastValue[N int64 | float64](limit int, r func() exemplar.Reservoir[N]) *lastValue[N] { func newLastValue[N int64 | float64](limit int, r func() exemplar.FilteredReservoir[N]) *lastValue[N] {
return &lastValue[N]{ return &lastValue[N]{
newRes: r, newRes: r,
limit: newLimiter[datapoint[N]](limit), limit: newLimiter[datapoint[N]](limit),
values: make(map[attribute.Set]datapoint[N]), values: make(map[attribute.Distinct]datapoint[N]),
start: now(),
} }
} }
@ -43,47 +33,130 @@ func newLastValue[N int64 | float64](limit int, r func() exemplar.Reservoir[N])
type lastValue[N int64 | float64] struct { type lastValue[N int64 | float64] struct {
sync.Mutex sync.Mutex
newRes func() exemplar.Reservoir[N] newRes func() exemplar.FilteredReservoir[N]
limit limiter[datapoint[N]] limit limiter[datapoint[N]]
values map[attribute.Set]datapoint[N] values map[attribute.Distinct]datapoint[N]
start time.Time
} }
func (s *lastValue[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) { func (s *lastValue[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) {
t := now()
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
attr := s.limit.Attributes(fltrAttr, s.values) attr := s.limit.Attributes(fltrAttr, s.values)
d, ok := s.values[attr] d, ok := s.values[attr.Equivalent()]
if !ok { if !ok {
d.res = s.newRes() d.res = s.newRes()
} }
d.timestamp = t d.attrs = attr
d.value = value d.value = value
d.res.Offer(ctx, t, value, droppedAttr) d.res.Offer(ctx, value, droppedAttr)
s.values[attr] = d s.values[attr.Equivalent()] = d
} }
func (s *lastValue[N]) computeAggregation(dest *[]metricdata.DataPoint[N]) { func (s *lastValue[N]) delta(dest *metricdata.Aggregation) int {
t := now()
// Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of
// the DataPoints is missed (better luck next time).
gData, _ := (*dest).(metricdata.Gauge[N])
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
n := s.copyDpts(&gData.DataPoints, t)
// Do not report stale values.
clear(s.values)
// Update start time for delta temporality.
s.start = t
*dest = gData
return n
}
func (s *lastValue[N]) cumulative(dest *metricdata.Aggregation) int {
t := now()
// Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of
// the DataPoints is missed (better luck next time).
gData, _ := (*dest).(metricdata.Gauge[N])
s.Lock()
defer s.Unlock()
n := s.copyDpts(&gData.DataPoints, t)
// TODO (#3006): This will use an unbounded amount of memory if there
// are unbounded number of attribute sets being aggregated. Attribute
// sets that become "stale" need to be forgotten so this will not
// overload the system.
*dest = gData
return n
}
// copyDpts copies the datapoints held by s into dest. The number of datapoints
// copied is returned.
func (s *lastValue[N]) copyDpts(dest *[]metricdata.DataPoint[N], t time.Time) int {
n := len(s.values) n := len(s.values)
*dest = reset(*dest, n, n) *dest = reset(*dest, n, n)
var i int var i int
for a, v := range s.values { for _, v := range s.values {
(*dest)[i].Attributes = a (*dest)[i].Attributes = v.attrs
// The event time is the only meaningful timestamp, StartTime is (*dest)[i].StartTime = s.start
// ignored. (*dest)[i].Time = t
(*dest)[i].Time = v.timestamp
(*dest)[i].Value = v.value (*dest)[i].Value = v.value
v.res.Collect(&(*dest)[i].Exemplars) collectExemplars(&(*dest)[i].Exemplars, v.res.Collect)
// Do not report stale values.
delete(s.values, a)
i++ i++
} }
return n
}
// newPrecomputedLastValue returns an aggregator that summarizes a set of
// observations as the last one made.
func newPrecomputedLastValue[N int64 | float64](limit int, r func() exemplar.FilteredReservoir[N]) *precomputedLastValue[N] {
return &precomputedLastValue[N]{lastValue: newLastValue[N](limit, r)}
}
// precomputedLastValue summarizes a set of observations as the last one made.
type precomputedLastValue[N int64 | float64] struct {
*lastValue[N]
}
func (s *precomputedLastValue[N]) delta(dest *metricdata.Aggregation) int {
t := now()
// Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of
// the DataPoints is missed (better luck next time).
gData, _ := (*dest).(metricdata.Gauge[N])
s.Lock()
defer s.Unlock()
n := s.copyDpts(&gData.DataPoints, t)
// Do not report stale values.
clear(s.values)
// Update start time for delta temporality.
s.start = t
*dest = gData
return n
}
func (s *precomputedLastValue[N]) cumulative(dest *metricdata.Aggregation) int {
t := now()
// Ignore if dest is not a metricdata.Gauge. The chance for memory reuse of
// the DataPoints is missed (better luck next time).
gData, _ := (*dest).(metricdata.Gauge[N])
s.Lock()
defer s.Unlock()
n := s.copyDpts(&gData.DataPoints, t)
// Do not report stale values.
clear(s.values)
*dest = gData
return n
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
@ -41,9 +30,9 @@ func newLimiter[V any](aggregation int) limiter[V] {
// aggregation cardinality limit for the existing measurements. If it will, // aggregation cardinality limit for the existing measurements. If it will,
// overflowSet is returned. Otherwise, if it will not exceed the limit, or the // overflowSet is returned. Otherwise, if it will not exceed the limit, or the
// limit is not set (limit <= 0), attr is returned. // limit is not set (limit <= 0), attr is returned.
func (l limiter[V]) Attributes(attrs attribute.Set, measurements map[attribute.Set]V) attribute.Set { func (l limiter[V]) Attributes(attrs attribute.Set, measurements map[attribute.Distinct]V) attribute.Set {
if l.aggLimit > 0 { if l.aggLimit > 0 {
_, exists := measurements[attrs] _, exists := measurements[attrs.Equivalent()]
if !exists && len(measurements) >= l.aggLimit-1 { if !exists && len(measurements) >= l.aggLimit-1 {
return overflowSet return overflowSet
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate" package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
@ -26,47 +15,47 @@ import (
type sumValue[N int64 | float64] struct { type sumValue[N int64 | float64] struct {
n N n N
res exemplar.Reservoir[N] res exemplar.FilteredReservoir[N]
attrs attribute.Set
} }
// valueMap is the storage for sums. // valueMap is the storage for sums.
type valueMap[N int64 | float64] struct { type valueMap[N int64 | float64] struct {
sync.Mutex sync.Mutex
newRes func() exemplar.Reservoir[N] newRes func() exemplar.FilteredReservoir[N]
limit limiter[sumValue[N]] limit limiter[sumValue[N]]
values map[attribute.Set]sumValue[N] values map[attribute.Distinct]sumValue[N]
} }
func newValueMap[N int64 | float64](limit int, r func() exemplar.Reservoir[N]) *valueMap[N] { func newValueMap[N int64 | float64](limit int, r func() exemplar.FilteredReservoir[N]) *valueMap[N] {
return &valueMap[N]{ return &valueMap[N]{
newRes: r, newRes: r,
limit: newLimiter[sumValue[N]](limit), limit: newLimiter[sumValue[N]](limit),
values: make(map[attribute.Set]sumValue[N]), values: make(map[attribute.Distinct]sumValue[N]),
} }
} }
func (s *valueMap[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) { func (s *valueMap[N]) measure(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue) {
t := now()
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
attr := s.limit.Attributes(fltrAttr, s.values) attr := s.limit.Attributes(fltrAttr, s.values)
v, ok := s.values[attr] v, ok := s.values[attr.Equivalent()]
if !ok { if !ok {
v.res = s.newRes() v.res = s.newRes()
} }
v.attrs = attr
v.n += value v.n += value
v.res.Offer(ctx, t, value, droppedAttr) v.res.Offer(ctx, value, droppedAttr)
s.values[attr] = v s.values[attr.Equivalent()] = v
} }
// newSum returns an aggregator that summarizes a set of measurements as their // newSum returns an aggregator that summarizes a set of measurements as their
// arithmetic sum. Each sum is scoped by attributes and the aggregation cycle // arithmetic sum. Each sum is scoped by attributes and the aggregation cycle
// the measurements were made in. // the measurements were made in.
func newSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.Reservoir[N]) *sum[N] { func newSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.FilteredReservoir[N]) *sum[N] {
return &sum[N]{ return &sum[N]{
valueMap: newValueMap[N](limit, r), valueMap: newValueMap[N](limit, r),
monotonic: monotonic, monotonic: monotonic,
@ -98,16 +87,16 @@ func (s *sum[N]) delta(dest *metricdata.Aggregation) int {
dPts := reset(sData.DataPoints, n, n) dPts := reset(sData.DataPoints, n, n)
var i int var i int
for attr, val := range s.values { for _, val := range s.values {
dPts[i].Attributes = attr dPts[i].Attributes = val.attrs
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = val.n dPts[i].Value = val.n
val.res.Collect(&dPts[i].Exemplars) collectExemplars(&dPts[i].Exemplars, val.res.Collect)
// Do not report stale values.
delete(s.values, attr)
i++ i++
} }
// Do not report stale values.
clear(s.values)
// The delta collection cycle resets. // The delta collection cycle resets.
s.start = t s.start = t
@ -133,12 +122,12 @@ func (s *sum[N]) cumulative(dest *metricdata.Aggregation) int {
dPts := reset(sData.DataPoints, n, n) dPts := reset(sData.DataPoints, n, n)
var i int var i int
for attr, value := range s.values { for _, value := range s.values {
dPts[i].Attributes = attr dPts[i].Attributes = value.attrs
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = value.n dPts[i].Value = value.n
value.res.Collect(&dPts[i].Exemplars) collectExemplars(&dPts[i].Exemplars, value.res.Collect)
// TODO (#3006): This will use an unbounded amount of memory if there // TODO (#3006): This will use an unbounded amount of memory if there
// are unbounded number of attribute sets being aggregated. Attribute // are unbounded number of attribute sets being aggregated. Attribute
// sets that become "stale" need to be forgotten so this will not // sets that become "stale" need to be forgotten so this will not
@ -155,7 +144,7 @@ func (s *sum[N]) cumulative(dest *metricdata.Aggregation) int {
// newPrecomputedSum returns an aggregator that summarizes a set of // newPrecomputedSum returns an aggregator that summarizes a set of
// observatrions as their arithmetic sum. Each sum is scoped by attributes and // observatrions as their arithmetic sum. Each sum is scoped by attributes and
// the aggregation cycle the measurements were made in. // the aggregation cycle the measurements were made in.
func newPrecomputedSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.Reservoir[N]) *precomputedSum[N] { func newPrecomputedSum[N int64 | float64](monotonic bool, limit int, r func() exemplar.FilteredReservoir[N]) *precomputedSum[N] {
return &precomputedSum[N]{ return &precomputedSum[N]{
valueMap: newValueMap[N](limit, r), valueMap: newValueMap[N](limit, r),
monotonic: monotonic, monotonic: monotonic,
@ -170,12 +159,12 @@ type precomputedSum[N int64 | float64] struct {
monotonic bool monotonic bool
start time.Time start time.Time
reported map[attribute.Set]N reported map[attribute.Distinct]N
} }
func (s *precomputedSum[N]) delta(dest *metricdata.Aggregation) int { func (s *precomputedSum[N]) delta(dest *metricdata.Aggregation) int {
t := now() t := now()
newReported := make(map[attribute.Set]N) newReported := make(map[attribute.Distinct]N)
// If *dest is not a metricdata.Sum, memory reuse is missed. In that case, // If *dest is not a metricdata.Sum, memory reuse is missed. In that case,
// use the zero-value sData and hope for better alignment next cycle. // use the zero-value sData and hope for better alignment next cycle.
@ -190,21 +179,20 @@ func (s *precomputedSum[N]) delta(dest *metricdata.Aggregation) int {
dPts := reset(sData.DataPoints, n, n) dPts := reset(sData.DataPoints, n, n)
var i int var i int
for attr, value := range s.values { for key, value := range s.values {
delta := value.n - s.reported[attr] delta := value.n - s.reported[key]
dPts[i].Attributes = attr dPts[i].Attributes = value.attrs
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = delta dPts[i].Value = delta
value.res.Collect(&dPts[i].Exemplars) collectExemplars(&dPts[i].Exemplars, value.res.Collect)
newReported[attr] = value.n newReported[key] = value.n
// Unused attribute sets do not report.
delete(s.values, attr)
i++ i++
} }
// Unused attribute sets are forgotten. // Unused attribute sets do not report.
clear(s.values)
s.reported = newReported s.reported = newReported
// The delta collection cycle resets. // The delta collection cycle resets.
s.start = t s.start = t
@ -231,17 +219,17 @@ func (s *precomputedSum[N]) cumulative(dest *metricdata.Aggregation) int {
dPts := reset(sData.DataPoints, n, n) dPts := reset(sData.DataPoints, n, n)
var i int var i int
for attr, val := range s.values { for _, val := range s.values {
dPts[i].Attributes = attr dPts[i].Attributes = val.attrs
dPts[i].StartTime = s.start dPts[i].StartTime = s.start
dPts[i].Time = t dPts[i].Time = t
dPts[i].Value = val.n dPts[i].Value = val.n
val.res.Collect(&dPts[i].Exemplars) collectExemplars(&dPts[i].Exemplars, val.res.Collect)
// Unused attribute sets do not report.
delete(s.values, attr)
i++ i++
} }
// Unused attribute sets do not report.
clear(s.values)
sData.DataPoints = dPts sData.DataPoints = dPts
*dest = sData *dest = sData

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package exemplar provides an implementation of the OpenTelemetry exemplar // Package exemplar provides an implementation of the OpenTelemetry exemplar
// reservoir to be used in metric collection pipelines. // reservoir to be used in metric collection pipelines.

View file

@ -1,36 +1,23 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import ( import (
"context" "context"
"time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
// Drop returns a [Reservoir] that drops all measurements it is offered. // Drop returns a [FilteredReservoir] that drops all measurements it is offered.
func Drop[N int64 | float64]() Reservoir[N] { return &dropRes[N]{} } func Drop[N int64 | float64]() FilteredReservoir[N] { return &dropRes[N]{} }
type dropRes[N int64 | float64] struct{} type dropRes[N int64 | float64] struct{}
// Offer does nothing, all measurements offered will be dropped. // Offer does nothing, all measurements offered will be dropped.
func (r *dropRes[N]) Offer(context.Context, time.Time, N, []attribute.KeyValue) {} func (r *dropRes[N]) Offer(context.Context, N, []attribute.KeyValue) {}
// Collect resets dest. No exemplars will ever be returned. // Collect resets dest. No exemplars will ever be returned.
func (r *dropRes[N]) Collect(dest *[]metricdata.Exemplar[N]) { func (r *dropRes[N]) Collect(dest *[]Exemplar) {
*dest = (*dest)[:0] *dest = (*dest)[:0]
} }

View file

@ -0,0 +1,29 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"time"
"go.opentelemetry.io/otel/attribute"
)
// Exemplar is a measurement sampled from a timeseries providing a typical
// example.
type Exemplar struct {
// FilteredAttributes are the attributes recorded with the measurement but
// filtered out of the timeseries' aggregated data.
FilteredAttributes []attribute.KeyValue
// Time is the time when the measurement was recorded.
Time time.Time
// Value is the measured value.
Value Value
// SpanID is the ID of the span that was active during the measurement. If
// no span was active or the span was not sampled this will be empty.
SpanID []byte `json:",omitempty"`
// TraceID is the ID of the trace the active span belonged to during the
// measurement. If no span was active or the span was not sampled this will
// be empty.
TraceID []byte `json:",omitempty"`
}

View file

@ -1,40 +1,29 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import ( import (
"context" "context"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
// SampledFilter returns a [Reservoir] wrapping r that will only offer measurements // Filter determines if a measurement should be offered.
// to r if the passed context associated with the measurement contains a sampled //
// The passed ctx needs to contain any baggage or span that were active
// when the measurement was made. This information may be used by the
// Reservoir in making a sampling decision.
type Filter func(context.Context) bool
// SampledFilter is a [Filter] that will only offer measurements
// if the passed context associated with the measurement contains a sampled
// [go.opentelemetry.io/otel/trace.SpanContext]. // [go.opentelemetry.io/otel/trace.SpanContext].
func SampledFilter[N int64 | float64](r Reservoir[N]) Reservoir[N] { func SampledFilter(ctx context.Context) bool {
return filtered[N]{Reservoir: r} return trace.SpanContextFromContext(ctx).IsSampled()
} }
type filtered[N int64 | float64] struct { // AlwaysOnFilter is a [Filter] that always offers measurements.
Reservoir[N] func AlwaysOnFilter(ctx context.Context) bool {
} return true
func (f filtered[N]) Offer(ctx context.Context, t time.Time, n N, a []attribute.KeyValue) {
if trace.SpanContextFromContext(ctx).IsSampled() {
f.Reservoir.Offer(ctx, t, n, a)
}
} }

View file

@ -0,0 +1,49 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import (
"context"
"time"
"go.opentelemetry.io/otel/attribute"
)
// FilteredReservoir wraps a [Reservoir] with a filter.
type FilteredReservoir[N int64 | float64] interface {
// Offer accepts the parameters associated with a measurement. The
// parameters will be stored as an exemplar if the filter decides to
// sample the measurement.
//
// The passed ctx needs to contain any baggage or span that were active
// when the measurement was made. This information may be used by the
// Reservoir in making a sampling decision.
Offer(ctx context.Context, val N, attr []attribute.KeyValue)
// Collect returns all the held exemplars in the reservoir.
Collect(dest *[]Exemplar)
}
// filteredReservoir handles the pre-sampled exemplar of measurements made.
type filteredReservoir[N int64 | float64] struct {
filter Filter
reservoir Reservoir
}
// NewFilteredReservoir creates a [FilteredReservoir] which only offers values
// that are allowed by the filter.
func NewFilteredReservoir[N int64 | float64](f Filter, r Reservoir) FilteredReservoir[N] {
return &filteredReservoir[N]{
filter: f,
reservoir: r,
}
}
func (f *filteredReservoir[N]) Offer(ctx context.Context, val N, attr []attribute.KeyValue) {
if f.filter(ctx) {
// only record the current time if we are sampling this measurment.
f.reservoir.Offer(ctx, time.Now(), NewValue(val), attr)
}
}
func (f *filteredReservoir[N]) Collect(dest *[]Exemplar) { f.reservoir.Collect(dest) }

View file

@ -1,21 +1,11 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import ( import (
"context" "context"
"slices"
"sort" "sort"
"time" "time"
@ -27,21 +17,30 @@ import (
// by bounds. // by bounds.
// //
// The passed bounds will be sorted by this function. // The passed bounds will be sorted by this function.
func Histogram[N int64 | float64](bounds []float64) Reservoir[N] { func Histogram(bounds []float64) Reservoir {
sort.Float64s(bounds) slices.Sort(bounds)
return &histRes[N]{ return &histRes{
bounds: bounds, bounds: bounds,
storage: newStorage[N](len(bounds) + 1), storage: newStorage(len(bounds) + 1),
} }
} }
type histRes[N int64 | float64] struct { type histRes struct {
*storage[N] *storage
// bounds are bucket bounds in ascending order. // bounds are bucket bounds in ascending order.
bounds []float64 bounds []float64
} }
func (r *histRes[N]) Offer(ctx context.Context, t time.Time, n N, a []attribute.KeyValue) { func (r *histRes) Offer(ctx context.Context, t time.Time, v Value, a []attribute.KeyValue) {
r.store[sort.SearchFloat64s(r.bounds, float64(n))] = newMeasurement(ctx, t, n, a) var x float64
switch v.Type() {
case Int64ValueType:
x = float64(v.Int64())
case Float64ValueType:
x = v.Float64()
default:
panic("unknown value type")
}
r.store[sort.SearchFloat64s(r.bounds, x)] = newMeasurement(ctx, t, v, a)
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
@ -18,17 +7,21 @@ import (
"context" "context"
"math" "math"
"math/rand" "math/rand"
"sync"
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
// rng is used to make sampling decisions. var (
// // rng is used to make sampling decisions.
// Do not use crypto/rand. There is no reason for the decrease in performance //
// given this is not a security sensitive decision. // Do not use crypto/rand. There is no reason for the decrease in performance
var rng = rand.New(rand.NewSource(time.Now().UnixNano())) // given this is not a security sensitive decision.
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
// Ensure concurrent safe accecess to rng and its underlying source.
rngMu sync.Mutex
)
// random returns, as a float64, a uniform pseudo-random number in the open // random returns, as a float64, a uniform pseudo-random number in the open
// interval (0.0,1.0). // interval (0.0,1.0).
@ -50,6 +43,9 @@ func random() float64 {
// //
// There are likely many other methods to explore here as well. // There are likely many other methods to explore here as well.
rngMu.Lock()
defer rngMu.Unlock()
f := rng.Float64() f := rng.Float64()
for f == 0 { for f == 0 {
f = rng.Float64() f = rng.Float64()
@ -61,14 +57,14 @@ func random() float64 {
// are k or less measurements made, the Reservoir will sample each one. If // are k or less measurements made, the Reservoir will sample each one. If
// there are more than k, the Reservoir will then randomly sample all // there are more than k, the Reservoir will then randomly sample all
// additional measurement with a decreasing probability. // additional measurement with a decreasing probability.
func FixedSize[N int64 | float64](k int) Reservoir[N] { func FixedSize(k int) Reservoir {
r := &randRes[N]{storage: newStorage[N](k)} r := &randRes{storage: newStorage(k)}
r.reset() r.reset()
return r return r
} }
type randRes[N int64 | float64] struct { type randRes struct {
*storage[N] *storage
// count is the number of measurement seen. // count is the number of measurement seen.
count int64 count int64
@ -80,7 +76,7 @@ type randRes[N int64 | float64] struct {
w float64 w float64
} }
func (r *randRes[N]) Offer(ctx context.Context, t time.Time, n N, a []attribute.KeyValue) { func (r *randRes) Offer(ctx context.Context, t time.Time, n Value, a []attribute.KeyValue) {
// The following algorithm is "Algorithm L" from Li, Kim-Hung (4 December // The following algorithm is "Algorithm L" from Li, Kim-Hung (4 December
// 1994). "Reservoir-Sampling Algorithms of Time Complexity // 1994). "Reservoir-Sampling Algorithms of Time Complexity
// O(n(1+log(N/n)))". ACM Transactions on Mathematical Software. 20 (4): // O(n(1+log(N/n)))". ACM Transactions on Mathematical Software. 20 (4):
@ -136,7 +132,7 @@ func (r *randRes[N]) Offer(ctx context.Context, t time.Time, n N, a []attribute.
} }
// reset resets r to the initial state. // reset resets r to the initial state.
func (r *randRes[N]) reset() { func (r *randRes) reset() {
// This resets the number of exemplars known. // This resets the number of exemplars known.
r.count = 0 r.count = 0
// Random index inserts should only happen after the storage is full. // Random index inserts should only happen after the storage is full.
@ -158,7 +154,7 @@ func (r *randRes[N]) reset() {
// advance updates the count at which the offered measurement will overwrite an // advance updates the count at which the offered measurement will overwrite an
// existing exemplar. // existing exemplar.
func (r *randRes[N]) advance() { func (r *randRes) advance() {
// Calculate the next value in the random number series. // Calculate the next value in the random number series.
// //
// The current value of r.w is based on the max of a distribution of random // The current value of r.w is based on the max of a distribution of random
@ -185,7 +181,7 @@ func (r *randRes[N]) advance() {
r.next += int64(math.Log(random())/math.Log(1-r.w)) + 1 r.next += int64(math.Log(random())/math.Log(1-r.w)) + 1
} }
func (r *randRes[N]) Collect(dest *[]metricdata.Exemplar[N]) { func (r *randRes) Collect(dest *[]Exemplar) {
r.storage.Collect(dest) r.storage.Collect(dest)
// Call reset here even though it will reset r.count and restart the random // Call reset here even though it will reset r.count and restart the random
// number series. This will persist any old exemplars as long as no new // number series. This will persist any old exemplars as long as no new

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
@ -19,11 +8,10 @@ import (
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
) )
// Reservoir holds the sampled exemplar of measurements made. // Reservoir holds the sampled exemplar of measurements made.
type Reservoir[N int64 | float64] interface { type Reservoir interface {
// Offer accepts the parameters associated with a measurement. The // Offer accepts the parameters associated with a measurement. The
// parameters will be stored as an exemplar if the Reservoir decides to // parameters will be stored as an exemplar if the Reservoir decides to
// sample the measurement. // sample the measurement.
@ -35,10 +23,10 @@ type Reservoir[N int64 | float64] interface {
// The time t is the time when the measurement was made. The val and attr // The time t is the time when the measurement was made. The val and attr
// parameters are the value and dropped (filtered) attributes of the // parameters are the value and dropped (filtered) attributes of the
// measurement respectively. // measurement respectively.
Offer(ctx context.Context, t time.Time, val N, attr []attribute.KeyValue) Offer(ctx context.Context, t time.Time, val Value, attr []attribute.KeyValue)
// Collect returns all the held exemplars. // Collect returns all the held exemplars.
// //
// The Reservoir state is preserved after this call. // The Reservoir state is preserved after this call.
Collect(dest *[]metricdata.Exemplar[N]) Collect(dest *[]Exemplar)
} }

View file

@ -1,16 +1,5 @@
// Copyright The OpenTelemetry Authors // Copyright The OpenTelemetry Authors
// // SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar" package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
@ -19,27 +8,26 @@ import (
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
// storage is an exemplar storage for [Reservoir] implementations. // storage is an exemplar storage for [Reservoir] implementations.
type storage[N int64 | float64] struct { type storage struct {
// store are the measurements sampled. // store are the measurements sampled.
// //
// This does not use []metricdata.Exemplar because it potentially would // This does not use []metricdata.Exemplar because it potentially would
// require an allocation for trace and span IDs in the hot path of Offer. // require an allocation for trace and span IDs in the hot path of Offer.
store []measurement[N] store []measurement
} }
func newStorage[N int64 | float64](n int) *storage[N] { func newStorage(n int) *storage {
return &storage[N]{store: make([]measurement[N], n)} return &storage{store: make([]measurement, n)}
} }
// Collect returns all the held exemplars. // Collect returns all the held exemplars.
// //
// The Reservoir state is preserved after this call. // The Reservoir state is preserved after this call.
func (r *storage[N]) Collect(dest *[]metricdata.Exemplar[N]) { func (r *storage) Collect(dest *[]Exemplar) {
*dest = reset(*dest, len(r.store), len(r.store)) *dest = reset(*dest, len(r.store), len(r.store))
var n int var n int
for _, m := range r.store { for _, m := range r.store {
@ -54,13 +42,13 @@ func (r *storage[N]) Collect(dest *[]metricdata.Exemplar[N]) {
} }
// measurement is a measurement made by a telemetry system. // measurement is a measurement made by a telemetry system.
type measurement[N int64 | float64] struct { type measurement struct {
// FilteredAttributes are the attributes dropped during the measurement. // FilteredAttributes are the attributes dropped during the measurement.
FilteredAttributes []attribute.KeyValue FilteredAttributes []attribute.KeyValue
// Time is the time when the measurement was made. // Time is the time when the measurement was made.
Time time.Time Time time.Time
// Value is the value of the measurement. // Value is the value of the measurement.
Value N Value Value
// SpanContext is the SpanContext active when a measurement was made. // SpanContext is the SpanContext active when a measurement was made.
SpanContext trace.SpanContext SpanContext trace.SpanContext
@ -68,8 +56,8 @@ type measurement[N int64 | float64] struct {
} }
// newMeasurement returns a new non-empty Measurement. // newMeasurement returns a new non-empty Measurement.
func newMeasurement[N int64 | float64](ctx context.Context, ts time.Time, v N, droppedAttr []attribute.KeyValue) measurement[N] { func newMeasurement(ctx context.Context, ts time.Time, v Value, droppedAttr []attribute.KeyValue) measurement {
return measurement[N]{ return measurement{
FilteredAttributes: droppedAttr, FilteredAttributes: droppedAttr,
Time: ts, Time: ts,
Value: v, Value: v,
@ -78,8 +66,8 @@ func newMeasurement[N int64 | float64](ctx context.Context, ts time.Time, v N, d
} }
} }
// Exemplar returns m as a [metricdata.Exemplar]. // Exemplar returns m as an [Exemplar].
func (m measurement[N]) Exemplar(dest *metricdata.Exemplar[N]) { func (m measurement) Exemplar(dest *Exemplar) {
dest.FilteredAttributes = m.FilteredAttributes dest.FilteredAttributes = m.FilteredAttributes
dest.Time = m.Time dest.Time = m.Time
dest.Value = m.Value dest.Value = m.Value

View file

@ -0,0 +1,58 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package exemplar // import "go.opentelemetry.io/otel/sdk/metric/internal/exemplar"
import "math"
// ValueType identifies the type of value used in exemplar data.
type ValueType uint8
const (
// UnknownValueType should not be used. It represents a misconfigured
// Value.
UnknownValueType ValueType = 0
// Int64ValueType represents a Value with int64 data.
Int64ValueType ValueType = 1
// Float64ValueType represents a Value with float64 data.
Float64ValueType ValueType = 2
)
// Value is the value of data held by an exemplar.
type Value struct {
t ValueType
val uint64
}
// NewValue returns a new [Value] for the provided value.
func NewValue[N int64 | float64](value N) Value {
switch v := any(value).(type) {
case int64:
return Value{t: Int64ValueType, val: uint64(v)}
case float64:
return Value{t: Float64ValueType, val: math.Float64bits(v)}
}
return Value{}
}
// Type returns the [ValueType] of data held by v.
func (v Value) Type() ValueType { return v.t }
// Int64 returns the value of v as an int64. If the ValueType of v is not an
// Int64ValueType, 0 is returned.
func (v Value) Int64() int64 {
if v.t == Int64ValueType {
// Assumes the correct int64 was stored in v.val based on type.
return int64(v.val) // nolint: gosec
}
return 0
}
// Float64 returns the value of v as an float64. If the ValueType of v is not
// an Float64ValueType, 0 is returned.
func (v Value) Float64() float64 {
if v.t == Float64ValueType {
return math.Float64frombits(v.val)
}
return 0
}

Some files were not shown because too many files have changed in this diff Show more