mirror of
https://github.com/docker/cli.git
synced 2026-08-24 02:24:17 -05:00
vendor: go.opentelemetry.io/otel v1.44.0, go.opentelemetry.io/contrib v0.69.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
+11
-11
@@ -49,14 +49,14 @@ require (
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
|
||||
go.opentelemetry.io/otel/metric v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0
|
||||
go.opentelemetry.io/otel v1.44.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0
|
||||
go.opentelemetry.io/otel/metric v1.44.0
|
||||
go.opentelemetry.io/otel/sdk v1.44.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0
|
||||
go.opentelemetry.io/otel/trace v1.44.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.45.0
|
||||
@@ -82,7 +82,7 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
@@ -98,13 +98,13 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
go.etcd.io/raft/v3 v3.6.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/grpc v1.81.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
+24
-22
@@ -87,8 +87,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -206,24 +206,26 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
|
||||
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUplec5dp06reu1zaXmOXdvqH398taqrDXqUl99jxSc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -285,8 +287,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
|
||||
+17
-1
@@ -71,6 +71,7 @@ type ServeMux struct {
|
||||
streamErrorHandler StreamErrorHandlerFunc
|
||||
routingErrorHandler RoutingErrorHandlerFunc
|
||||
disablePathLengthFallback bool
|
||||
disableHTTPMethodOverride bool
|
||||
unescapingMode UnescapingMode
|
||||
writeContentLength bool
|
||||
disableChunkedEncoding bool
|
||||
@@ -271,6 +272,19 @@ func WithDisablePathLengthFallback() ServeMuxOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDisableHTTPMethodOverride returns a ServeMuxOption that disables the
|
||||
// X-HTTP-Method-Override header handling.
|
||||
//
|
||||
// When this option is used, the mux will no longer allow POST requests with
|
||||
// the X-HTTP-Method-Override header to override the HTTP method. The path
|
||||
// length fallback (POST with application/x-www-form-urlencoded falling back
|
||||
// to a matching GET handler) is not affected by this option.
|
||||
func WithDisableHTTPMethodOverride() ServeMuxOption {
|
||||
return func(serveMux *ServeMux) {
|
||||
serveMux.disableHTTPMethodOverride = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithWriteContentLength returns a ServeMuxOption to enable writing content length on non-streaming responses
|
||||
func WithWriteContentLength() ServeMuxOption {
|
||||
return func(serveMux *ServeMux) {
|
||||
@@ -405,7 +419,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path = r.URL.RawPath
|
||||
}
|
||||
|
||||
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) {
|
||||
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && !s.disableHTTPMethodOverride && s.isPathLengthFallback(r) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
_, outboundMarshaler := MarshalerForRequest(s, r)
|
||||
sterr := status.Error(codes.InvalidArgument, err.Error())
|
||||
@@ -467,6 +481,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: mse,
|
||||
})
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -509,6 +524,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: mse,
|
||||
})
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
+12
-5
@@ -35,10 +35,6 @@ type middleware struct {
|
||||
semconv semconv.HTTPServer
|
||||
}
|
||||
|
||||
func defaultHandlerFormatter(operation string, _ *http.Request) string {
|
||||
return operation
|
||||
}
|
||||
|
||||
// NewHandler wraps the passed handler in a span named after the operation and
|
||||
// enriches it with metrics.
|
||||
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
|
||||
@@ -55,12 +51,17 @@ func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Han
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||
WithSpanNameFormatter(defaultHandlerFormatter),
|
||||
}
|
||||
|
||||
c := newConfig(append(defaultOpts, opts...)...)
|
||||
h.configure(c)
|
||||
|
||||
if h.spanNameFormatter == nil {
|
||||
h.spanNameFormatter = func(_ string, r *http.Request) string {
|
||||
return h.semconv.SpanName(r)
|
||||
}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveHTTP(w, r, next)
|
||||
@@ -138,7 +139,13 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
|
||||
// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
|
||||
bw := request.NewBodyWrapper(r.Body, readRecordFunc)
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
origReq := r
|
||||
prevBody := r.Body
|
||||
r.Body = bw
|
||||
|
||||
// Restore the original body after the request is processed to avoid issues
|
||||
// with extra wrapper since `http/server.go` later checks type of `r.Body`.
|
||||
defer func() { origReq.Body = prevBody }()
|
||||
}
|
||||
|
||||
writeRecordFunc := func(int64) {}
|
||||
|
||||
Generated
Vendored
+41
-4
@@ -12,6 +12,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -19,8 +20,8 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/httpconv"
|
||||
)
|
||||
|
||||
type HTTPClient struct {
|
||||
@@ -166,7 +167,7 @@ func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue
|
||||
|
||||
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
return semconv.HTTPRequestMethodOther, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
@@ -176,7 +177,7 @@ func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValu
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconv.HTTPRequestMethodGet, orig
|
||||
return semconv.HTTPRequestMethodOther, orig
|
||||
}
|
||||
|
||||
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
@@ -249,6 +250,9 @@ func (o MetricOpts) AddOptions() metric.AddOption {
|
||||
|
||||
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
||||
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
||||
if ma.StatusCode == 0 && ma.Err != nil {
|
||||
attributes = append(attributes, n.ErrorType(ma.Err))
|
||||
}
|
||||
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
|
||||
return MetricOpts{
|
||||
@@ -257,6 +261,39 @@ func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorType returns an error.type attribute for the given error. The otelhttp
|
||||
// Transport calls the underlying RoundTripper directly, so transport failures
|
||||
// arrive as *net.OpError (connection refused, timeout, etc.) rather than
|
||||
// *url.Error (which http.Client.Do adds above the Transport layer). This
|
||||
// function intentionally does not unwrap further: reporting a single concrete
|
||||
// type per failure keeps attribute cardinality bounded, as required by the
|
||||
// OTel spec (error.type SHOULD have low cardinality). Callers that need
|
||||
// finer-grained error distinctions should inspect the error themselves.
|
||||
func (n HTTPClient) ErrorType(err error) attribute.KeyValue {
|
||||
t := reflect.TypeOf(err)
|
||||
if t == nil {
|
||||
return semconv.ErrorTypeOther
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
var value string
|
||||
if t.PkgPath() == "" || t.Name() == "" {
|
||||
// t.PkgPath() == "" covers builtin and unnamed types.
|
||||
// t.Name() == "" covers anonymous struct types that implement error,
|
||||
// which are uncommon but possible. Fall back to t.String() for both.
|
||||
value = t.String()
|
||||
} else {
|
||||
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
return semconv.ErrorTypeOther
|
||||
}
|
||||
|
||||
return semconv.ErrorTypeKey.String(value)
|
||||
}
|
||||
|
||||
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
|
||||
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
||||
defer func() {
|
||||
|
||||
Generated
Vendored
+28
-11
@@ -20,8 +20,8 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/httpconv"
|
||||
)
|
||||
|
||||
type RequestTraceAttrsOpts struct {
|
||||
@@ -243,6 +243,7 @@ type MetricAttributes struct {
|
||||
StatusCode int
|
||||
Route string
|
||||
AdditionalAttributes []attribute.KeyValue
|
||||
Err error
|
||||
}
|
||||
|
||||
type MetricData struct {
|
||||
@@ -250,13 +251,11 @@ type MetricData struct {
|
||||
RequestDuration time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
metricRecordOptionPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &[]metric.RecordOption{}
|
||||
},
|
||||
}
|
||||
)
|
||||
var metricRecordOptionPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &[]metric.RecordOption{}
|
||||
},
|
||||
}
|
||||
|
||||
func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
||||
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
|
||||
@@ -270,9 +269,27 @@ func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
||||
metricRecordOptionPool.Put(recordOpts)
|
||||
}
|
||||
|
||||
// SpanName returns the span name for an HTTP request following the
|
||||
// OpenTelemetry HTTP semantic conventions.
|
||||
// It returns "{method} {route}" when the request has a pattern,
|
||||
// or just "{method}" when no route is available.
|
||||
// Non-standard HTTP methods are replaced by "HTTP".
|
||||
func (n HTTPServer) SpanName(r *http.Request) string {
|
||||
method := strings.ToUpper(r.Method)
|
||||
if _, ok := methodLookup[method]; !ok {
|
||||
method = "HTTP"
|
||||
}
|
||||
|
||||
route := httpRoute(r.Pattern)
|
||||
if route != "" {
|
||||
return method + " " + route
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||
if method == "" {
|
||||
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
||||
return semconv.HTTPRequestMethodOther, attribute.KeyValue{}
|
||||
}
|
||||
if attr, ok := methodLookup[method]; ok {
|
||||
return attr, attribute.KeyValue{}
|
||||
@@ -282,7 +299,7 @@ func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValu
|
||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||
return attr, orig
|
||||
}
|
||||
return semconv.HTTPRequestMethodGet, orig
|
||||
return semconv.HTTPRequestMethodOther, orig
|
||||
}
|
||||
|
||||
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
// SplitHostPort splits a network address hostport of the form "host",
|
||||
|
||||
Generated
Vendored
+2
-1
@@ -15,7 +15,7 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
otelsemconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
otelsemconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
@@ -161,6 +161,7 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
t.semconv.MetricOptions(semconv.MetricAttributes{
|
||||
Req: r,
|
||||
StatusCode: statusCode,
|
||||
Err: err,
|
||||
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
|
||||
}),
|
||||
)
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
// Version is the current release version of the otelhttp instrumentation.
|
||||
const Version = "0.68.0"
|
||||
const Version = "0.69.0"
|
||||
|
||||
+11
-7
@@ -96,9 +96,9 @@ linters:
|
||||
- "!**/exporters/zipkin/**"
|
||||
deny:
|
||||
- pkg: go.opentelemetry.io/otel/semconv
|
||||
desc: "Use go.opentelemetry.io/otel/semconv/v1.40.0 instead. If a newer semconv version has been released, update the depguard rule."
|
||||
desc: "Use go.opentelemetry.io/otel/semconv/v1.41.0 instead. If a newer semconv version has been released, update the depguard rule."
|
||||
allow:
|
||||
- go.opentelemetry.io/otel/semconv/v1.40.0
|
||||
- go.opentelemetry.io/otel/semconv/v1.41.0
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
@@ -134,13 +134,16 @@ linters:
|
||||
strconcat: true
|
||||
revive:
|
||||
confidence: 0.01
|
||||
enable-all-rules: false
|
||||
enable-default-rules: true
|
||||
max-open-files: 2048
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: bool-literal-in-expr
|
||||
- name: constant-logical-expr
|
||||
- name: context-as-argument
|
||||
arguments:
|
||||
- allowTypesBefore: '*testing.T'
|
||||
- allow-types-before: '*testing.T'
|
||||
disabled: true
|
||||
- name: context-keys-type
|
||||
- name: deep-exit
|
||||
@@ -152,7 +155,7 @@ linters:
|
||||
- name: duplicated-imports
|
||||
- name: early-return
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: empty-block
|
||||
- name: empty-lines
|
||||
- name: error-naming
|
||||
@@ -161,7 +164,7 @@ linters:
|
||||
- name: errorf
|
||||
- name: exported
|
||||
arguments:
|
||||
- sayRepetitiveInsteadOfStutters
|
||||
- say-repetitive-instead-of-stutters
|
||||
- name: flag-parameter
|
||||
- name: identical-branches
|
||||
- name: if-return
|
||||
@@ -169,11 +172,12 @@ linters:
|
||||
- name: increment-decrement
|
||||
- name: indent-error-flow
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: range-val-in-closure
|
||||
- name: range-val-address
|
||||
- name: receiver-naming
|
||||
- name: redefines-builtin-id
|
||||
- name: string-format
|
||||
arguments:
|
||||
@@ -183,7 +187,7 @@ linters:
|
||||
- name: struct-tag
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: time-equal
|
||||
- name: unconditional-recursion
|
||||
- name: unexported-return
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Agent Guide for opentelemetry-go
|
||||
|
||||
This file contains active, task-oriented instructions for autonomous and semi-autonomous coding agents working in this repository.
|
||||
|
||||
Before starting any task, read `.github/copilot-instructions.md`, `CONTRIBUTING.md`, and this file.
|
||||
Treat `.github/copilot-instructions.md` as global passive guidance for every task, including docs-only and review-only work.
|
||||
|
||||
## Core expectations
|
||||
|
||||
- Preserve OpenTelemetry specification compliance, API stability, and idiomatic Go.
|
||||
- Prefer minimal, surgical changes over broad refactors or speculative cleanup.
|
||||
- Read the package you are editing and match its existing naming, option types, error handling, comments, tests, and concurrency patterns.
|
||||
- Keep public APIs backward compatible unless the task explicitly requires a breaking change.
|
||||
- Keep telemetry resilient and loosely coupled. Do not introduce behavior that can unexpectedly interfere with host applications.
|
||||
- Inspect boundaries carefully: input validation, resource limits, cancellation, shutdown, error propagation, concurrency, and memory growth.
|
||||
- Prefer fail-safe behavior and explicit invariants over implicit assumptions.
|
||||
- Keep dependencies minimal and justified.
|
||||
- Preserve host-application safety: telemetry should not panic, block indefinitely, or amplify attacker-controlled input.
|
||||
- Be conservative on hot paths. Avoid unnecessary allocations, reflection, interface churn, blocking, global state, and high-cardinality telemetry.
|
||||
- Write comments only for intent, invariants, and non-obvious constraints. Do not add comments that restate the code.
|
||||
|
||||
## Default workflow
|
||||
|
||||
For new features and behavior changes, use this order unless the task explicitly says otherwise:
|
||||
|
||||
1. Read the relevant package, its tests, and any package docs or `README.md`.
|
||||
2. Add or update a failing unit test that captures the required behavior or regression.
|
||||
3. Implement the smallest change that makes the test pass.
|
||||
4. Refactor only after the behavior is locked in, and only if the refactor keeps the diff focused.
|
||||
5. If the changed code is on a hot path or performance-sensitive, inspect existing benchmarks and run them. Add a benchmark if coverage is missing.
|
||||
6. Update documentation artifacts as needed while the context is fresh. Follow the documentation and changelog conventions below for the specific updates required.
|
||||
7. Run `make precommit` each time before considering the work complete.
|
||||
|
||||
For docs-only, test-only, or review-only tasks, still start with the required repository guidance above, then skip the workflow steps that do not apply while keeping the same discipline around scope, verification, and repository conventions.
|
||||
|
||||
## Verification
|
||||
|
||||
- Use `make` as the canonical repository verification command. The default target is `precommit`.
|
||||
- `make precommit` is the expected final verification step for linting, generation, README checks, module checks, and tests.
|
||||
- During iteration, targeted commands are fine for fast feedback, but do not stop there if the task changes code.
|
||||
- If you touch performance-sensitive code, run focused benchmarks and compare the results using `benchstat` in addition to `make`.
|
||||
|
||||
## Documentation and changelog
|
||||
|
||||
- Non-internal, non-test packages should have Go doc comments, usually in `doc.go`.
|
||||
- Non-internal, non-test, non-documentation packages should also have a `README.md` with at least a title and a `pkg.go.dev` badge.
|
||||
- Prefer examples over long code snippets in GoDoc when practical.
|
||||
- Keep docs aligned with actual behavior. Do not leave stale comments, stale examples, or stale package documentation behind.
|
||||
- For user-visible changes, update `CHANGELOG.md` under the appropriate `Added`, `Changed`, `Deprecated`, `Fixed`, or `Removed` section within `## [Unreleased]`.
|
||||
|
||||
## Repository habits
|
||||
|
||||
- Prefer focused diffs. Avoid drive-by cleanup.
|
||||
- Follow existing option patterns and exported API conventions instead of inventing new abstractions.
|
||||
- Generated files are checked in. If your change affects generation, keep generated output up to date.
|
||||
- Prefer fast local search tools such as `rg` when exploring the repository.
|
||||
- When changing behavior, make the invariants explicit in tests.
|
||||
|
||||
## Personas
|
||||
|
||||
### Feature Agent
|
||||
|
||||
Use this persona for new behavior, new API surface, or spec-driven feature work.
|
||||
|
||||
- Start with a failing unit test.
|
||||
- Confirm the expected behavior against the spec, existing package behavior, and public API compatibility.
|
||||
- Implement the smallest viable change.
|
||||
- Update GoDoc, examples, `README.md`, and `CHANGELOG.md` when the change is user-visible.
|
||||
- If the feature touches a hot path, check benchmarks and add one if the coverage is missing.
|
||||
|
||||
### Refactoring Agent
|
||||
|
||||
Use this persona when improving structure without intentionally changing behavior.
|
||||
|
||||
- Treat behavior preservation as the default contract.
|
||||
- Add or tighten tests before moving code if current behavior is not already pinned down.
|
||||
- Avoid broad rewrites, clever abstractions, or package-wide cleanup unless explicitly requested.
|
||||
- If a refactor touches a hot path, benchmark before and after.
|
||||
- Keep API shape, semantics, concurrency guarantees, and failure modes unchanged unless the task says otherwise.
|
||||
|
||||
### Test Agent
|
||||
|
||||
Use this persona when adding missing coverage, reproducing bugs, or hardening regressions.
|
||||
|
||||
- Reproduce the bug or missing behavior with the smallest failing test you can.
|
||||
- Prefer testing public behavior and externally visible invariants.
|
||||
- Add targeted regression tests before changing production code.
|
||||
- Only change production code when it is required to make the tested behavior correct or testable.
|
||||
- Keep tests deterministic, readable, and aligned with package patterns.
|
||||
|
||||
### Performance Agent
|
||||
|
||||
Use this persona for hot-path work, allocation reduction, or throughput and latency improvements.
|
||||
|
||||
- Benchmark first to establish a baseline.
|
||||
- Prefer changes that reduce allocations, copying, interface churn, and unnecessary synchronization.
|
||||
- Do not trade away correctness, spec compliance, or API stability for micro-optimizations.
|
||||
- Add or update benchmarks when performance-sensitive coverage is missing.
|
||||
- If you materially change a hot path, capture before-and-after results, preferably with `benchstat`.
|
||||
|
||||
### Review Agent
|
||||
|
||||
Use this persona when asked to review code, patches, or pull requests.
|
||||
|
||||
- Lead with findings, not summaries.
|
||||
- Order findings by severity and include precise file and line references when available.
|
||||
- Focus on correctness, spec compliance, API compatibility, concurrency safety, resilience, performance regressions, missing tests, missing benchmarks, documentation gaps, and changelog gaps.
|
||||
- Call out when a diff is broader than necessary.
|
||||
- If you find no issues, say that explicitly and note any residual risks or verification gaps.
|
||||
+96
-1
@@ -11,6 +11,100 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
<!-- Released section -->
|
||||
<!-- Don't change this section unless doing release -->
|
||||
|
||||
## [1.44.0/0.66.0/0.20.0/0.0.17] 2026-05-27
|
||||
|
||||
### Added
|
||||
|
||||
- Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948)
|
||||
- Apply attribute value limit to the `KindBytes` attribute type in `go.opentelemetry.io/otel/sdk/log`. (#7990)
|
||||
- Apply attribute value limit to the `BYTESLICE` attribute type in `go.opentelemetry.io/otel/sdk/trace`. (#7990)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/trace`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8153)
|
||||
- Add `String` method for `Value` type in `go.opentelemetry.io/otel/attribute`. (#8142)
|
||||
- Add `Slice` and `SliceValue` functions for new `SLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#8166)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8216)
|
||||
- Apply `AttributeValueLengthLimit` to `attribute.SLICE` type attribute values in `go.opentelemetry.io/otel/sdk/trace`, recursively truncating contained string values. (#8217)
|
||||
- Add `Error` field on `Record` type in `go.opentelemetry.io/otel/log/logtest`. (#8148)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8157)
|
||||
- Add `Settable` to `go.opentelemetry.io/otel/metric/x` to allow reusing attribute options. (#8178)
|
||||
- Add experimental support for splitting metric data across multiple batches in `go.opentelemetry.io/otel/sdk/metric`.
|
||||
Set `OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE=<max_size>` to enable for all periodic readers.
|
||||
See `go.opentelemetry.io/otel/sdk/metric/internal/x` for feature documentation. (#8071)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x` for feature documentation. (#8192)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/x` for feature documentation. (#8194)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/stdout/stdoutlog/internal/x` for feature documentation. (#8263)
|
||||
- Add `WithDefaultAttributes` to `go.opentelemetry.io/otel/metric/x` to support setting default attributes on instruments. (#8135)
|
||||
- Add `go.opentelemetry.io/otel/semconv/v1.41.0` package.
|
||||
The package contains semantic conventions from the `v1.41.0` version of the OpenTelemetry Semantic Conventions.
|
||||
See the [migration documentation](./semconv/v1.41.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.40.0`. (#8324)
|
||||
- Add Observable variants of instruments to `go.opentelemetry.io/otel/semconv/v1.41.0` package. (#8350)
|
||||
- Generate explicit histogram bucket boundaries from weaver configuration for HTTP and RPC duration instruments in `go.opentelemetry.io/otel/semconv/v1.41.0`. (#8002)
|
||||
|
||||
### Changed
|
||||
|
||||
- ⚠️ **Breaking Change:** `go.opentelemetry.io/otel/sdk/metric` now applies a default cardinality limit of 2000 to comply with the Metrics SDK specification recommendation.
|
||||
New attribute sets are dropped when the cardinality limit is reached. The measurement of these sets are aggregated into a special attribute set containing `attribute.Bool("otel.metric.overflow", true)`.
|
||||
This can break users who relied on the previous unlimited default.
|
||||
Set `WithCardinalityLimit(0)` or the deprecated `OTEL_GO_X_CARDINALITY_LIMIT=0` environment variable to preserve unlimited cardinality.
|
||||
Note that support for `OTEL_GO_X_CARDINALITY_LIMIT` may be removed in a future release. (#8247)
|
||||
- `ErrorType` in `go.opentelemetry.io/otel/semconv` now unwraps errors created with `fmt.Errorf` when deriving the `error.type` attribute. (#8133)
|
||||
- `go.opentelemetry.io/otel/sdk/log` now unwraps error chains created with `fmt.Errorf` when deriving the `error.type` attribute from errors on log records. (#8133)
|
||||
- `Set.MarshalLog` method in `go.opentelemetry.io/otel/attribute` now uses `Value.String` formatting following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8169)
|
||||
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir and short-circuit `Offer` calls to the exemplar reservoir when `exemplar.AlwaysOffFilter` is configured. (#8211) (#8267)
|
||||
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir for asynchronous instruments when `exemplar.TraceBasedFilter` is configured. (#8286)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `Value.Emit` method in `go.opentelemetry.io/otel/attribute`.
|
||||
Use `Value.String` instead. (#8176)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8135)
|
||||
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8152)
|
||||
- `go.opentelemetry.io/otel/exporters/prometheus` now uses `Value.String` formatting for label values following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8170)
|
||||
- Propagate errors from the exporter when calling `Shutdown` on `BatchSpanProcessor` in `go.opentelemetry.io/otel/sdk/trace`. (#8197)
|
||||
- Fix stale status code reporting on self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8226)
|
||||
- Fix a concurrent `Collect` data race and potential panic in `go.opentelemetry.io/otel/exporters/prometheus` when `WithResourceAsConstantLabels` option is used. (#8227)
|
||||
- Fix race condition in `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` by reverting #7447. (#8249)
|
||||
- Fix `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` to safely handle zero size.
|
||||
A capacity check in the constructor initializes the reservoir safely and skips initialization for zero-cap; early returns in `Offer()` and `Collect()` ensure no-op behavior. (#8295)
|
||||
- Fix counting of spans and logs in self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`, and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8254)
|
||||
- Drop conflicting scope attributes named `name`, `version`, or `schema_url` from metric labels in `go.opentelemetry.io/otel/exporters/prometheus`, preserving the dedicated `otel_scope_name`, `otel_scope_version`, and `otel_scope_schema_url` labels. (#8264)
|
||||
- Close schema files opened by `ParseFile` in `go.opentelemetry.io/otel/schema/v1.0` and `go.opentelemetry.io/otel/schema/v1.1`. ([GHSA-995v-fvrw-c78m](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-995v-fvrw-c78m))
|
||||
- Enforce the 8192-byte baggage size limit during extraction/parsing, changing behavior when the limit is exceeded in `go.opentelemetry.io/otel/baggage` and `go.opentelemetry.io/otel/propagation`. (#8222)
|
||||
- Fix `go.opentelemetry.io/otel/semconv/v1.41.0` to include `Attr*` helper methods for required attributes on observable instruments. (#8361)
|
||||
- Limit baggage extraction error reporting in `go.opentelemetry.io/otel/propagation` to prevent malformed or oversized baggage headers from flooding logs. ([GHSA-5wrp-cwcj-q835](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-5wrp-cwcj-q835))
|
||||
|
||||
## [1.43.0/0.65.0/0.19.0] 2026-04-02
|
||||
|
||||
### Added
|
||||
@@ -3619,7 +3713,8 @@ It contains api and sdk for trace and meter.
|
||||
- CircleCI build CI manifest files.
|
||||
- CODEOWNERS file to track owners of this project.
|
||||
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.43.0...HEAD
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...HEAD
|
||||
[1.44.0/0.66.0/0.20.0/0.0.17]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.44.0
|
||||
[1.43.0/0.65.0/0.19.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.43.0
|
||||
[1.42.0/0.64.0/0.18.0/0.0.16]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.42.0
|
||||
[1.41.0/0.63.0/0.17.0/0.0.15]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.41.0
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Instructions for Claude Code
|
||||
|
||||
@AGENTS.md
|
||||
+89
-12
@@ -11,6 +11,12 @@ for a summary description of past meetings. To request edit access,
|
||||
join the meeting or get in touch on
|
||||
[Slack](https://cloud-native.slack.com/archives/C01NPAXACKT).
|
||||
|
||||
The meeting is open for all to join. We invite everyone to join our
|
||||
meeting, regardless of your experience level. Whether you're a
|
||||
seasoned OpenTelemetry developer, just starting your journey, or
|
||||
simply curious about the work we do, you're more than welcome to
|
||||
participate!
|
||||
|
||||
## Development
|
||||
|
||||
You can view and edit the source code by cloning this repository:
|
||||
@@ -746,8 +752,8 @@ Encapsulate setup in constructor functions, ensuring clear ownership and scope:
|
||||
import (
|
||||
"errors"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
type SDKComponent struct {
|
||||
@@ -808,11 +814,11 @@ func (c *Component) initObservability() {
|
||||
|
||||
#### Performance
|
||||
|
||||
When observability is disabled there should be little to no overhead.
|
||||
When observability is disabled or the instrument is not `Enabled`, there should be little to no overhead.
|
||||
|
||||
```go
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
||||
if e.inst != nil {
|
||||
if e.inst != nil && e.inst.Enabled(ctx) {
|
||||
attrs := expensiveOperation()
|
||||
e.inst.recordSpanInflight(ctx, int64(len(spans)), attrs...)
|
||||
}
|
||||
@@ -829,7 +835,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
||||
}
|
||||
|
||||
func (i *instrumentation) recordSpanInflight(ctx context.Context, count int64, attrs ...attribute.KeyValue) {
|
||||
if i == nil || i.inflight == nil {
|
||||
if i == nil || i.inflight == nil || !i.inflight.Enabled(ctx) {
|
||||
return
|
||||
}
|
||||
i.inflight.Add(ctx, count, metric.WithAttributes(attrs...))
|
||||
@@ -865,8 +871,12 @@ var (
|
||||
)
|
||||
|
||||
func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...attribute.KeyValue) {
|
||||
if !i.counter.Enabled(ctx) {
|
||||
return
|
||||
}
|
||||
attrs := attrPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
clear(*attrs) // Clear references to strings/etc to let GC collect them.
|
||||
*attrs = (*attrs)[:0] // Reset.
|
||||
attrPool.Put(attrs)
|
||||
}()
|
||||
@@ -877,6 +887,7 @@ func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...
|
||||
|
||||
addOpt := addOptPool.Get().(*[]metric.AddOption)
|
||||
defer func() {
|
||||
clear(*addOpt)
|
||||
*addOpt = (*addOpt)[:0]
|
||||
addOptPool.Put(addOpt)
|
||||
}()
|
||||
@@ -1007,16 +1018,20 @@ Ensure observability measurements receive the correct context, especially for tr
|
||||
```go
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
||||
// Use the provided context for observability measurements
|
||||
e.inst.recordSpanExportStarted(ctx, len(spans))
|
||||
if e.inst.Enabled(ctx) {
|
||||
e.inst.recordSpanExportStarted(ctx, len(spans))
|
||||
}
|
||||
|
||||
err := e.doExport(ctx, spans)
|
||||
|
||||
if err != nil {
|
||||
e.inst.recordSpanExportFailed(ctx, len(spans), err)
|
||||
} else {
|
||||
e.inst.recordSpanExportSucceeded(ctx, len(spans))
|
||||
if e.inst.Enabled(ctx) {
|
||||
if err != nil {
|
||||
e.inst.recordSpanExportFailed(ctx, len(spans), err)
|
||||
} else {
|
||||
e.inst.recordSpanExportSucceeded(ctx, len(spans))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return err
|
||||
}
|
||||
```
|
||||
@@ -1039,7 +1054,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
||||
|
||||
All observability metrics should follow the [OpenTelemetry Semantic Conventions for SDK metrics](https://github.com/open-telemetry/semantic-conventions/blob/1cf2476ae5e518225a766990a28a6d5602bd5a30/docs/otel/sdk-metrics.md).
|
||||
|
||||
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.40.0/otelconv/metric.go).
|
||||
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.41.0/otelconv/metric.go).
|
||||
|
||||
##### Component Identification
|
||||
|
||||
@@ -1109,6 +1124,68 @@ func TestObservability(t *testing.T) {
|
||||
Test order should not affect results.
|
||||
Ensure that any global state (e.g. component ID counters) is reset between tests.
|
||||
|
||||
### Experimental Features
|
||||
|
||||
To support the development of new features in the specification, we use the following patterns to implement in-development features without adding new public artifacts in stable modules.
|
||||
|
||||
#### Experimental behavior with no API artifacts
|
||||
|
||||
Features that change behavior without changing the API (e.g., exemplar collection, auto-generation of identifiers) are implemented behind a feature gate.
|
||||
The implementation resides in an `/internal/x` package and is activated through environment variables with the `OTEL_GO_X_` prefix (e.g., `OTEL_GO_X_OBSERVABILITY`).
|
||||
The feature must be documented in a `README.md` file in the `/internal/x` package.
|
||||
|
||||
#### Experimental methods on SDK-only interfaces
|
||||
|
||||
Features that require new methods on SDK interfaces are defined as a new interface in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
|
||||
The SDK must not depend on the experimental module.
|
||||
|
||||
#### Experimental structs, functions, or interfaces
|
||||
|
||||
Features that don't need any changes to the existing stable package are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
|
||||
#### Experimental signals and components
|
||||
|
||||
New telemetry signals (e.g., Logs before stabilization) and components (e.g. bridges) are hosted in new, unstable modules (e.g., `go.opentelemetry.io/otel/log` before 1.0.0).
|
||||
The package should have the final name it will use once stabilized (i.e. not `/x`), and is released at a v0.x.y version to indicate it is not stable.
|
||||
Most new components are hosted in [opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib).
|
||||
|
||||
#### Experimental options for API or SDK functions
|
||||
|
||||
Experimental Options functions are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
The return type of the Option function must embed the option's type (e.g. `metric.InstrumentOption`), and have an `Experimental()` method to prevent the API from panicking when the option is used.
|
||||
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
|
||||
The SDK must not depend on the experimental module.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
type myOption struct {
|
||||
// Embed the stable option type.
|
||||
metric.InstrumentOption
|
||||
value string
|
||||
}
|
||||
|
||||
// Experimental prevents the API from panicking when the option is used.
|
||||
func (o myOption) Experimental() {}
|
||||
|
||||
// The SDK can use type assertions to use this function.
|
||||
func (o myOption) Value() string { return o.value }
|
||||
|
||||
func WithMyOption(value string) metric.InstrumentOption {
|
||||
return myOption{value: value}
|
||||
}
|
||||
```
|
||||
|
||||
#### Not Supported
|
||||
|
||||
The following kinds of experimental features are **not currently supported** on stable interfaces:
|
||||
|
||||
- Experimental methods on API interfaces
|
||||
- Experimental fields for API or SDK exported structs
|
||||
|
||||
In some cases forks or long-lived branches may be used for prototyping these features.
|
||||
|
||||
## Approvers and Maintainers
|
||||
|
||||
### Maintainers
|
||||
|
||||
+9
-1
@@ -191,8 +191,16 @@ benchmark: $(OTEL_GO_MOD_DIRS:%=benchmark/%)
|
||||
benchmark/%:
|
||||
cd $* && $(GO) test -run='^$$' -bench=. $(ARGS) ./...
|
||||
|
||||
# sdk/metric is split into two shards to work around CodSpeed limitations.
|
||||
# See https://github.com/CodSpeedHQ/codspeed-go/issues/56
|
||||
BENCHMARK_SHARDS := $(filter-out ./sdk/metric,$(OTEL_GO_MOD_DIRS)) ./sdk/metric/root ./sdk/metric/internal
|
||||
benchmark/./sdk/metric/root:
|
||||
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) . ./exemplar/...
|
||||
benchmark/./sdk/metric/internal:
|
||||
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) ./internal/...
|
||||
|
||||
print-sharded-benchmarks:
|
||||
@echo $(OTEL_GO_MOD_DIRS) | jq -cR 'split(" ")'
|
||||
@echo $(BENCHMARK_SHARDS) | jq -cR 'split(" ")'
|
||||
|
||||
.PHONY: golangci-lint golangci-lint-fix
|
||||
golangci-lint-fix: ARGS=--fix
|
||||
|
||||
+3
-1
@@ -105,7 +105,9 @@ func (d *defaultAttrEncoder) Encode(iter Iterator) string {
|
||||
if keyValue.Value.Type() == STRING {
|
||||
copyAndEscape(buf, keyValue.Value.AsString())
|
||||
} else {
|
||||
_, _ = buf.WriteString(keyValue.Value.Emit())
|
||||
_, _ = buf.WriteString(
|
||||
keyValue.Value.Emit(),
|
||||
) //nolint:staticcheck // Preserve the existing default encoder output.
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
|
||||
+47
-11
@@ -27,6 +27,8 @@ const (
|
||||
int64SliceID uint64 = 3762322556277578591 // "_[]int64" (little endian)
|
||||
float64SliceID uint64 = 7308324551835016539 // "[]double" (little endian)
|
||||
stringSliceID uint64 = 7453010373645655387 // "[]string" (little endian)
|
||||
byteSliceID uint64 = 6874028470941080415 // "_[]byte_" (little endian)
|
||||
sliceID uint64 = 7883494272577650031 // "__slice_" (little endian)
|
||||
emptyID uint64 = 7305809155345288421 // "__empty_" (little endian)
|
||||
)
|
||||
|
||||
@@ -42,53 +44,87 @@ func hashKVs(kvs []KeyValue) uint64 {
|
||||
// hashKV returns the xxHash64 hash of kv with h as the base.
|
||||
func hashKV(h xxhash.Hash, kv KeyValue) xxhash.Hash {
|
||||
h = h.String(string(kv.Key))
|
||||
return hashValue(h, kv.Value)
|
||||
}
|
||||
|
||||
switch kv.Value.Type() {
|
||||
func hashValue(h xxhash.Hash, v Value) xxhash.Hash {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
h = h.Uint64(boolID)
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case INT64:
|
||||
h = h.Uint64(int64ID)
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case FLOAT64:
|
||||
h = h.Uint64(float64ID)
|
||||
// Assumes numeric stored with math.Float64bits.
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case STRING:
|
||||
h = h.Uint64(stringID)
|
||||
h = h.String(kv.Value.stringly)
|
||||
h = h.String(v.stringly)
|
||||
case BOOLSLICE:
|
||||
h = h.Uint64(boolSliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Bool(rv.Index(i).Bool())
|
||||
}
|
||||
case INT64SLICE:
|
||||
h = h.Uint64(int64SliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Int64(rv.Index(i).Int())
|
||||
}
|
||||
case FLOAT64SLICE:
|
||||
h = h.Uint64(float64SliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Float64(rv.Index(i).Float())
|
||||
}
|
||||
case STRINGSLICE:
|
||||
h = h.Uint64(stringSliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.String(rv.Index(i).String())
|
||||
}
|
||||
case BYTESLICE:
|
||||
h = h.Uint64(byteSliceID)
|
||||
h = h.String(v.stringly)
|
||||
case SLICE:
|
||||
h = h.Uint64(sliceID)
|
||||
switch vals := v.slice.(type) {
|
||||
case [0]Value:
|
||||
// No values to hash, but the type identifier is still hashed above.
|
||||
case [1]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [2]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [3]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [4]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [5]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
default:
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = hashValue(h, rv.Index(i).Interface().(Value))
|
||||
}
|
||||
}
|
||||
case EMPTY:
|
||||
h = h.Uint64(emptyID)
|
||||
default:
|
||||
// Logging is an alternative, but using the internal logger here
|
||||
// causes an import cycle so it is not done.
|
||||
v := kv.Value.AsInterface()
|
||||
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", v)
|
||||
val := v.AsInterface()
|
||||
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", val)
|
||||
panic(msg)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func hashValueSlice(h xxhash.Hash, vals []Value) xxhash.Hash {
|
||||
for _, v := range vals {
|
||||
h = hashValue(h, v)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
+22
@@ -117,6 +117,28 @@ func (k Key) StringSlice(v []string) KeyValue {
|
||||
}
|
||||
}
|
||||
|
||||
// ByteSlice creates a KeyValue instance with a BYTESLICE Value.
|
||||
//
|
||||
// If creating both a key and value at the same time, use the provided
|
||||
// convenience function instead -- ByteSlice(name, value).
|
||||
func (k Key) ByteSlice(v []byte) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: ByteSliceValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Slice creates a KeyValue instance with a SLICE Value.
|
||||
//
|
||||
// If creating both a key and value at the same time, use the provided
|
||||
// convenience function instead -- Slice(name, values...).
|
||||
func (k Key) Slice(v ...Value) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: SliceValue(v...),
|
||||
}
|
||||
}
|
||||
|
||||
// Defined reports whether the key is not empty.
|
||||
func (k Key) Defined() bool {
|
||||
return len(k) != 0
|
||||
|
||||
+10
@@ -68,6 +68,16 @@ func StringSlice(k string, v []string) KeyValue {
|
||||
return Key(k).StringSlice(v)
|
||||
}
|
||||
|
||||
// ByteSlice creates a KeyValue with a BYTESLICE Value type.
|
||||
func ByteSlice(k string, v []byte) KeyValue {
|
||||
return Key(k).ByteSlice(v)
|
||||
}
|
||||
|
||||
// Slice creates a KeyValue with a SLICE Value type.
|
||||
func Slice(k string, v ...Value) KeyValue {
|
||||
return Key(k).Slice(v...)
|
||||
}
|
||||
|
||||
// Stringer creates a new key-value pair with a passed name and a string
|
||||
// value generated by the passed Stringer interface.
|
||||
func Stringer(k string, v fmt.Stringer) KeyValue {
|
||||
|
||||
+2
-2
@@ -401,7 +401,7 @@ func computeDataFixed(kvs []KeyValue) any {
|
||||
func computeDataReflect(kvs []KeyValue) any {
|
||||
at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem()
|
||||
for i, keyValue := range kvs {
|
||||
*(at.Index(i).Addr().Interface().(*KeyValue)) = keyValue
|
||||
*at.Index(i).Addr().Interface().(*KeyValue) = keyValue
|
||||
}
|
||||
return at.Interface()
|
||||
}
|
||||
@@ -415,7 +415,7 @@ func (l *Set) MarshalJSON() ([]byte, error) {
|
||||
func (l Set) MarshalLog() any {
|
||||
kvs := make(map[string]string)
|
||||
for _, kv := range l.ToSlice() {
|
||||
kvs[string(kv.Key)] = kv.Value.Emit()
|
||||
kvs[string(kv.Key)] = kv.Value.String()
|
||||
}
|
||||
return kvs
|
||||
}
|
||||
|
||||
+4
-2
@@ -17,11 +17,13 @@ func _() {
|
||||
_ = x[INT64SLICE-6]
|
||||
_ = x[FLOAT64SLICE-7]
|
||||
_ = x[STRINGSLICE-8]
|
||||
_ = x[BYTESLICE-9]
|
||||
_ = x[SLICE-10]
|
||||
}
|
||||
|
||||
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE"
|
||||
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICEBYTESLICESLICE"
|
||||
|
||||
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69}
|
||||
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69, 78, 83}
|
||||
|
||||
func (i Type) String() string {
|
||||
idx := int(i) - 0
|
||||
|
||||
+742
@@ -4,9 +4,14 @@
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
attribute "go.opentelemetry.io/otel/attribute/internal"
|
||||
)
|
||||
@@ -45,6 +50,10 @@ const (
|
||||
FLOAT64SLICE
|
||||
// STRINGSLICE is a slice of strings Type Value.
|
||||
STRINGSLICE
|
||||
// BYTESLICE is a slice of bytes Type Value.
|
||||
BYTESLICE
|
||||
// SLICE is a slice of Value Type values.
|
||||
SLICE
|
||||
// INVALID is used for a Value with no value set.
|
||||
//
|
||||
// Deprecated: Use EMPTY instead as an empty value is a valid value.
|
||||
@@ -134,6 +143,19 @@ func StringSliceValue(v []string) Value {
|
||||
return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
|
||||
}
|
||||
|
||||
// ByteSliceValue creates a BYTESLICE Value.
|
||||
func ByteSliceValue(v []byte) Value {
|
||||
return Value{
|
||||
vtype: BYTESLICE,
|
||||
stringly: string(v),
|
||||
}
|
||||
}
|
||||
|
||||
// SliceValue creates a SLICE Value.
|
||||
func SliceValue(v ...Value) Value {
|
||||
return Value{vtype: SLICE, slice: sliceValue(v)}
|
||||
}
|
||||
|
||||
// Type returns a type of the Value.
|
||||
func (v Value) Type() Type {
|
||||
return v.vtype
|
||||
@@ -215,6 +237,59 @@ func (v Value) asStringSlice() []string {
|
||||
return attribute.AsSlice[string](v.slice)
|
||||
}
|
||||
|
||||
// AsSlice returns the []Value value. Make sure that the Value's type is
|
||||
// SLICE.
|
||||
func (v Value) AsSlice() []Value {
|
||||
if v.vtype != SLICE {
|
||||
return nil
|
||||
}
|
||||
return v.asSlice()
|
||||
}
|
||||
|
||||
func (v Value) asSlice() []Value {
|
||||
switch vals := v.slice.(type) {
|
||||
case [0]Value:
|
||||
return []Value{}
|
||||
case [1]Value:
|
||||
return []Value{vals[0]}
|
||||
case [2]Value:
|
||||
return []Value{vals[0], vals[1]}
|
||||
case [3]Value:
|
||||
return []Value{vals[0], vals[1], vals[2]}
|
||||
case [4]Value:
|
||||
return []Value{vals[0], vals[1], vals[2], vals[3]}
|
||||
case [5]Value:
|
||||
return []Value{vals[0], vals[1], vals[2], vals[3], vals[4]}
|
||||
default:
|
||||
return asValueSliceReflect(v.slice)
|
||||
}
|
||||
}
|
||||
|
||||
func asValueSliceReflect(v any) []Value {
|
||||
rv := reflect.ValueOf(v)
|
||||
if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[Value]() {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]Value, rv.Len())
|
||||
if len(cpy) > 0 {
|
||||
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// AsByteSlice returns the bytes value. Make sure that the Value's type
|
||||
// is BYTESLICE.
|
||||
func (v Value) AsByteSlice() []byte {
|
||||
if v.vtype != BYTESLICE {
|
||||
return nil
|
||||
}
|
||||
return v.asByteSlice()
|
||||
}
|
||||
|
||||
func (v Value) asByteSlice() []byte {
|
||||
return []byte(v.stringly)
|
||||
}
|
||||
|
||||
type unknownValueType struct{}
|
||||
|
||||
// AsInterface returns Value's data as any.
|
||||
@@ -236,13 +311,60 @@ func (v Value) AsInterface() any {
|
||||
return v.stringly
|
||||
case STRINGSLICE:
|
||||
return v.asStringSlice()
|
||||
case BYTESLICE:
|
||||
return v.asByteSlice()
|
||||
case SLICE:
|
||||
return v.asSlice()
|
||||
case EMPTY:
|
||||
return nil
|
||||
}
|
||||
return unknownValueType{}
|
||||
}
|
||||
|
||||
// String returns a string representation of Value using the
|
||||
// [OpenTelemetry AnyValue representation for non-OTLP protocols] rules.
|
||||
//
|
||||
// Strings are returned as-is without JSON quoting, booleans and integers use
|
||||
// JSON literals, floating-point values use JSON numbers except that NaN and
|
||||
// ±Inf are rendered as NaN, Infinity, and -Infinity, byte slices are
|
||||
// base64-encoded, empty values are the empty string, and slices are encoded as
|
||||
// JSON arrays. String, byte, and special floating-point values inside arrays
|
||||
// are encoded as JSON strings, and empty values inside arrays are encoded as
|
||||
// null.
|
||||
//
|
||||
// [OpenTelemetry AnyValue representation for non-OTLP protocols]: https://opentelemetry.io/docs/specs/otel/common/#anyvalue-representation-for-non-otlp-protocols
|
||||
func (v Value) String() string {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
return strconv.FormatBool(v.AsBool())
|
||||
case BOOLSLICE:
|
||||
return formatBoolSliceValue(v.slice)
|
||||
case INT64:
|
||||
return strconv.FormatInt(v.AsInt64(), 10)
|
||||
case INT64SLICE:
|
||||
return formatInt64SliceValue(v.slice)
|
||||
case FLOAT64:
|
||||
return formatFloat64(v.AsFloat64())
|
||||
case FLOAT64SLICE:
|
||||
return formatFloat64SliceValue(v.slice)
|
||||
case STRING:
|
||||
return v.stringly
|
||||
case STRINGSLICE:
|
||||
return formatStringSliceValue(v.slice)
|
||||
case BYTESLICE:
|
||||
return formatByteSlice(v.stringly)
|
||||
case SLICE:
|
||||
return formatValueSliceValue(v.slice)
|
||||
case EMPTY:
|
||||
return ""
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Emit returns a string representation of Value's data.
|
||||
//
|
||||
// Deprecated: Use [Value.String] instead.
|
||||
func (v Value) Emit() string {
|
||||
switch v.Type() {
|
||||
case BOOLSLICE:
|
||||
@@ -273,6 +395,10 @@ func (v Value) Emit() string {
|
||||
return string(j)
|
||||
case STRING:
|
||||
return v.stringly
|
||||
case BYTESLICE:
|
||||
return formatByteSlice(v.stringly)
|
||||
case SLICE:
|
||||
return formatValueSliceValue(v.slice)
|
||||
case EMPTY:
|
||||
return ""
|
||||
default:
|
||||
@@ -280,6 +406,622 @@ func (v Value) Emit() string {
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
jsonArrayBracketsLen = len("[]")
|
||||
boolArrayElemMaxLen = len("false")
|
||||
int64ArrayElemMaxLen = len("-9223372036854775808")
|
||||
float64ArrayElemMaxLen = len("-1.7976931348623157e+308")
|
||||
commaLen = len(",")
|
||||
)
|
||||
|
||||
func sliceValue(v []Value) any {
|
||||
switch len(v) {
|
||||
case 0:
|
||||
return [0]Value{}
|
||||
case 1:
|
||||
return [1]Value{v[0]}
|
||||
case 2:
|
||||
return [2]Value{v[0], v[1]}
|
||||
case 3:
|
||||
return [3]Value{v[0], v[1], v[2]}
|
||||
case 4:
|
||||
return [4]Value{v[0], v[1], v[2], v[3]}
|
||||
case 5:
|
||||
return [5]Value{v[0], v[1], v[2], v[3], v[4]}
|
||||
default:
|
||||
return sliceValueReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func sliceValueReflect(v []Value) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[Value]())).Elem()
|
||||
reflect.Copy(cp, reflect.ValueOf(v))
|
||||
return cp.Interface()
|
||||
}
|
||||
|
||||
func formatBoolSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]bool:
|
||||
return "[]"
|
||||
case [1]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
case [2]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
case [3]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
default:
|
||||
return formatBoolSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatBoolSlice(vals []bool) string {
|
||||
var b strings.Builder
|
||||
appendBoolSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatBoolSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendBoolSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendBoolSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]bool:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
case [2]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
case [3]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
default:
|
||||
appendBoolSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendBoolSlice(dst *strings.Builder, vals []bool) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(boolArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
if val {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendBoolSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(boolArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
if rv.Index(i).Bool() {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatInt64SliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]int64:
|
||||
return "[]"
|
||||
case [1]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
case [2]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
case [3]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
default:
|
||||
return formatInt64SliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatInt64Slice(vals []int64) string {
|
||||
var b strings.Builder
|
||||
appendInt64Slice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatInt64SliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendInt64SliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendInt64SliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]int64:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
case [2]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
case [3]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
default:
|
||||
appendInt64SliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendInt64Slice(dst *strings.Builder, vals []int64) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(int64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var buf [int64ArrayElemMaxLen]byte
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
out := strconv.AppendInt(buf[:0], val, 10)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendInt64SliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(int64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var scratch [int64ArrayElemMaxLen]byte
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
out := strconv.AppendInt(scratch[:0], rv.Index(i).Int(), 10)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatFloat64(v float64) string {
|
||||
switch {
|
||||
case math.IsNaN(v):
|
||||
return "NaN"
|
||||
case math.IsInf(v, 1):
|
||||
return "Infinity"
|
||||
case math.IsInf(v, -1):
|
||||
return "-Infinity"
|
||||
default:
|
||||
return strconv.FormatFloat(v, 'g', -1, 64)
|
||||
}
|
||||
}
|
||||
|
||||
func formatFloat64SliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]float64:
|
||||
return "[]"
|
||||
case [1]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
case [2]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
case [3]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
default:
|
||||
return formatFloat64SliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatFloat64Slice(vals []float64) string {
|
||||
var b strings.Builder
|
||||
appendFloat64Slice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatFloat64SliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendFloat64SliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendFloat64SliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]float64:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
case [2]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
case [3]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
default:
|
||||
appendFloat64SliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendFloat64Slice(dst *strings.Builder, vals []float64) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(float64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var buf [float64ArrayElemMaxLen]byte
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
_, _ = dst.WriteString(`"NaN"`)
|
||||
case math.IsInf(val, 1):
|
||||
_, _ = dst.WriteString(`"Infinity"`)
|
||||
case math.IsInf(val, -1):
|
||||
_, _ = dst.WriteString(`"-Infinity"`)
|
||||
default:
|
||||
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendFloat64SliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(float64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var scratch [float64ArrayElemMaxLen]byte
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
val := rv.Index(i).Float()
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
_, _ = dst.WriteString(`"NaN"`)
|
||||
case math.IsInf(val, 1):
|
||||
_, _ = dst.WriteString(`"Infinity"`)
|
||||
case math.IsInf(val, -1):
|
||||
_, _ = dst.WriteString(`"-Infinity"`)
|
||||
default:
|
||||
out := strconv.AppendFloat(scratch[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatStringSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]string:
|
||||
return "[]"
|
||||
case [1]string:
|
||||
return formatStringSlice(vals[:])
|
||||
case [2]string:
|
||||
return formatStringSlice(vals[:])
|
||||
case [3]string:
|
||||
return formatStringSlice(vals[:])
|
||||
default:
|
||||
return formatStringSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatStringSlice(vals []string) string {
|
||||
var b strings.Builder
|
||||
appendStringSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatStringSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendStringSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendStringSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]string:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
case [2]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
case [3]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
default:
|
||||
appendStringSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendStringSlice(dst *strings.Builder, vals []string) {
|
||||
size := jsonArrayBracketsLen
|
||||
for _, val := range vals {
|
||||
size += len(val) + commaLen + 2 // Account for JSON string quotes and comma.
|
||||
}
|
||||
|
||||
dst.Grow(size)
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONString(dst, val)
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendStringSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
size := jsonArrayBracketsLen
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
size += len(rv.Index(i).String()) + commaLen + 2 // Account for JSON string quotes and comma.
|
||||
}
|
||||
|
||||
dst.Grow(size)
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONString(dst, rv.Index(i).String())
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatByteSlice(v string) string {
|
||||
var b strings.Builder
|
||||
appendBase64(&b, v)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatValueSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]Value:
|
||||
return "[]"
|
||||
case [1]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [2]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [3]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [4]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [5]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
default:
|
||||
return formatValueSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatValueSlice(vals []Value) string {
|
||||
var b strings.Builder
|
||||
appendValueSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatValueSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendValueSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendValueSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]Value:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [2]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [3]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [4]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [5]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
default:
|
||||
appendValueSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendValueSlice(dst *strings.Builder, vals []Value) {
|
||||
// Estimate 10 bytes per value for small values and commas.
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*commaLen + len(vals)*10)
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONValue(dst, val)
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendValueSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
// Estimate 10 bytes per value for small values and commas.
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*commaLen + rv.Len()*10)
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONValue(dst, rv.Index(i).Interface().(Value))
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendJSONValue(dst *strings.Builder, v Value) {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
if v.AsBool() {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
case BOOLSLICE:
|
||||
appendBoolSliceValue(dst, v.slice)
|
||||
case INT64:
|
||||
var buf [int64ArrayElemMaxLen]byte
|
||||
out := strconv.AppendInt(buf[:0], v.AsInt64(), 10)
|
||||
_, _ = dst.Write(out)
|
||||
case INT64SLICE:
|
||||
appendInt64SliceValue(dst, v.slice)
|
||||
case FLOAT64:
|
||||
val := v.AsFloat64()
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
appendJSONString(dst, "NaN")
|
||||
case math.IsInf(val, 1):
|
||||
appendJSONString(dst, "Infinity")
|
||||
case math.IsInf(val, -1):
|
||||
appendJSONString(dst, "-Infinity")
|
||||
default:
|
||||
var buf [float64ArrayElemMaxLen]byte
|
||||
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
case FLOAT64SLICE:
|
||||
appendFloat64SliceValue(dst, v.slice)
|
||||
case STRING:
|
||||
appendJSONString(dst, v.stringly)
|
||||
case STRINGSLICE:
|
||||
appendStringSliceValue(dst, v.slice)
|
||||
case BYTESLICE:
|
||||
_ = dst.WriteByte('"')
|
||||
appendBase64(dst, v.stringly)
|
||||
_ = dst.WriteByte('"')
|
||||
case SLICE:
|
||||
appendValueSliceValue(dst, v.slice)
|
||||
case EMPTY:
|
||||
_, _ = dst.WriteString("null")
|
||||
default:
|
||||
appendJSONString(dst, "unknown")
|
||||
}
|
||||
}
|
||||
|
||||
// appendJSONString appends s to dst as a JSON string literal.
|
||||
//
|
||||
// This is adapted from the Go standard library's encoding/json
|
||||
// [appendString implementation]. It keeps the same escaping behavior we need
|
||||
// here, but writes directly into a strings.Builder and intentionally does not
|
||||
// apply HTML escaping because the OpenTelemetry non-OTLP AnyValue representation
|
||||
// only requires JSON array string encoding. We inline this instead of using
|
||||
// encoding/json so slice formatting avoids allocations and reflection.
|
||||
//
|
||||
// [appendString implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/json/encode.go#L998-L1064
|
||||
func appendJSONString(dst *strings.Builder, s string) {
|
||||
const hex = "0123456789abcdef" // For escaping bytes to hex.
|
||||
|
||||
_ = dst.WriteByte('"')
|
||||
start := 0
|
||||
|
||||
for i := 0; i < len(s); {
|
||||
if c := s[i]; c < utf8.RuneSelf {
|
||||
if c >= 0x20 && c != '\\' && c != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '\\', '"':
|
||||
_ = dst.WriteByte('\\')
|
||||
_ = dst.WriteByte(c)
|
||||
case '\b':
|
||||
_, _ = dst.WriteString(`\b`)
|
||||
case '\f':
|
||||
_, _ = dst.WriteString(`\f`)
|
||||
case '\n':
|
||||
_, _ = dst.WriteString(`\n`)
|
||||
case '\r':
|
||||
_, _ = dst.WriteString(`\r`)
|
||||
case '\t':
|
||||
_, _ = dst.WriteString(`\t`)
|
||||
default:
|
||||
_, _ = dst.WriteString(`\u00`)
|
||||
_ = dst.WriteByte(hex[c>>4])
|
||||
_ = dst.WriteByte(hex[c&0x0f])
|
||||
}
|
||||
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
// Match encoding/json by replacing invalid UTF-8 with U+FFFD.
|
||||
_, _ = dst.WriteString(`\ufffd`)
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\u2028' || r == '\u2029' {
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
// Escape JSONP-sensitive separators unconditionally, like encoding/json.
|
||||
_, _ = dst.WriteString(`\u202`)
|
||||
_ = dst.WriteByte(hex[r&0x0f])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
i += size
|
||||
}
|
||||
|
||||
if start < len(s) {
|
||||
_, _ = dst.WriteString(s[start:])
|
||||
}
|
||||
_ = dst.WriteByte('"')
|
||||
}
|
||||
|
||||
// This is adapted from the Go standard library's encoding/base64
|
||||
// [Encoding.Encode implementation]. It keeps the same encoding behavior we need
|
||||
// here, but writes directly into a strings.Builder. We inline this instead of using
|
||||
// encoding/base64 to avoid allocations.
|
||||
//
|
||||
// [Encoding.Encode implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/base64/base64.go#L139-L189
|
||||
func appendBase64(dst *strings.Builder, s string) {
|
||||
const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
dst.Grow(base64.StdEncoding.EncodedLen(len(s)))
|
||||
|
||||
i := 0
|
||||
for ; i+2 < len(s); i += 3 {
|
||||
n := uint32(s[i])<<16 | uint32(s[i+1])<<8 | uint32(s[i+2])
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>6&0x3f])
|
||||
_ = dst.WriteByte(encode[n&0x3f])
|
||||
}
|
||||
|
||||
switch len(s) - i {
|
||||
case 1:
|
||||
n := uint32(s[i]) << 16
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte('=')
|
||||
_ = dst.WriteByte('=')
|
||||
case 2:
|
||||
n := uint32(s[i])<<16 | uint32(s[i+1])<<8
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>6&0x3f])
|
||||
_ = dst.WriteByte('=')
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding of the Value.
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
var jsonVal struct {
|
||||
|
||||
+26
-4
@@ -14,6 +14,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxParseErrors = 5
|
||||
|
||||
// W3C Baggage specification limits.
|
||||
// https://www.w3.org/TR/baggage/#limits
|
||||
maxMembers = 64
|
||||
maxBytesPerBaggageString = 8192
|
||||
|
||||
@@ -493,9 +497,15 @@ func New(members ...Member) (Baggage, error) {
|
||||
// from the W3C Baggage specification which allows duplicate list-members, but
|
||||
// conforms to the OpenTelemetry Baggage specification.
|
||||
//
|
||||
// If the baggage-string exceeds the maximum allowed members (64) or bytes
|
||||
// (8192), members are dropped until the limits are satisfied and an error is
|
||||
// returned along with the partial result.
|
||||
// If the raw baggage-string exceeds the maximum allowed bytes (8192), an
|
||||
// empty Baggage and an error are returned.
|
||||
//
|
||||
// Otherwise, members are parsed left-to-right and accumulated until one of
|
||||
// the following conditions is reached, at which point parsing stops and an
|
||||
// error is returned alongside the partial result:
|
||||
// - accepting the next member would cause the encoded baggage to exceed
|
||||
// 8192 bytes, or
|
||||
// - the baggage already contains 64 distinct keys.
|
||||
//
|
||||
// Invalid members are skipped and the error is returned along with the
|
||||
// partial result containing the valid members.
|
||||
@@ -504,9 +514,14 @@ func Parse(bStr string) (Baggage, error) {
|
||||
return Baggage{}, nil
|
||||
}
|
||||
|
||||
if n := len(bStr); n > maxBytesPerBaggageString {
|
||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||
}
|
||||
|
||||
b := make(baggage.List)
|
||||
sizes := make(map[string]int) // Track per-key byte sizes
|
||||
var totalBytes int
|
||||
var parseErrors int
|
||||
var truncateErr error
|
||||
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
|
||||
// Check member count limit.
|
||||
@@ -517,7 +532,10 @@ func Parse(bStr string) (Baggage, error) {
|
||||
|
||||
m, err := parseMember(memberStr)
|
||||
if err != nil {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
parseErrors++
|
||||
if parseErrors <= maxParseErrors {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
}
|
||||
continue // skip invalid member, keep processing
|
||||
}
|
||||
|
||||
@@ -553,6 +571,10 @@ func Parse(bStr string) (Baggage, error) {
|
||||
totalBytes = newTotalBytes
|
||||
}
|
||||
|
||||
if dropped := parseErrors - maxParseErrors; dropped > 0 {
|
||||
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more invalid member(s)", dropped))
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return Baggage{}, truncateErr
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# This is a renovate-friendly source of Docker images.
|
||||
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
|
||||
FROM otel/weaver:v0.22.1@sha256:33ae522ae4b71c1c562563c1d81f46aa0f79f088a0873199143a1f11ac30e5c9 AS weaver
|
||||
FROM otel/weaver:v0.23.0@sha256:7984ecb55b859eb3034ae9d836c4eeda137e2bdd0873b7ba2bb6c3d24d6ff457 AS weaver
|
||||
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
|
||||
|
||||
Generated
Vendored
+18
-9
@@ -6,6 +6,7 @@ package otlpmetricgrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf"
|
||||
@@ -22,9 +24,10 @@ import (
|
||||
)
|
||||
|
||||
type client struct {
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
requestFunc retry.RequestFunc
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
maxRequestSize int
|
||||
requestFunc retry.RequestFunc
|
||||
|
||||
// ourConn keeps track of where conn was created: true if created here in
|
||||
// NewClient, or false if passed with an option. This is important on
|
||||
@@ -38,9 +41,10 @@ type client struct {
|
||||
// newClient creates a new gRPC metric client.
|
||||
func newClient(_ context.Context, cfg oconf.Config) (*client, error) {
|
||||
c := &client{
|
||||
exportTimeout: cfg.Metrics.Timeout,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
conn: cfg.GRPCConn,
|
||||
exportTimeout: cfg.Metrics.Timeout,
|
||||
maxRequestSize: cfg.Metrics.MaxRequestSize,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
conn: cfg.GRPCConn,
|
||||
}
|
||||
|
||||
if len(cfg.Metrics.Headers) > 0 {
|
||||
@@ -115,10 +119,15 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
pbRequest := &colmetricpb.ExportMetricsServiceRequest{
|
||||
ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics},
|
||||
}
|
||||
if maxSize := c.maxRequestSize; maxSize > 0 && proto.Size(pbRequest) > maxSize {
|
||||
return fmt.Errorf("request message too large: exceeded %d bytes", maxSize)
|
||||
}
|
||||
|
||||
return errors.Join(uploadErr, c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
resp, err := c.msc.Export(iCtx, &colmetricpb.ExportMetricsServiceRequest{
|
||||
ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics},
|
||||
})
|
||||
resp, err := c.msc.Export(iCtx, pbRequest)
|
||||
if resp != nil && resp.PartialSuccess != nil {
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedDataPoints()
|
||||
|
||||
Generated
Vendored
+10
@@ -231,6 +231,16 @@ func WithTimeout(duration time.Duration) Option {
|
||||
return wrappedOption{oconf.WithTimeout(duration)}
|
||||
}
|
||||
|
||||
// WithMaxRequestSize sets the maximum size, in bytes, of a serialized export
|
||||
// request, before compression, that the exporter will send.
|
||||
//
|
||||
// If size is less than or equal to zero, no request-size limit is applied.
|
||||
// Disabling the limit is not recommended because it can lead to excessive
|
||||
// resource consumption or abuse.
|
||||
func WithMaxRequestSize(size int) Option {
|
||||
return wrappedOption{oconf.WithMaxRequestSize(size)}
|
||||
}
|
||||
|
||||
// WithRetry sets the retry policy for transient retryable errors that are
|
||||
// returned by the target endpoint.
|
||||
//
|
||||
|
||||
+3
@@ -77,6 +77,9 @@ default aggregation to use for histogram instruments. Supported values:
|
||||
|
||||
The configuration can be overridden by [WithAggregationSelector] option.
|
||||
|
||||
See [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x] for information about
|
||||
the experimental features.
|
||||
|
||||
[W3C Baggage HTTP Header Content Format]: https://www.w3.org/TR/baggage/#header-content
|
||||
[Explicit Bucket Histogram Aggregation]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.26.0/specification/metrics/sdk.md#explicit-bucket-histogram-aggregation
|
||||
[Base2 Exponential Bucket Histogram Aggregation]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.26.0/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation
|
||||
|
||||
Generated
Vendored
+31
-2
@@ -11,8 +11,11 @@ import (
|
||||
|
||||
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/transform"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
@@ -31,6 +34,9 @@ type Exporter struct {
|
||||
aggregationSelector metric.AggregationSelector
|
||||
|
||||
shutdownOnce sync.Once
|
||||
|
||||
// Self-observability metrics
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
func newExporter(c *client, cfg oconf.Config) (*Exporter, error) {
|
||||
@@ -46,12 +52,27 @@ func newExporter(c *client, cfg oconf.Config) (*Exporter, error) {
|
||||
as = metric.DefaultAggregationSelector
|
||||
}
|
||||
|
||||
var inst *observ.Instrumentation
|
||||
var initErr error
|
||||
if x.Observability.Enabled() {
|
||||
var err error
|
||||
inst, err = observ.NewInstrumentation(
|
||||
counter.NextExporterID(),
|
||||
c.conn.CanonicalTarget(),
|
||||
)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
}
|
||||
}
|
||||
|
||||
return &Exporter{
|
||||
client: c,
|
||||
|
||||
temporalitySelector: ts,
|
||||
aggregationSelector: as,
|
||||
}, nil
|
||||
|
||||
inst: inst,
|
||||
}, initErr
|
||||
}
|
||||
|
||||
// Temporality returns the Temporality to use for an instrument kind.
|
||||
@@ -72,10 +93,18 @@ func (e *Exporter) Export(ctx context.Context, rm *metricdata.ResourceMetrics) e
|
||||
defer global.Debug("OTLP/gRPC exporter export", "Data", rm)
|
||||
|
||||
otlpRm, err := transform.ResourceMetrics(rm)
|
||||
|
||||
// Track export operation for self-observability
|
||||
op := e.inst.TrackExport(ctx, otlpRm)
|
||||
|
||||
var upErr error
|
||||
defer func() { op.End(upErr) }()
|
||||
|
||||
// Best effort upload of transformable metrics.
|
||||
e.clientMu.Lock()
|
||||
upErr := e.client.UploadMetrics(ctx, otlpRm)
|
||||
upErr = e.client.UploadMetrics(ctx, otlpRm)
|
||||
e.clientMu.Unlock()
|
||||
|
||||
if upErr != nil {
|
||||
if err == nil {
|
||||
return fmt.Errorf("failed to upload metrics: %w", upErr)
|
||||
|
||||
vendor/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter/counter.go
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/counter/counter.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package counter provides a simple counter for generating unique IDs.
|
||||
//
|
||||
// This package is used to generate unique IDs while allowing testing packages
|
||||
// to reset the counter.
|
||||
package counter // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter"
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// exporterN is a global 0-based count of the number of exporters created.
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// NextExporterID returns the next unique ID for an exporter.
|
||||
func NextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// SetExporterID sets the exporter ID counter to v and returns the previous
|
||||
// value.
|
||||
//
|
||||
// This function is useful for testing purposes, allowing you to reset the
|
||||
// counter. It should not be used in production code.
|
||||
func SetExporterID(v int64) int64 {
|
||||
return exporterN.Swap(v)
|
||||
}
|
||||
Generated
Vendored
+9
@@ -7,6 +7,9 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/partialsuccess.go.tmpl "--data={}" --out=partialsuccess.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/partialsuccess_test.go.tmpl "--data={}" --out=partialsuccess_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target.go.tmpl "--data={ \"pkg\": \"observ\", \"pkg_path\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ\" }" --out=observ/target.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target_test.go.tmpl "--data={ \"pkg\": \"observ\" }" --out=observ/target_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry.go.tmpl "--data={}" --out=retry/retry.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry_test.go.tmpl "--data={}" --out=retry/retry_test.go
|
||||
|
||||
@@ -30,3 +33,9 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error_test.go.tmpl "--data={}" --out=transform/error_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={}" --out=transform/metricdata.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={}" --out=transform/metricdata_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/counter\" }" --out=counter/counter.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
|
||||
Generated
Vendored
+343
@@ -0,0 +1,343 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides self-observability metrics for OTLP metric exporters.
|
||||
// This is an experimental feature controlled by the x.Observability feature flag.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
)
|
||||
|
||||
var (
|
||||
attrPool = sync.Pool{
|
||||
New: func() any {
|
||||
// Pre-allocate for common attributes and dynamic error attributes.
|
||||
const n = 1 /* otel.component.type */ +
|
||||
1 /* otel.component.name */ +
|
||||
1 /* server.address */ +
|
||||
1 /* server.port */ +
|
||||
1 /* error.type */ +
|
||||
1 /* rpc.grpc.status_code */
|
||||
s := make([]attribute.KeyValue, 0, n)
|
||||
return &s
|
||||
},
|
||||
}
|
||||
|
||||
recOptPool = sync.Pool{
|
||||
New: func() any {
|
||||
o := make([]metric.RecordOption, 0, 1)
|
||||
return &o
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Instrumentation holds the self-observability metric instruments for an OTLP metric exporter.
|
||||
type Instrumentation struct {
|
||||
exported otelconv.SDKExporterMetricDataPointExported
|
||||
inflight otelconv.SDKExporterMetricDataPointInflight
|
||||
duration otelconv.SDKExporterOperationDuration
|
||||
attrs []attribute.KeyValue
|
||||
addOpt metric.AddOption
|
||||
recOpt metric.RecordOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for an OTLP over gRPC metric
|
||||
// exporter with the provided ID using the global MeterProvider.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the endpoint the exporter is exporting to.
|
||||
//
|
||||
// If the experimental observability is disabled, nil is returned.
|
||||
func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
em := &Instrumentation{}
|
||||
|
||||
meter := otel.GetMeterProvider().Meter(
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc",
|
||||
metric.WithInstrumentationVersion(sdk.Version()),
|
||||
metric.WithSchemaURL(semconv.SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
var instrumentErr error
|
||||
|
||||
em.exported, instrumentErr = otelconv.NewSDKExporterMetricDataPointExported(meter)
|
||||
if instrumentErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to create exported metric: %w", instrumentErr))
|
||||
}
|
||||
|
||||
em.inflight, instrumentErr = otelconv.NewSDKExporterMetricDataPointInflight(meter)
|
||||
if instrumentErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to create inflight metric: %w", instrumentErr))
|
||||
}
|
||||
|
||||
em.duration, instrumentErr = otelconv.NewSDKExporterOperationDuration(meter)
|
||||
if instrumentErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to create duration metric: %w", instrumentErr))
|
||||
}
|
||||
|
||||
em.attrs = BaseAttrs(id, target)
|
||||
|
||||
attrSet := attribute.NewSet(em.attrs...)
|
||||
em.addOpt = metric.WithAttributeSet(attrSet)
|
||||
em.recOpt = metric.WithAttributeSet(attribute.NewSet(append(
|
||||
[]attribute.KeyValue{semconv.RPCResponseStatusCode(codes.OK.String())},
|
||||
em.attrs...,
|
||||
)...))
|
||||
|
||||
return em, err
|
||||
}
|
||||
|
||||
// ComponentName returns the component name for the exporter with the
|
||||
// provided ID.
|
||||
func ComponentName(id int64) string {
|
||||
t := string(otelconv.ComponentTypeOtlpGRPCMetricExporter)
|
||||
return fmt.Sprintf("%s/%d", t, id)
|
||||
}
|
||||
|
||||
// BaseAttrs returns the base attributes for the exporter with the provided ID
|
||||
// and target.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the gRPC target the exporter is exporting to. It is expected
|
||||
// to be the output of the Client's CanonicalTarget method.
|
||||
func BaseAttrs(id int64, target string) []attribute.KeyValue {
|
||||
host, port, err := ParseCanonicalTarget(target)
|
||||
if err != nil || (host == "" && port < 0) {
|
||||
if err != nil {
|
||||
global.Debug("failed to parse target", "target", target, "error", err)
|
||||
}
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use append so the slice is exactly allocated.
|
||||
|
||||
if port < 0 {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
semconv.ServerAddress(host),
|
||||
}
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(string(otelconv.ComponentTypeOtlpGRPCMetricExporter)),
|
||||
semconv.ServerAddress(host),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
// TrackExport tracks an export operation and returns an ExportOp to complete the tracking.
|
||||
func (em *Instrumentation) TrackExport(ctx context.Context, rm *metricpb.ResourceMetrics) ExportOp {
|
||||
if em == nil {
|
||||
return ExportOp{}
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
var dataPointCount int64
|
||||
inflightEnabled := em.inflight.Enabled(ctx)
|
||||
exportedEnabled := em.exported.Enabled(ctx)
|
||||
|
||||
if inflightEnabled || exportedEnabled {
|
||||
dataPointCount = countProtoDataPoints(rm)
|
||||
}
|
||||
|
||||
if inflightEnabled {
|
||||
em.inflight.Inst().Add(ctx, dataPointCount, em.addOpt)
|
||||
}
|
||||
|
||||
return ExportOp{
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
dataPointCount: dataPointCount,
|
||||
inst: em,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp tracks the operation being observed by [Instrumentation.TrackExport].
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
dataPointCount int64
|
||||
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End completes the observation of the operation being observed by a call to
|
||||
// [Instrumentation.TrackExport].
|
||||
//
|
||||
// Any error that is encountered is provided as err.
|
||||
func (e ExportOp) End(err error) {
|
||||
if e.inst == nil {
|
||||
return
|
||||
}
|
||||
if e.inst.inflight.Enabled(e.ctx) {
|
||||
e.inst.inflight.Inst().Add(e.ctx, -e.dataPointCount, e.inst.addOpt)
|
||||
}
|
||||
|
||||
success := successful(e.dataPointCount, err)
|
||||
// Record successfully exported data points, even if the value is 0 which are
|
||||
// meaningful to distribution aggregations.
|
||||
if e.inst.exported.Enabled(e.ctx) {
|
||||
e.inst.exported.Inst().Add(e.ctx, success, e.inst.addOpt)
|
||||
}
|
||||
|
||||
if err != nil && e.inst.exported.Enabled(e.ctx) {
|
||||
attrsPtr := attrPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
*attrsPtr = (*attrsPtr)[:0]
|
||||
attrPool.Put(attrsPtr)
|
||||
}()
|
||||
|
||||
*attrsPtr = append(*attrsPtr, e.inst.attrs...)
|
||||
*attrsPtr = append(*attrsPtr, semconv.ErrorType(err))
|
||||
|
||||
set := attribute.NewSet(*attrsPtr...)
|
||||
e.inst.exported.Inst().Add(e.ctx, e.dataPointCount-success, metric.WithAttributeSet(set))
|
||||
}
|
||||
|
||||
if e.inst.duration.Enabled(e.ctx) {
|
||||
d := time.Since(e.start).Seconds()
|
||||
if err != nil {
|
||||
recOptPtr := recOptPool.Get().(*[]metric.RecordOption)
|
||||
defer func() {
|
||||
*recOptPtr = (*recOptPtr)[:0]
|
||||
recOptPool.Put(recOptPtr)
|
||||
}()
|
||||
|
||||
attrsPtr := attrPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
*attrsPtr = (*attrsPtr)[:0]
|
||||
attrPool.Put(attrsPtr)
|
||||
}()
|
||||
|
||||
*attrsPtr = append(*attrsPtr, e.inst.attrs...)
|
||||
*attrsPtr = append(
|
||||
*attrsPtr,
|
||||
semconv.ErrorType(err),
|
||||
semconv.RPCResponseStatusCode(status.Code(err).String()),
|
||||
)
|
||||
|
||||
set := attribute.NewSet(*attrsPtr...)
|
||||
*recOptPtr = append(*recOptPtr, metric.WithAttributeSet(set))
|
||||
|
||||
e.inst.duration.Inst().Record(e.ctx, d, *recOptPtr...)
|
||||
} else {
|
||||
e.inst.duration.Inst().Record(e.ctx, d, e.inst.recOpt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// countProtoDataPoints counts the total number of data points in a ResourceMetrics.
|
||||
func countProtoDataPoints(rm *metricpb.ResourceMetrics) int64 {
|
||||
if rm == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var total int64
|
||||
for _, sm := range rm.ScopeMetrics {
|
||||
for _, m := range sm.Metrics {
|
||||
switch data := m.Data.(type) {
|
||||
case *metricpb.Metric_Gauge:
|
||||
if data.Gauge != nil {
|
||||
total += int64(len(data.Gauge.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_Sum:
|
||||
if data.Sum != nil {
|
||||
total += int64(len(data.Sum.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_Histogram:
|
||||
if data.Histogram != nil {
|
||||
total += int64(len(data.Histogram.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_ExponentialHistogram:
|
||||
if data.ExponentialHistogram != nil {
|
||||
total += int64(len(data.ExponentialHistogram.DataPoints))
|
||||
}
|
||||
case *metricpb.Metric_Summary:
|
||||
if data.Summary != nil {
|
||||
total += int64(len(data.Summary.DataPoints))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// successful returns the number of successfully exported data points out of the n
|
||||
// that were exported based on the provided error.
|
||||
//
|
||||
// If err is nil, n is returned. All data points were successfully exported.
|
||||
//
|
||||
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
|
||||
// It is assumed all data points failed to be exported.
|
||||
//
|
||||
// If err is an [internal.PartialSuccess] error, the number of successfully
|
||||
// exported data points is computed by subtracting the RejectedItems field from n. If
|
||||
// RejectedItems is negative, n is returned. If RejectedItems is greater than
|
||||
// n, 0 is returned.
|
||||
func successful(n int64, err error) int64 {
|
||||
if err == nil {
|
||||
return n // All data points successfully exported.
|
||||
}
|
||||
// Split rejection calculation so successful is inlinable.
|
||||
return n - rejected(n, err)
|
||||
}
|
||||
|
||||
var errPartialPool = &sync.Pool{
|
||||
New: func() any { return new(internal.PartialSuccess) },
|
||||
}
|
||||
|
||||
// rejected returns how many out of the n data points exporter were rejected based on
|
||||
// the provided non-nil err.
|
||||
func rejected(n int64, err error) int64 {
|
||||
ps := errPartialPool.Get().(*internal.PartialSuccess)
|
||||
defer errPartialPool.Put(ps)
|
||||
// Check for partial success.
|
||||
if errors.As(err, ps) {
|
||||
// Bound RejectedItems to [0, n]. This should not be needed,
|
||||
// but be defensive as this is from an external source.
|
||||
return min(max(ps.RejectedItems, 0), n)
|
||||
}
|
||||
return n // All data points rejected.
|
||||
}
|
||||
Generated
Vendored
+143
@@ -0,0 +1,143 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/observ/target.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeUnix = "unix"
|
||||
schemeUnixAbstract = "unix-abstract"
|
||||
)
|
||||
|
||||
// ParseCanonicalTarget parses a target string and returns the extracted host
|
||||
// (domain address or IP), the target port, or an error.
|
||||
//
|
||||
// If no port is specified, -1 is returned.
|
||||
//
|
||||
// If no host is specified, an empty string is returned.
|
||||
//
|
||||
// The target string is expected to always have the form
|
||||
// "<scheme>://[authority]/<endpoint>". For example:
|
||||
// - "dns:///example.com:42"
|
||||
// - "dns://8.8.8.8/example.com:42"
|
||||
// - "unix:///path/to/socket"
|
||||
// - "unix-abstract:///socket-name"
|
||||
// - "passthrough:///192.34.2.1:42"
|
||||
//
|
||||
// The target is expected to come from the CanonicalTarget method of a gRPC
|
||||
// Client.
|
||||
func ParseCanonicalTarget(target string) (string, int, error) {
|
||||
const sep = "://"
|
||||
|
||||
// Find scheme. Do not allocate the string by using url.Parse.
|
||||
idx := strings.Index(target, sep)
|
||||
if idx == -1 {
|
||||
return "", -1, fmt.Errorf("invalid target %q: missing scheme", target)
|
||||
}
|
||||
scheme, endpoint := target[:idx], target[idx+len(sep):]
|
||||
|
||||
// Check for unix schemes.
|
||||
if scheme == schemeUnix || scheme == schemeUnixAbstract {
|
||||
return parseUnix(endpoint)
|
||||
}
|
||||
|
||||
// Strip leading slash and any authority.
|
||||
if i := strings.Index(endpoint, "/"); i != -1 {
|
||||
endpoint = endpoint[i+1:]
|
||||
}
|
||||
|
||||
// DNS, passthrough, and custom resolvers.
|
||||
return parseEndpoint(endpoint)
|
||||
}
|
||||
|
||||
// parseUnix parses unix socket targets.
|
||||
func parseUnix(endpoint string) (string, int, error) {
|
||||
// Format: unix[-abstract]://path
|
||||
//
|
||||
// We should have "/path" (empty authority) if valid.
|
||||
if len(endpoint) >= 1 && endpoint[0] == '/' {
|
||||
// Return the full path including leading slash.
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
// If there's no leading slash, it means there might be an authority
|
||||
// Check for authority case (should error): "authority/path"
|
||||
if slashIdx := strings.Index(endpoint, "/"); slashIdx > 0 {
|
||||
return "", -1, fmt.Errorf("invalid (non-empty) authority: %s", endpoint[:slashIdx])
|
||||
}
|
||||
|
||||
return "", -1, errors.New("invalid unix target format")
|
||||
}
|
||||
|
||||
// parseEndpoint parses an endpoint from a gRPC target.
|
||||
//
|
||||
// It supports the following formats:
|
||||
// - "host"
|
||||
// - "host%zone"
|
||||
// - "host:port"
|
||||
// - "host%zone:port"
|
||||
// - "ipv4"
|
||||
// - "ipv4%zone"
|
||||
// - "ipv4:port"
|
||||
// - "ipv4%zone:port"
|
||||
// - "ipv6"
|
||||
// - "ipv6%zone"
|
||||
// - "[ipv6]"
|
||||
// - "[ipv6%zone]"
|
||||
// - "[ipv6]:port"
|
||||
// - "[ipv6%zone]:port"
|
||||
//
|
||||
// It returns the host or host%zone (domain address or IP), the port (or -1 if
|
||||
// not specified), or an error if the input is not a valid.
|
||||
func parseEndpoint(endpoint string) (string, int, error) {
|
||||
// First check if the endpoint is just an IP address.
|
||||
if ip := parseIP(endpoint); ip != "" {
|
||||
return ip, -1, nil
|
||||
}
|
||||
|
||||
// If there's no colon, there is no port (IPv6 with no port checked above).
|
||||
if !strings.Contains(endpoint, ":") {
|
||||
return endpoint, -1, nil
|
||||
}
|
||||
|
||||
host, portStr, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid host:port %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
const base, bitSize = 10, 16
|
||||
port16, err := strconv.ParseUint(portStr, base, bitSize)
|
||||
if err != nil {
|
||||
return "", -1, fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||
}
|
||||
port := int(port16) // port is guaranteed to be in the range [0, 65535].
|
||||
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
// parseIP attempts to parse the entire endpoint as an IP address.
|
||||
// It returns the normalized string form of the IP if successful,
|
||||
// or an empty string if parsing fails.
|
||||
func parseIP(ip string) string {
|
||||
// Strip leading and trailing brackets for IPv6 addresses.
|
||||
if len(ip) >= 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
ip = ip[1 : len(ip)-1]
|
||||
}
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// Return the normalized string form of the IP.
|
||||
return addr.String()
|
||||
}
|
||||
Generated
Vendored
+28
-15
@@ -35,6 +35,9 @@ const (
|
||||
// DefaultMetricsPath is a default URL path for endpoint that
|
||||
// receives metrics.
|
||||
DefaultMetricsPath string = "/v1/metrics"
|
||||
// DefaultMaxRequestSize is the default maximum size of a serialized export
|
||||
// request, before compression.
|
||||
DefaultMaxRequestSize int = 64 * 1024 * 1024
|
||||
// DefaultBackoff is a default base backoff time used in the
|
||||
// exponential backoff strategy.
|
||||
DefaultBackoff time.Duration = 300 * time.Millisecond
|
||||
@@ -49,13 +52,14 @@ type (
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
MaxRequestSize int
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
TemporalitySelector metric.TemporalitySelector
|
||||
AggregationSelector metric.AggregationSelector
|
||||
@@ -87,10 +91,11 @@ type (
|
||||
func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
cfg := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
@@ -123,10 +128,11 @@ func cleanPath(urlPath string, defaultPath string) string {
|
||||
func NewGRPCConfig(opts ...GRPCOption) Config {
|
||||
cfg := Config{
|
||||
Metrics: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultMetricsPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
|
||||
TemporalitySelector: metric.DefaultTemporalitySelector,
|
||||
AggregationSelector: metric.DefaultAggregationSelector,
|
||||
@@ -354,6 +360,13 @@ func WithTimeout(duration time.Duration) GenericOption {
|
||||
})
|
||||
}
|
||||
|
||||
func WithMaxRequestSize(size int) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.MaxRequestSize = size
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Metrics.TemporalitySelector = selector
|
||||
|
||||
Generated
Vendored
+18
@@ -81,6 +81,16 @@ func Value(v attribute.Value) *cpb.AnyValue {
|
||||
av.Value = &cpb.AnyValue_StringValue{
|
||||
StringValue: v.AsString(),
|
||||
}
|
||||
case attribute.BYTESLICE:
|
||||
av.Value = &cpb.AnyValue_BytesValue{
|
||||
BytesValue: v.AsByteSlice(),
|
||||
}
|
||||
case attribute.SLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
Values: attrValues(v.AsSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
av.Value = &cpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &cpb.ArrayValue{
|
||||
@@ -143,3 +153,11 @@ func stringSliceValues(vals []string) []*cpb.AnyValue {
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func attrValues(vals []attribute.Value) []*cpb.AnyValue {
|
||||
converted := make([]*cpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = Value(v)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
Generated
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
# Experimental Features
|
||||
|
||||
The OTLP gRPC metric exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the OpenTelemetry Go OTLP exporters prior to stabilization in the specification so that users can start experimenting with them and provide feedback.
|
||||
|
||||
These features may change in backwards incompatible ways as feedback is applied.
|
||||
See the [Compatibility and Stability](#compatibility-and-stability) section for more information.
|
||||
|
||||
## Features
|
||||
|
||||
- [Self-Observability](#self-observability)
|
||||
|
||||
### Self-Observability
|
||||
|
||||
The OTLP gRPC metric exporter can emit self-observability metrics to track its own operation.
|
||||
|
||||
This experimental feature can be enabled by setting the `OTEL_GO_X_OBSERVABILITY` environment variable.
|
||||
The value must be the case-insensitive string of `"true"` to enable the feature.
|
||||
All other values are ignored.
|
||||
|
||||
When enabled, the exporter will emit the following metrics using the global MeterProvider:
|
||||
|
||||
- `otel.sdk.exporter.metric_data_point.exported`: Counter tracking successfully exported data points
|
||||
- `otel.sdk.exporter.metric_data_point.inflight`: UpDownCounter tracking data points currently being exported
|
||||
- `otel.sdk.exporter.operation.duration`: Histogram tracking export operation duration in seconds
|
||||
|
||||
All metrics include attributes identifying the exporter component and destination server:
|
||||
|
||||
- `otel.component.type`: Type of component (e.g., "otlp_grpc_metric_exporter")
|
||||
- `otel.component.name`: Unique component instance name (e.g., "otlp_grpc_metric_exporter/0")
|
||||
- `server.address`: Server hostname or address
|
||||
- `server.port`: Server port number
|
||||
|
||||
#### Examples
|
||||
|
||||
Enable self-observability metrics.
|
||||
|
||||
```console
|
||||
export OTEL_GO_X_OBSERVABILITY=true
|
||||
```
|
||||
|
||||
Disable self-observability metrics.
|
||||
|
||||
```console
|
||||
unset OTEL_GO_X_OBSERVABILITY
|
||||
```
|
||||
|
||||
## Compatibility and Stability
|
||||
|
||||
Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../../../VERSIONING.md).
|
||||
These features may be removed or modified in successive version releases, including patch versions.
|
||||
|
||||
When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release.
|
||||
There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version.
|
||||
If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support.
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
|
||||
import "strings"
|
||||
|
||||
// Observability is an experimental feature flag that defines if OTLP
|
||||
// gRPC metric exporter should include self-observability metrics.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
|
||||
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
|
||||
// will also enable this).
|
||||
var Observability = newFeature(
|
||||
[]string{"OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/x/x.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Feature is an experimental feature control flag. It provides a uniform way
|
||||
// to interact with these feature flags and parse their values.
|
||||
type Feature[T any] struct {
|
||||
keys []string
|
||||
parse func(v string) (T, bool)
|
||||
}
|
||||
|
||||
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
|
||||
const envKeyRoot = "OTEL_GO_X_"
|
||||
keys := make([]string, 0, len(suffix))
|
||||
for _, s := range suffix {
|
||||
keys = append(keys, envKeyRoot+s)
|
||||
}
|
||||
return Feature[T]{
|
||||
keys: keys,
|
||||
parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns the environment variable keys that can be set to enable the
|
||||
// feature.
|
||||
func (f Feature[T]) Keys() []string { return f.keys }
|
||||
|
||||
// Lookup returns the user configured value for the feature and true if the
|
||||
// user has enabled the feature. Otherwise, if the feature is not enabled, a
|
||||
// zero-value and false are returned.
|
||||
func (f Feature[T]) Lookup() (v T, ok bool) {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value
|
||||
//
|
||||
// > The SDK MUST interpret an empty value of an environment variable the
|
||||
// > same way as when the variable is unset.
|
||||
for _, key := range f.keys {
|
||||
vRaw := os.Getenv(key)
|
||||
if vRaw != "" {
|
||||
return f.parse(vRaw)
|
||||
}
|
||||
}
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// Enabled reports whether the feature is enabled.
|
||||
func (f Feature[T]) Enabled() bool {
|
||||
_, ok := f.Lookup()
|
||||
return ok
|
||||
}
|
||||
Generated
Vendored
+1
-1
@@ -5,5 +5,5 @@ package otlpmetricgrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP over gRPC metrics exporter in use.
|
||||
func Version() string {
|
||||
return "1.43.0"
|
||||
return "1.44.0"
|
||||
}
|
||||
|
||||
Generated
Vendored
+18
@@ -87,6 +87,16 @@ func Value(v attribute.Value) *commonpb.AnyValue {
|
||||
av.Value = &commonpb.AnyValue_StringValue{
|
||||
StringValue: v.AsString(),
|
||||
}
|
||||
case attribute.BYTESLICE:
|
||||
av.Value = &commonpb.AnyValue_BytesValue{
|
||||
BytesValue: v.AsByteSlice(),
|
||||
}
|
||||
case attribute.SLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: values(v.AsSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
@@ -149,3 +159,11 @@ func stringSliceValues(vals []string) []*commonpb.AnyValue {
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func values(vals []attribute.Value) []*commonpb.AnyValue {
|
||||
converted := make([]*commonpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = Value(v)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
+34
-18
@@ -6,6 +6,7 @@ package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptra
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
@@ -26,11 +28,12 @@ import (
|
||||
)
|
||||
|
||||
type client struct {
|
||||
endpoint string
|
||||
dialOpts []grpc.DialOption
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
requestFunc retry.RequestFunc
|
||||
endpoint string
|
||||
dialOpts []grpc.DialOption
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
maxRequestSize int
|
||||
requestFunc retry.RequestFunc
|
||||
|
||||
// stopCtx is used as a parent context for all exports. Therefore, when it
|
||||
// is canceled with the stopFunc all exports are canceled.
|
||||
@@ -65,14 +68,15 @@ func newClient(opts ...Option) *client {
|
||||
ctx, cancel := context.WithCancel(context.Background()) //nolint:gosec // cancel called in client shutdown.
|
||||
|
||||
c := &client{
|
||||
endpoint: cfg.Traces.Endpoint,
|
||||
exportTimeout: cfg.Traces.Timeout,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
dialOpts: cfg.DialOptions,
|
||||
stopCtx: ctx,
|
||||
stopFunc: cancel,
|
||||
conn: cfg.GRPCConn,
|
||||
instID: counter.NextExporterID(),
|
||||
endpoint: cfg.Traces.Endpoint,
|
||||
exportTimeout: cfg.Traces.Timeout,
|
||||
maxRequestSize: cfg.Traces.MaxRequestSize,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
dialOpts: cfg.DialOptions,
|
||||
stopCtx: ctx,
|
||||
stopFunc: cancel,
|
||||
conn: cfg.GRPCConn,
|
||||
instID: counter.NextExporterID(),
|
||||
}
|
||||
|
||||
if len(cfg.Traces.Headers) > 0 {
|
||||
@@ -205,16 +209,28 @@ func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
var code codes.Code
|
||||
pbRequest := &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: protoSpans,
|
||||
}
|
||||
|
||||
code := codes.Unknown
|
||||
if c.inst != nil {
|
||||
op := c.inst.ExportSpans(ctx, len(protoSpans))
|
||||
var spanCount int
|
||||
for _, rs := range protoSpans {
|
||||
for _, ss := range rs.ScopeSpans {
|
||||
spanCount += len(ss.Spans)
|
||||
}
|
||||
}
|
||||
op := c.inst.ExportSpans(ctx, spanCount)
|
||||
defer func() { op.End(uploadErr, code) }()
|
||||
}
|
||||
|
||||
if maxSize := c.maxRequestSize; maxSize > 0 && proto.Size(pbRequest) > maxSize {
|
||||
return fmt.Errorf("request message too large: exceeded %d bytes", maxSize)
|
||||
}
|
||||
|
||||
return c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
resp, err := c.tsc.Export(iCtx, &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: protoSpans,
|
||||
})
|
||||
resp, err := c.tsc.Export(iCtx, pbRequest)
|
||||
if resp != nil && resp.PartialSuccess != nil {
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedSpans()
|
||||
|
||||
Generated
Vendored
+7
-3
@@ -18,8 +18,8 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,6 +72,7 @@ var (
|
||||
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||
|
||||
func put[T any](p *sync.Pool, s *[]T) {
|
||||
clear(*s) // erase elements to allow GC to collect what they refer to.
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
@@ -339,7 +340,10 @@ var errPartialPool = &sync.Pool{
|
||||
// the provided non-nil err.
|
||||
func rejected(n int64, err error) int64 {
|
||||
ps := errPartialPool.Get().(*internal.PartialSuccess)
|
||||
defer errPartialPool.Put(ps)
|
||||
defer func() {
|
||||
*ps = internal.PartialSuccess{} // erase fields to allow GC to collect them.
|
||||
errPartialPool.Put(ps)
|
||||
}()
|
||||
// Check for partial success.
|
||||
if errors.As(err, ps) {
|
||||
// Bound RejectedItems to [0, n]. This should not be needed,
|
||||
|
||||
Generated
Vendored
+28
-15
@@ -31,6 +31,9 @@ const (
|
||||
// DefaultTracesPath is a default URL path for endpoint that
|
||||
// receives spans.
|
||||
DefaultTracesPath string = "/v1/traces"
|
||||
// DefaultMaxRequestSize is the default maximum size of a serialized export
|
||||
// request, before compression.
|
||||
DefaultMaxRequestSize int = 64 * 1024 * 1024
|
||||
// DefaultTimeout is a default max waiting time for the backend to process
|
||||
// each span batch.
|
||||
DefaultTimeout time.Duration = 10 * time.Second
|
||||
@@ -42,13 +45,14 @@ type (
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
MaxRequestSize int
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
// gRPC configurations
|
||||
GRPCCredentials credentials.TransportCredentials
|
||||
@@ -77,10 +81,11 @@ type (
|
||||
func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
cfg := Config{
|
||||
Traces: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
}
|
||||
@@ -111,10 +116,11 @@ func NewGRPCConfig(opts ...GRPCOption) Config {
|
||||
userAgent := "OTel OTLP Exporter Go/" + otlptrace.Version()
|
||||
cfg := Config{
|
||||
Traces: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
MaxRequestSize: DefaultMaxRequestSize,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
DialOptions: []grpc.DialOption{grpc.WithUserAgent(userAgent)},
|
||||
@@ -345,6 +351,13 @@ func WithTimeout(duration time.Duration) GenericOption {
|
||||
})
|
||||
}
|
||||
|
||||
func WithMaxRequestSize(size int) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.MaxRequestSize = size
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Proxy = pf
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -5,4 +5,4 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP gRPC trace
|
||||
// exporter in use.
|
||||
const Version = "1.43.0"
|
||||
const Version = "1.44.0"
|
||||
|
||||
Generated
Vendored
+10
@@ -192,6 +192,16 @@ func WithTimeout(duration time.Duration) Option {
|
||||
return wrappedOption{otlpconfig.WithTimeout(duration)}
|
||||
}
|
||||
|
||||
// WithMaxRequestSize sets the maximum size, in bytes, of a serialized export
|
||||
// request, before compression, that the exporter will send.
|
||||
//
|
||||
// If size is less than or equal to zero, no request-size limit is applied.
|
||||
// Disabling the limit is not recommended because it can lead to excessive
|
||||
// resource consumption or abuse.
|
||||
func WithMaxRequestSize(size int) Option {
|
||||
return wrappedOption{otlpconfig.WithMaxRequestSize(size)}
|
||||
}
|
||||
|
||||
// WithRetry sets the retry policy for transient retryable errors that may be
|
||||
// returned by the target endpoint when exporting a batch of spans.
|
||||
//
|
||||
|
||||
+1
-1
@@ -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.
|
||||
func Version() string {
|
||||
return "1.43.0"
|
||||
return "1.44.0"
|
||||
}
|
||||
|
||||
+9
@@ -51,6 +51,9 @@ type Float64ObservableCounterConfig struct {
|
||||
func NewFloat64ObservableCounterConfig(opts ...Float64ObservableCounterOption) Float64ObservableCounterConfig {
|
||||
var config Float64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -111,6 +114,9 @@ func NewFloat64ObservableUpDownCounterConfig(
|
||||
) Float64ObservableUpDownCounterConfig {
|
||||
var config Float64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -168,6 +174,9 @@ type Float64ObservableGaugeConfig struct {
|
||||
func NewFloat64ObservableGaugeConfig(opts ...Float64ObservableGaugeOption) Float64ObservableGaugeConfig {
|
||||
var config Float64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+9
@@ -50,6 +50,9 @@ type Int64ObservableCounterConfig struct {
|
||||
func NewInt64ObservableCounterConfig(opts ...Int64ObservableCounterOption) Int64ObservableCounterConfig {
|
||||
var config Int64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -110,6 +113,9 @@ func NewInt64ObservableUpDownCounterConfig(
|
||||
) Int64ObservableUpDownCounterConfig {
|
||||
var config Int64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -167,6 +173,9 @@ type Int64ObservableGaugeConfig struct {
|
||||
func NewInt64ObservableGaugeConfig(opts ...Int64ObservableGaugeOption) Int64ObservableGaugeConfig {
|
||||
var config Int64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+7
@@ -42,11 +42,18 @@ type MeterOption interface {
|
||||
applyMeter(MeterConfig) MeterConfig
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewMeterConfig creates a new MeterConfig and applies
|
||||
// all the given options.
|
||||
func NewMeterConfig(opts ...MeterOption) MeterConfig {
|
||||
var config MeterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyMeter(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+39
-12
@@ -24,10 +24,10 @@ all instruments fall into two overlapping logical categories: asynchronous or
|
||||
synchronous, and int64 or float64.
|
||||
|
||||
All synchronous instruments ([Int64Counter], [Int64UpDownCounter],
|
||||
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
|
||||
[Float64Histogram]) are used to measure the operation and performance of source
|
||||
code during the source code execution. These instruments only make measurements
|
||||
when the source code they instrument is run.
|
||||
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
|
||||
[Float64Histogram], and [Float64Gauge]) are used to measure the operation and
|
||||
performance of source code during the source code execution. These instruments
|
||||
only make measurements when the source code they instrument is run.
|
||||
|
||||
All asynchronous instruments ([Int64ObservableCounter],
|
||||
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
|
||||
@@ -50,9 +50,11 @@ incrementally increase in value. UpDownCounters ([Int64UpDownCounter],
|
||||
values that can increase and decrease. When more information needs to be
|
||||
conveyed about all the synchronous measurements made during a collection cycle,
|
||||
a Histogram ([Int64Histogram] and [Float64Histogram]) should be used. Finally,
|
||||
when just the most recent measurement needs to be conveyed about an
|
||||
asynchronous measurement, a Gauge ([Int64ObservableGauge] and
|
||||
[Float64ObservableGauge]) should be used.
|
||||
when just the most recent measurement needs to be conveyed, a Gauge
|
||||
([Int64Gauge], [Float64Gauge], [Int64ObservableGauge], and
|
||||
[Float64ObservableGauge]) should be used: the synchronous variants record an
|
||||
instantaneous value at a specific point in code, while the observable variants
|
||||
sample the value via a callback once per collection cycle.
|
||||
|
||||
See the [OpenTelemetry documentation] for more information about instruments
|
||||
and their intended use.
|
||||
@@ -80,11 +82,11 @@ Measurements are made by recording values and information about the values with
|
||||
an instrument. How these measurements are recorded depends on the instrument.
|
||||
|
||||
Measurements for synchronous instruments ([Int64Counter], [Int64UpDownCounter],
|
||||
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
|
||||
[Float64Histogram]) are recorded using the instrument methods directly. All
|
||||
counter instruments have an Add method that is used to measure an increment
|
||||
value, and all histogram instruments have a Record method to measure a data
|
||||
point.
|
||||
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
|
||||
[Float64Histogram], and [Float64Gauge]) are recorded using the instrument
|
||||
methods directly. All counter instruments have an Add method that is used to
|
||||
measure an increment value, and all histogram and synchronous gauge
|
||||
instruments have a Record method to measure a data point.
|
||||
|
||||
Asynchronous instruments ([Int64ObservableCounter],
|
||||
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
|
||||
@@ -107,6 +109,31 @@ respectively):
|
||||
If the criteria are not met, use the RegisterCallback method of the [Meter] that
|
||||
created the instrument to register a [Callback].
|
||||
|
||||
# Avoiding Expensive Computations
|
||||
|
||||
All synchronous instruments provide an Enabled method that reports whether the
|
||||
instrument will process measurements for the given context. When no SDK is
|
||||
registered or the instrument is otherwise disabled, Enabled returns false. This
|
||||
can be used to avoid expensive measurement work when a measurement will not be
|
||||
recorded:
|
||||
|
||||
if counter.Enabled(ctx) {
|
||||
counter.Add(ctx, 1, metric.WithAttributes(expensiveAttributes()...))
|
||||
}
|
||||
|
||||
This is especially valuable when computing attributes is expensive.
|
||||
[WithAttributes] performs non-trivial work on every call to build an
|
||||
[attribute.Set] from the provided attributes, and that work is wasted if the
|
||||
measurement is not recorded.
|
||||
|
||||
For performance sensitive code where the same attribute set is used repeatedly,
|
||||
prefer [WithAttributeSet]. It accepts a pre-built [attribute.Set], letting you
|
||||
pay the construction cost once and reuse it across many measurements:
|
||||
|
||||
attrs := attribute.NewSet(attribute.String("key", "val"))
|
||||
// ... later, on each call:
|
||||
counter.Add(ctx, 1, metric.WithAttributeSet(attrs))
|
||||
|
||||
# API Implementations
|
||||
|
||||
This package does not conform to the standard Go versioning policy, all of its
|
||||
|
||||
+33
-6
@@ -3,7 +3,9 @@
|
||||
|
||||
package metric // import "go.opentelemetry.io/otel/metric"
|
||||
|
||||
import "go.opentelemetry.io/otel/attribute"
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
// Observable is used as a grouping mechanism for all instruments that are
|
||||
// updated within a Callback.
|
||||
@@ -228,6 +230,9 @@ type AddConfig struct {
|
||||
func NewAddConfig(opts []AddOption) AddConfig {
|
||||
config := AddConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyAdd(config)
|
||||
}
|
||||
return config
|
||||
@@ -253,6 +258,9 @@ type RecordConfig struct {
|
||||
func NewRecordConfig(opts []RecordOption) RecordConfig {
|
||||
config := RecordConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyRecord(config)
|
||||
}
|
||||
return config
|
||||
@@ -278,6 +286,9 @@ type ObserveConfig struct {
|
||||
func NewObserveConfig(opts []ObserveOption) ObserveConfig {
|
||||
config := ObserveConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyObserve(config)
|
||||
}
|
||||
return config
|
||||
@@ -299,6 +310,10 @@ type attrOpt struct {
|
||||
set attribute.Set
|
||||
}
|
||||
|
||||
func (o *attrOpt) Set(set attribute.Set) {
|
||||
o.set = set
|
||||
}
|
||||
|
||||
// mergeSets returns the union of keys between a and b. Any duplicate keys will
|
||||
// use the value associated with b.
|
||||
func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
@@ -311,7 +326,7 @@ func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
return attribute.NewSet(merged...)
|
||||
}
|
||||
|
||||
func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
func (o *attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -322,7 +337,7 @@ func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
func (o *attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -333,7 +348,7 @@ func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
func (o *attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -350,8 +365,14 @@ func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
// If multiple WithAttributeSet or WithAttributes options are passed the
|
||||
// attributes will be merged together in the order they are passed. Attributes
|
||||
// with duplicate keys will use the last value passed.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][attribute.Set], which can be
|
||||
// used to replace the option's attribute set and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
return attrOpt{set: attributes}
|
||||
return &attrOpt{set: attributes}
|
||||
}
|
||||
|
||||
// WithAttributes converts attributes into an attribute Set and sets the Set to
|
||||
@@ -369,8 +390,14 @@ func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
//
|
||||
// See [WithAttributeSet] for information about how multiple WithAttributes are
|
||||
// merged.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][[]attribute.KeyValue], which can be
|
||||
// used to replace the option's attributes and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributes(attributes ...attribute.KeyValue) MeasurementOption {
|
||||
cp := make([]attribute.KeyValue, len(attributes))
|
||||
copy(cp, attributes)
|
||||
return attrOpt{set: attribute.NewSet(cp...)}
|
||||
return &attrOpt{set: attribute.NewSet(cp...)}
|
||||
}
|
||||
|
||||
+12
@@ -51,6 +51,9 @@ type Float64CounterConfig struct {
|
||||
func NewFloat64CounterConfig(opts ...Float64CounterOption) Float64CounterConfig {
|
||||
var config Float64CounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Counter(config)
|
||||
}
|
||||
return config
|
||||
@@ -116,6 +119,9 @@ type Float64UpDownCounterConfig struct {
|
||||
func NewFloat64UpDownCounterConfig(opts ...Float64UpDownCounterOption) Float64UpDownCounterConfig {
|
||||
var config Float64UpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64UpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -182,6 +188,9 @@ type Float64HistogramConfig struct {
|
||||
func NewFloat64HistogramConfig(opts ...Float64HistogramOption) Float64HistogramConfig {
|
||||
var config Float64HistogramConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Histogram(config)
|
||||
}
|
||||
return config
|
||||
@@ -251,6 +260,9 @@ type Float64GaugeConfig struct {
|
||||
func NewFloat64GaugeConfig(opts ...Float64GaugeOption) Float64GaugeConfig {
|
||||
var config Float64GaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Gauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+12
@@ -51,6 +51,9 @@ type Int64CounterConfig struct {
|
||||
func NewInt64CounterConfig(opts ...Int64CounterOption) Int64CounterConfig {
|
||||
var config Int64CounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Counter(config)
|
||||
}
|
||||
return config
|
||||
@@ -116,6 +119,9 @@ type Int64UpDownCounterConfig struct {
|
||||
func NewInt64UpDownCounterConfig(opts ...Int64UpDownCounterOption) Int64UpDownCounterConfig {
|
||||
var config Int64UpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64UpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -182,6 +188,9 @@ type Int64HistogramConfig struct {
|
||||
func NewInt64HistogramConfig(opts ...Int64HistogramOption) Int64HistogramConfig {
|
||||
var config Int64HistogramConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Histogram(config)
|
||||
}
|
||||
return config
|
||||
@@ -251,6 +260,9 @@ type Int64GaugeConfig struct {
|
||||
func NewInt64GaugeConfig(opts ...Int64GaugeOption) Int64GaugeConfig {
|
||||
var config Int64GaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Gauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+60
-12
@@ -5,6 +5,9 @@ package propagation // import "go.opentelemetry.io/otel/propagation"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/baggage"
|
||||
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||
@@ -13,11 +16,18 @@ import (
|
||||
const (
|
||||
baggageHeader = "baggage"
|
||||
|
||||
maxParseErrors = 5
|
||||
|
||||
// W3C Baggage specification limits.
|
||||
// https://www.w3.org/TR/baggage/#limits
|
||||
maxMembers = 64
|
||||
maxMembers = 64
|
||||
maxBytesPerBaggageString = 8192
|
||||
)
|
||||
|
||||
// handleExtractErrOnce limits error reporting for attacker-controlled baggage headers
|
||||
// to one process-wide emission, preventing repeated extraction from flooding logs.
|
||||
var handleExtractErrOnce sync.Once
|
||||
|
||||
// Baggage is a propagator that supports the W3C Baggage format.
|
||||
//
|
||||
// This propagates user-defined baggage associated with a trace. The complete
|
||||
@@ -57,7 +67,9 @@ func extractSingleBaggage(parent context.Context, carrier TextMapCarrier) contex
|
||||
|
||||
bag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
handleExtractErrOnce.Do(func() {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
})
|
||||
}
|
||||
if bag.Len() == 0 {
|
||||
return parent
|
||||
@@ -72,24 +84,60 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
|
||||
}
|
||||
|
||||
var members []baggage.Member
|
||||
for _, bStr := range bVals {
|
||||
currBag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
var totalBytes int
|
||||
var parseErrors int
|
||||
var truncateErr error
|
||||
for i, bStr := range bVals {
|
||||
if i > 0 {
|
||||
totalBytes++ // comma separator between combined header values
|
||||
}
|
||||
if currBag.Len() == 0 {
|
||||
continue
|
||||
totalBytes += len(bStr)
|
||||
if totalBytes > maxBytesPerBaggageString {
|
||||
// Per the W3C Baggage spec, the byte limit applies to the
|
||||
// combination of all baggage headers, not each header
|
||||
// individually. Mirror the single-header behavior of
|
||||
// reporting the error and returning the parent context
|
||||
// with no baggage attached.
|
||||
handleExtractErrOnce.Do(func() {
|
||||
errorhandler.GetErrorHandler().Handle(fmt.Errorf(
|
||||
"baggage: aggregate header size %d exceeds %d byte limit",
|
||||
totalBytes,
|
||||
maxBytesPerBaggageString,
|
||||
))
|
||||
})
|
||||
return parent
|
||||
}
|
||||
members = append(members, currBag.Members()...)
|
||||
if len(members) >= maxMembers {
|
||||
break
|
||||
|
||||
// If members exceed the limit, stop parsing baggage.
|
||||
if len(members) <= maxMembers {
|
||||
currBag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
parseErrors++
|
||||
if parseErrors <= maxParseErrors {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
}
|
||||
}
|
||||
if currBag.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
members = append(members, currBag.Members()...)
|
||||
}
|
||||
}
|
||||
|
||||
if dropped := parseErrors - maxParseErrors; dropped > 0 {
|
||||
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more error(s)", dropped))
|
||||
}
|
||||
|
||||
b, err := baggage.New(members...)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
}
|
||||
if truncateErr != nil {
|
||||
handleExtractErrOnce.Do(func() {
|
||||
errorhandler.GetErrorHandler().Handle(truncateErr)
|
||||
})
|
||||
}
|
||||
|
||||
if b.Len() == 0 {
|
||||
return parent
|
||||
}
|
||||
|
||||
+13
-3
@@ -25,7 +25,7 @@ type config struct {
|
||||
cardinalityLimit int
|
||||
}
|
||||
|
||||
const defaultCardinalityLimit = 0
|
||||
const defaultCardinalityLimit = 2000
|
||||
|
||||
// readerSignals returns a force-flush and shutdown function for a
|
||||
// MeterProvider to call in their respective options. All Readers c contains
|
||||
@@ -70,6 +70,10 @@ func unifyShutdown(funcs []func(context.Context) error) func(context.Context) er
|
||||
}
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// newConfig returns a config configured with options.
|
||||
func newConfig(options []Option) config {
|
||||
conf := config{
|
||||
@@ -81,6 +85,9 @@ func newConfig(options []Option) config {
|
||||
conf = o.apply(conf)
|
||||
}
|
||||
for _, o := range options {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
conf = o.apply(conf)
|
||||
}
|
||||
return conf
|
||||
@@ -150,7 +157,7 @@ func WithView(views ...View) Option {
|
||||
// exemplar reservoir, but the exemplar reservoir makes the final decision of
|
||||
// whether to store an exemplar.
|
||||
//
|
||||
// By default, the [exemplar.SampledFilter]
|
||||
// By default, the [exemplar.TraceBasedFilter]
|
||||
// is used. Exemplars can be entirely disabled by providing the
|
||||
// [exemplar.AlwaysOffFilter].
|
||||
func WithExemplarFilter(filter exemplar.Filter) Option {
|
||||
@@ -165,7 +172,10 @@ func WithExemplarFilter(filter exemplar.Filter) Option {
|
||||
// The cardinality limit is the hard limit on the number of metric datapoints
|
||||
// that can be collected for a single instrument in a single collect cycle.
|
||||
//
|
||||
// Setting this to a zero or negative value means no limit is applied.
|
||||
// By default, if this option is not used, a limit of
|
||||
// 2000 is applied.
|
||||
//
|
||||
// Setting this to a zero or negative means no limit is applied.
|
||||
// This value applies to all instrument kinds, but can be overridden per kind by
|
||||
// the reader's cardinality limit selector (see [WithCardinalityLimitSelector]).
|
||||
func WithCardinalityLimit(limit int) Option {
|
||||
|
||||
+7
-5
@@ -24,6 +24,10 @@
|
||||
// View. Views allow users that run OpenTelemetry instrumented code to modify
|
||||
// the generated data of that instrumentation.
|
||||
//
|
||||
// Note that attributes filtered out by a View may still appear on Exemplars,
|
||||
// because Exemplars are recorded with the dropped measurement attributes
|
||||
// when View attribute filtering is applied.
|
||||
//
|
||||
// The data generated by a MeterProvider needs to include information about its
|
||||
// origin. A MeterProvider needs to be configured with a Resource, using the
|
||||
// WithResource MeterProviderOption, to include this information. This Resource
|
||||
@@ -44,9 +48,7 @@
|
||||
// Cardinality refers to the number of unique attributes collected. High cardinality can lead to
|
||||
// excessive memory usage, increased storage costs, and backend performance issues.
|
||||
//
|
||||
// Currently, the OpenTelemetry Go Metric SDK does not enforce a cardinality limit by default
|
||||
// (note that this may change in a future release). Use [WithCardinalityLimit] to set the
|
||||
// cardinality limit as desired.
|
||||
// By default, the OpenTelemetry Go Metric SDK enforces a cardinality limit of 2000.
|
||||
//
|
||||
// New attribute sets are dropped when the cardinality limit is reached. The measurement of
|
||||
// these sets are aggregated into
|
||||
@@ -57,8 +59,8 @@
|
||||
//
|
||||
// Recommendations:
|
||||
//
|
||||
// - Set the limit based on the theoretical maximum combinations or expected
|
||||
// active combinations. The OpenTelemetry Specification recommends a default of 2000.
|
||||
// - Tune the limit based on the theoretical maximum combinations or expected
|
||||
// active combinations. The SDK default is 2000.
|
||||
// - A too high of a limit increases worst-case memory overhead in the SDK and may cause downstream
|
||||
// issues for databases that cannot handle high cardinality.
|
||||
// - A too low of a limit causes loss of attribute detail as more data falls into overflow.
|
||||
|
||||
+11
@@ -4,6 +4,7 @@
|
||||
package metric // import "go.opentelemetry.io/otel/sdk/metric"
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
@@ -19,9 +20,19 @@ type ExemplarReservoirProviderSelector func(Aggregation) exemplar.ReservoirProvi
|
||||
// reservoirFunc returns the appropriately configured exemplar reservoir
|
||||
// creation func based on the passed InstrumentKind and filter configuration.
|
||||
func reservoirFunc[N int64 | float64](
|
||||
kind InstrumentKind,
|
||||
provider exemplar.ReservoirProvider,
|
||||
filter exemplar.Filter,
|
||||
) func(attribute.Set) aggregate.FilteredExemplarReservoir[N] {
|
||||
if reflect.ValueOf(filter).Pointer() == reflect.ValueOf(exemplar.AlwaysOffFilter).Pointer() {
|
||||
return aggregate.DropReservoir[N]
|
||||
}
|
||||
if (kind == InstrumentKindObservableCounter || kind == InstrumentKindObservableUpDownCounter || kind == InstrumentKindObservableGauge) &&
|
||||
reflect.ValueOf(filter).Pointer() == reflect.ValueOf(exemplar.TraceBasedFilter).Pointer() {
|
||||
// Asynchronous instruments do not accept context, so TraceBasedFilter
|
||||
// will never record any exemplars.
|
||||
return aggregate.DropReservoir[N]
|
||||
}
|
||||
return func(attrs attribute.Set) aggregate.FilteredExemplarReservoir[N] {
|
||||
return aggregate.NewFilteredExemplarReservoir[N](filter, provider(attrs))
|
||||
}
|
||||
|
||||
+70
-108
@@ -8,7 +8,6 @@ import (
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
@@ -27,19 +26,7 @@ func FixedSizeReservoirProvider(k int) ReservoirProvider {
|
||||
// sample each one. If there are more than k, the Reservoir will then randomly
|
||||
// sample all additional measurement with a decreasing probability.
|
||||
func NewFixedSizeReservoir(k int) *FixedSizeReservoir {
|
||||
if k < 0 {
|
||||
k = 0
|
||||
}
|
||||
// Use math.MaxInt32 instead of math.MaxUint32 to prevent overflowing int
|
||||
// on 32-bit systems.
|
||||
if k > math.MaxInt32 {
|
||||
k = math.MaxInt32
|
||||
}
|
||||
return &FixedSizeReservoir{
|
||||
storage: newStorage(k),
|
||||
// Above we ensure k is positive, and less than MaxInt32.
|
||||
nextTracker: newNextTracker(uint32(k)), // nolint: gosec
|
||||
}
|
||||
return newFixedSizeReservoir(newStorage(k))
|
||||
}
|
||||
|
||||
var _ Reservoir = &FixedSizeReservoir{}
|
||||
@@ -51,7 +38,42 @@ var _ Reservoir = &FixedSizeReservoir{}
|
||||
type FixedSizeReservoir struct {
|
||||
reservoir.ConcurrentSafe
|
||||
*storage
|
||||
*nextTracker
|
||||
mu sync.Mutex
|
||||
|
||||
// count is the number of measurement seen.
|
||||
count int64
|
||||
// next is the next count that will store a measurement at a random index
|
||||
// once the reservoir has been filled.
|
||||
next int64
|
||||
// w is the largest random number in a distribution that is used to compute
|
||||
// the next next.
|
||||
w float64
|
||||
}
|
||||
|
||||
func newFixedSizeReservoir(s *storage) *FixedSizeReservoir {
|
||||
r := &FixedSizeReservoir{
|
||||
storage: s,
|
||||
}
|
||||
if cap(r.measurements) > 0 {
|
||||
r.reset()
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// randomFloat64 returns, as a float64, a uniform pseudo-random number in the
|
||||
// open interval (0.0,1.0).
|
||||
func (*FixedSizeReservoir) randomFloat64() float64 {
|
||||
// TODO: Use an algorithm that avoids rejection sampling. For example:
|
||||
//
|
||||
// const precision = 1 << 53 // 2^53
|
||||
// // Generate an integer in [1, 2^53 - 1]
|
||||
// v := rand.Uint64() % (precision - 1) + 1
|
||||
// return float64(v) / float64(precision)
|
||||
f := rand.Float64()
|
||||
for f == 0 {
|
||||
f = rand.Float64()
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Offer accepts the parameters associated with a measurement. The
|
||||
@@ -66,6 +88,10 @@ type FixedSizeReservoir struct {
|
||||
// parameters are the value and dropped (filtered) attributes of the
|
||||
// measurement respectively.
|
||||
func (r *FixedSizeReservoir) Offer(ctx context.Context, t time.Time, n Value, a []attribute.KeyValue) {
|
||||
if cap(r.measurements) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// The following algorithm is "Algorithm L" from Li, Kim-Hung (4 December
|
||||
// 1994). "Reservoir-Sampling Algorithms of Time Complexity
|
||||
// O(n(1+log(N/n)))". ACM Transactions on Mathematical Software. 20 (4):
|
||||
@@ -107,65 +133,25 @@ func (r *FixedSizeReservoir) Offer(ctx context.Context, t time.Time, n Value, a
|
||||
// https://github.com/MrAlias/reservoir-sampling for a performance
|
||||
// comparison of reservoir sampling algorithms.
|
||||
|
||||
count, next := r.incrementCount()
|
||||
if count < r.k {
|
||||
r.store(ctx, int(count), t, n, a)
|
||||
} else if count == next {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if int(r.count) < cap(r.measurements) {
|
||||
r.store(ctx, int(r.count), t, n, a)
|
||||
} else if r.count == r.next {
|
||||
// Overwrite a random existing measurement with the one offered.
|
||||
idx := rand.IntN(int(r.k))
|
||||
idx := int(rand.Int64N(int64(cap(r.measurements))))
|
||||
r.store(ctx, idx, t, n, a)
|
||||
r.wMu.Lock()
|
||||
defer r.wMu.Unlock()
|
||||
newCount, newNext := r.loadCountAndNext()
|
||||
if newNext < next || newCount < count {
|
||||
// This Observe() raced with Collect(), and r.reset() has been
|
||||
// called since r.incrementCount(). Skip the call to advance in
|
||||
// this case because our exemplar may have been collected in the
|
||||
// previous interval.
|
||||
return
|
||||
}
|
||||
r.advance()
|
||||
}
|
||||
}
|
||||
|
||||
// Collect returns all the held exemplars.
|
||||
//
|
||||
// The Reservoir state is preserved after this call.
|
||||
func (r *FixedSizeReservoir) Collect(dest *[]Exemplar) {
|
||||
r.storage.Collect(dest)
|
||||
// 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
|
||||
// measurements are offered, but it will also prioritize those new
|
||||
// measurements that are made over the older collection cycle ones.
|
||||
r.reset()
|
||||
}
|
||||
|
||||
func newNextTracker(k uint32) *nextTracker {
|
||||
nt := &nextTracker{k: k}
|
||||
nt.reset()
|
||||
return nt
|
||||
}
|
||||
|
||||
type nextTracker struct {
|
||||
// countAndNext holds the current counts in the lower 32 bits and the next
|
||||
// value in the upper 32 bits.
|
||||
countAndNext atomic.Uint64
|
||||
// w is the largest random number in a distribution that is used to compute
|
||||
// the next next.
|
||||
w float64
|
||||
// wMu ensures w is kept consistent with next during advance and reset.
|
||||
wMu sync.Mutex
|
||||
// k is the number of measurements that can be stored in the reservoir.
|
||||
k uint32
|
||||
r.count++
|
||||
}
|
||||
|
||||
// reset resets r to the initial state.
|
||||
func (r *nextTracker) reset() {
|
||||
r.wMu.Lock()
|
||||
defer r.wMu.Unlock()
|
||||
func (r *FixedSizeReservoir) reset() {
|
||||
// This resets the number of exemplars known.
|
||||
r.count = 0
|
||||
// Random index inserts should only happen after the storage is full.
|
||||
r.setCountAndNext(0, r.k)
|
||||
r.next = int64(cap(r.measurements))
|
||||
|
||||
// Initial random number in the series used to generate r.next.
|
||||
//
|
||||
@@ -176,40 +162,14 @@ func (r *nextTracker) reset() {
|
||||
// This maps the uniform random number in (0,1) to a geometric distribution
|
||||
// over the same interval. The mean of the distribution is inversely
|
||||
// proportional to the storage capacity.
|
||||
r.w = math.Exp(math.Log(randomFloat64()) / float64(r.k))
|
||||
r.w = math.Exp(math.Log(r.randomFloat64()) / float64(cap(r.measurements)))
|
||||
|
||||
r.advance()
|
||||
}
|
||||
|
||||
// incrementCount increments the count. It returns the count before the
|
||||
// increment and the current next value.
|
||||
func (r *nextTracker) incrementCount() (uint32, uint32) {
|
||||
n := r.countAndNext.Add(1)
|
||||
// Both count and next are stored in the upper and lower 32 bits, and thus
|
||||
// can't overflow.
|
||||
return uint32(n&((1<<32)-1) - 1), uint32(n >> 32) // nolint: gosec
|
||||
}
|
||||
|
||||
// incrementNext increments the next value.
|
||||
func (r *nextTracker) incrementNext(inc uint32) {
|
||||
r.countAndNext.Add(uint64(inc) << 32)
|
||||
}
|
||||
|
||||
// setCountAndNext sets the count and next values.
|
||||
func (r *nextTracker) setCountAndNext(count, next uint32) {
|
||||
r.countAndNext.Store(uint64(next)<<32 + uint64(count))
|
||||
}
|
||||
|
||||
func (r *nextTracker) loadCountAndNext() (uint32, uint32) {
|
||||
n := r.countAndNext.Load()
|
||||
// Both count and next are stored in the upper and lower 32 bits, and thus
|
||||
// can't overflow.
|
||||
return uint32(n&((1<<32)-1) - 1), uint32(n >> 32) // nolint: gosec
|
||||
}
|
||||
|
||||
// advance updates the count at which the offered measurement will overwrite an
|
||||
// existing exemplar.
|
||||
func (r *nextTracker) advance() {
|
||||
func (r *FixedSizeReservoir) advance() {
|
||||
// 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
|
||||
@@ -222,7 +182,7 @@ func (r *nextTracker) advance() {
|
||||
// therefore the next r.w will be based on the same distribution (i.e.
|
||||
// `max(u_1,u_2,...,u_k)`). Therefore, we can sample the next r.w by
|
||||
// computing the next random number `u` and take r.w as `w * u^(1/k)`.
|
||||
r.w *= math.Exp(math.Log(randomFloat64()) / float64(r.k))
|
||||
r.w *= math.Exp(math.Log(r.randomFloat64()) / float64(cap(r.measurements)))
|
||||
// Use the new random number in the series to calculate the count of the
|
||||
// next measurement that will be stored.
|
||||
//
|
||||
@@ -233,21 +193,23 @@ func (r *nextTracker) advance() {
|
||||
//
|
||||
// Important to note, the new r.next will always be at least 1 more than
|
||||
// the last r.next.
|
||||
r.incrementNext(uint32(math.Log(randomFloat64())/math.Log(1-r.w)) + 1)
|
||||
r.next += int64(math.Log(r.randomFloat64())/math.Log(1-r.w)) + 1
|
||||
}
|
||||
|
||||
// randomFloat64 returns, as a float64, a uniform pseudo-random number in the
|
||||
// open interval (0.0,1.0).
|
||||
func randomFloat64() float64 {
|
||||
// TODO: Use an algorithm that avoids rejection sampling. For example:
|
||||
//
|
||||
// const precision = 1 << 53 // 2^53
|
||||
// // Generate an integer in [1, 2^53 - 1]
|
||||
// v := rand.Uint64() % (precision - 1) + 1
|
||||
// return float64(v) / float64(precision)
|
||||
f := rand.Float64()
|
||||
for f == 0 {
|
||||
f = rand.Float64()
|
||||
// Collect returns all the held exemplars.
|
||||
//
|
||||
// The Reservoir state is preserved after this call.
|
||||
func (r *FixedSizeReservoir) Collect(dest *[]Exemplar) {
|
||||
if cap(r.measurements) == 0 {
|
||||
*dest = (*dest)[:0]
|
||||
return
|
||||
}
|
||||
return f
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.storage.Collect(dest)
|
||||
// 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
|
||||
// measurements are offered, but it will also prioritize those new
|
||||
// measurements that are made over the older collection cycle ones.
|
||||
r.reset()
|
||||
}
|
||||
|
||||
+4
@@ -141,6 +141,10 @@ type Stream struct {
|
||||
// the attribute will not be recorded, otherwise, if it returns true, it
|
||||
// will record the attribute.
|
||||
//
|
||||
// Note that attributes filtered out by a View may still appear on Exemplars,
|
||||
// because Exemplars are recorded with the dropped measurement attributes
|
||||
// when View attribute filtering is applied.
|
||||
//
|
||||
// Use NewAllowKeysFilter from "go.opentelemetry.io/otel/attribute" to
|
||||
// provide an allow-list of attribute keys here.
|
||||
AttributeFilter attribute.Filter
|
||||
|
||||
+3
-3
@@ -36,8 +36,8 @@ type Builder[N int64 | float64] struct {
|
||||
// ReservoirFunc is the factory function used by aggregate functions to
|
||||
// create new exemplar reservoirs for a new seen attribute set.
|
||||
//
|
||||
// If this is not provided a default factory function that returns an
|
||||
// dropReservoir reservoir will be used.
|
||||
// If this is not provided a default factory function that returns a
|
||||
// DropReservoir reservoir will be used.
|
||||
ReservoirFunc func(attribute.Set) FilteredExemplarReservoir[N]
|
||||
// AggregationLimit is the cardinality limit of measurement attributes. Any
|
||||
// measurement for new attributes once the limit has been reached will be
|
||||
@@ -54,7 +54,7 @@ func (b Builder[N]) resFunc() func(attribute.Set) FilteredExemplarReservoir[N] {
|
||||
return b.ReservoirFunc
|
||||
}
|
||||
|
||||
return dropReservoir
|
||||
return DropReservoir
|
||||
}
|
||||
|
||||
type fltrMeasure[N int64 | float64] func(ctx context.Context, value N, fltrAttr attribute.Set, droppedAttr []attribute.KeyValue)
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ import (
|
||||
"go.opentelemetry.io/otel/sdk/metric/exemplar"
|
||||
)
|
||||
|
||||
// dropReservoir returns a [FilteredReservoir] that drops all measurements it is offered.
|
||||
func dropReservoir[N int64 | float64](attribute.Set) FilteredExemplarReservoir[N] {
|
||||
// DropReservoir returns a [FilteredExemplarReservoir] that drops all measurements it is offered.
|
||||
func DropReservoir[N int64 | float64](attribute.Set) FilteredExemplarReservoir[N] {
|
||||
return &dropRes[N]{}
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+10
-4
@@ -26,8 +26,9 @@ const (
|
||||
|
||||
// expoHistogramDataPoint is a single data point in an exponential histogram.
|
||||
type expoHistogramDataPoint[N int64 | float64] struct {
|
||||
attrs attribute.Set
|
||||
res FilteredExemplarReservoir[N]
|
||||
attrs attribute.Set
|
||||
res FilteredExemplarReservoir[N]
|
||||
dropExemplars bool
|
||||
|
||||
minMax atomicMinMax[N]
|
||||
sum atomicCounter[N]
|
||||
@@ -349,13 +350,18 @@ func (e *expoHistogram[N]) measure(
|
||||
v, ok = e.values[fltrAttr.Equivalent()]
|
||||
if !ok {
|
||||
v = newExpoHistogramDataPoint[N](fltrAttr, e.maxSize, e.maxScale, e.noMinMax, e.noSum)
|
||||
v.res = e.newRes(fltrAttr)
|
||||
r := e.newRes(fltrAttr)
|
||||
_, isDrop := r.(*dropRes[N])
|
||||
v.res = r
|
||||
v.dropExemplars = isDrop
|
||||
|
||||
e.values[fltrAttr.Equivalent()] = v
|
||||
}
|
||||
}
|
||||
v.record(value)
|
||||
v.res.Offer(ctx, value, droppedAttr)
|
||||
if !v.dropExemplars {
|
||||
v.res.Offer(ctx, value, droppedAttr)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *expoHistogram[N]) delta(
|
||||
|
||||
+24
-12
@@ -17,8 +17,9 @@ import (
|
||||
|
||||
// histogramPoint is a single histogram point, used in delta aggregations.
|
||||
type histogramPoint[N int64 | float64] struct {
|
||||
attrs attribute.Set
|
||||
res FilteredExemplarReservoir[N]
|
||||
attrs attribute.Set
|
||||
res FilteredExemplarReservoir[N]
|
||||
dropExemplars bool
|
||||
histogramPointCounters[N]
|
||||
}
|
||||
|
||||
@@ -28,9 +29,10 @@ type hotColdHistogramPoint[N int64 | float64] struct {
|
||||
hcwg hotColdWaitGroup
|
||||
hotColdPoint [2]histogramPointCounters[N]
|
||||
|
||||
attrs attribute.Set
|
||||
res FilteredExemplarReservoir[N]
|
||||
startTime time.Time
|
||||
attrs attribute.Set
|
||||
res FilteredExemplarReservoir[N]
|
||||
startTime time.Time
|
||||
dropExemplars bool
|
||||
}
|
||||
|
||||
// histogramPointCounters contains only the atomic counter data, and is used by
|
||||
@@ -113,9 +115,12 @@ func (s *deltaHistogram[N]) measure(
|
||||
hotIdx := s.hcwg.start()
|
||||
defer s.hcwg.done(hotIdx)
|
||||
h := s.hotColdValMap[hotIdx].LoadOrStoreAttr(fltrAttr, func(attr attribute.Set) any {
|
||||
r := s.newRes(attr)
|
||||
_, isDrop := r.(*dropRes[N])
|
||||
hPt := &histogramPoint[N]{
|
||||
res: s.newRes(attr),
|
||||
attrs: attr,
|
||||
res: r,
|
||||
attrs: attr,
|
||||
dropExemplars: isDrop,
|
||||
// N+1 buckets. For example:
|
||||
//
|
||||
// bounds = [0, 5, 10]
|
||||
@@ -141,7 +146,9 @@ func (s *deltaHistogram[N]) measure(
|
||||
if !s.noSum {
|
||||
h.total.add(value)
|
||||
}
|
||||
h.res.Offer(ctx, value, droppedAttr)
|
||||
if !h.dropExemplars {
|
||||
h.res.Offer(ctx, value, droppedAttr)
|
||||
}
|
||||
}
|
||||
|
||||
// newDeltaHistogram returns a histogram that is reset each time it is
|
||||
@@ -282,9 +289,13 @@ func (s *cumulativeHistogram[N]) measure(
|
||||
droppedAttr []attribute.KeyValue,
|
||||
) {
|
||||
h := s.values.LoadOrStoreAttr(fltrAttr, func(attr attribute.Set) any {
|
||||
r := s.newRes(attr)
|
||||
_, isDrop := r.(*dropRes[N])
|
||||
hPt := &hotColdHistogramPoint[N]{
|
||||
res: s.newRes(attr),
|
||||
attrs: attr,
|
||||
res: r,
|
||||
attrs: attr,
|
||||
startTime: now(),
|
||||
dropExemplars: isDrop,
|
||||
// N+1 buckets. For example:
|
||||
//
|
||||
// bounds = [0, 5, 10]
|
||||
@@ -300,7 +311,6 @@ func (s *cumulativeHistogram[N]) measure(
|
||||
counts: make([]atomic.Uint64, len(s.bounds)+1),
|
||||
},
|
||||
},
|
||||
startTime: now(),
|
||||
}
|
||||
return hPt
|
||||
}).(*hotColdHistogramPoint[N])
|
||||
@@ -322,7 +332,9 @@ func (s *cumulativeHistogram[N]) measure(
|
||||
if !s.noSum {
|
||||
h.hotColdPoint[hotIdx].total.add(value)
|
||||
}
|
||||
h.res.Offer(ctx, value, droppedAttr)
|
||||
if !h.dropExemplars {
|
||||
h.res.Offer(ctx, value, droppedAttr)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *cumulativeHistogram[N]) collect(
|
||||
|
||||
+14
-8
@@ -14,10 +14,11 @@ import (
|
||||
|
||||
// lastValuePoint is timestamped measurement data.
|
||||
type lastValuePoint[N int64 | float64] struct {
|
||||
attrs attribute.Set
|
||||
value atomicN[N]
|
||||
res FilteredExemplarReservoir[N]
|
||||
startTime time.Time
|
||||
attrs attribute.Set
|
||||
value atomicN[N]
|
||||
res FilteredExemplarReservoir[N]
|
||||
startTime time.Time
|
||||
dropExemplars bool
|
||||
}
|
||||
|
||||
// lastValueMap summarizes a set of measurements as the last one made.
|
||||
@@ -33,17 +34,22 @@ func (s *lastValueMap[N]) measure(
|
||||
droppedAttr []attribute.KeyValue,
|
||||
) {
|
||||
lv := s.values.LoadOrStoreAttr(fltrAttr, func(attr attribute.Set) any {
|
||||
r := s.newRes(attr)
|
||||
_, isDrop := r.(*dropRes[N])
|
||||
p := &lastValuePoint[N]{
|
||||
res: s.newRes(attr),
|
||||
attrs: attr,
|
||||
startTime: now(),
|
||||
res: r,
|
||||
attrs: attr,
|
||||
startTime: now(),
|
||||
dropExemplars: isDrop,
|
||||
}
|
||||
p.value.Store(value)
|
||||
return p
|
||||
}).(*lastValuePoint[N])
|
||||
|
||||
lv.value.Store(value)
|
||||
lv.res.Offer(ctx, value, droppedAttr)
|
||||
if !lv.dropExemplars {
|
||||
lv.res.Offer(ctx, value, droppedAttr)
|
||||
}
|
||||
}
|
||||
|
||||
func newDeltaLastValue[N int64 | float64](
|
||||
|
||||
+14
-8
@@ -13,10 +13,11 @@ import (
|
||||
)
|
||||
|
||||
type sumValue[N int64 | float64] struct {
|
||||
n atomicCounter[N]
|
||||
res FilteredExemplarReservoir[N]
|
||||
attrs attribute.Set
|
||||
startTime time.Time
|
||||
n atomicCounter[N]
|
||||
res FilteredExemplarReservoir[N]
|
||||
attrs attribute.Set
|
||||
startTime time.Time
|
||||
dropExemplars bool
|
||||
}
|
||||
|
||||
type sumValueMap[N int64 | float64] struct {
|
||||
@@ -31,17 +32,22 @@ func (s *sumValueMap[N]) measure(
|
||||
droppedAttr []attribute.KeyValue,
|
||||
) {
|
||||
sv := s.values.LoadOrStoreAttr(fltrAttr, func(attr attribute.Set) any {
|
||||
r := s.newRes(attr)
|
||||
_, isDrop := r.(*dropRes[N])
|
||||
return &sumValue[N]{
|
||||
res: s.newRes(attr),
|
||||
attrs: attr,
|
||||
startTime: now(),
|
||||
res: r,
|
||||
attrs: attr,
|
||||
startTime: now(),
|
||||
dropExemplars: isDrop,
|
||||
}
|
||||
}).(*sumValue[N])
|
||||
sv.n.add(value)
|
||||
// It is possible for collection to race with measurement and observe the
|
||||
// exemplar in the batch of metrics after the add() for cumulative sums.
|
||||
// This is an accepted tradeoff to avoid locking during measurement.
|
||||
sv.res.Offer(ctx, value, droppedAttr)
|
||||
if !sv.dropExemplars {
|
||||
sv.res.Offer(ctx, value, droppedAttr)
|
||||
}
|
||||
}
|
||||
|
||||
// newDeltaSum returns an aggregator that summarizes a set of measurements as
|
||||
|
||||
+3
-2
@@ -16,8 +16,8 @@ import (
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/sdk/internal/x"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -54,6 +54,7 @@ var (
|
||||
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
|
||||
|
||||
func put[T any](p *sync.Pool, s *[]T) {
|
||||
clear(*s)
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# Experimental Features
|
||||
|
||||
The Metric SDK contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the OpenTelemetry Go Metric 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
|
||||
|
||||
- [Metric Export Batch Size](#metric-export-batch-size)
|
||||
|
||||
### Metric Export Batch Size
|
||||
|
||||
The metric export can be split into batches before exporting by specifying a maximum number of data points per batch.
|
||||
|
||||
This experimental feature can be enabled by setting the `OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE` environment variable.
|
||||
The value MUST be a positive integer.
|
||||
All other values or an empty value will result in the default behavior of not batching.
|
||||
|
||||
#### Examples
|
||||
|
||||
Enable metrics to be batched by maximum export batch size of 200.
|
||||
|
||||
```console
|
||||
export OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE=200
|
||||
```
|
||||
|
||||
Disable metric export batching.
|
||||
|
||||
```console
|
||||
unset OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE
|
||||
```
|
||||
|
||||
## 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.
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package x contains support for OTel metric 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/metric/internal/x"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
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 reports whether the feature is enabled.
|
||||
func (f Feature[T]) Enabled() bool {
|
||||
_, ok := f.Lookup()
|
||||
return ok
|
||||
}
|
||||
|
||||
// MetricExportBatchSize is an experimental feature flag that controls the
|
||||
// max export batch size for metric data.
|
||||
//
|
||||
// To enable this feature set the OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE environment
|
||||
// variable to a positive integer value.
|
||||
var MetricExportBatchSize = newFeature(
|
||||
"METRIC_EXPORT_BATCH_SIZE",
|
||||
func(v string) (int, bool) {
|
||||
val, err := strconv.Atoi(v)
|
||||
if err == nil && val > 0 {
|
||||
return val, true
|
||||
}
|
||||
return 0, false
|
||||
},
|
||||
)
|
||||
+5
-5
@@ -169,17 +169,17 @@ func (mr *ManualReader) Collect(ctx context.Context, rm *metricdata.ResourceMetr
|
||||
}
|
||||
|
||||
// MarshalLog returns logging data about the ManualReader.
|
||||
func (r *ManualReader) MarshalLog() any {
|
||||
r.mu.Lock()
|
||||
down := r.isShutdown
|
||||
r.mu.Unlock()
|
||||
func (mr *ManualReader) MarshalLog() any {
|
||||
mr.mu.Lock()
|
||||
down := mr.isShutdown
|
||||
mr.mu.Unlock()
|
||||
return struct {
|
||||
Type string
|
||||
Registered bool
|
||||
Shutdown bool
|
||||
}{
|
||||
Type: "ManualReader",
|
||||
Registered: r.sdkProducer.Load() != nil,
|
||||
Registered: mr.sdkProducer.Load() != nil,
|
||||
Shutdown: down,
|
||||
}
|
||||
}
|
||||
|
||||
+83
-34
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/metric/embedded"
|
||||
@@ -70,7 +71,7 @@ func (m *meter) Int64Counter(name string, options ...metric.Int64CounterOption)
|
||||
cfg := metric.NewInt64CounterConfig(options...)
|
||||
const kind = InstrumentKindCounter
|
||||
p := int64InstProvider{m}
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit())
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -88,7 +89,7 @@ func (m *meter) Int64UpDownCounter(
|
||||
cfg := metric.NewInt64UpDownCounterConfig(options...)
|
||||
const kind = InstrumentKindUpDownCounter
|
||||
p := int64InstProvider{m}
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit())
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -102,7 +103,7 @@ func (m *meter) Int64UpDownCounter(
|
||||
func (m *meter) Int64Histogram(name string, options ...metric.Int64HistogramOption) (metric.Int64Histogram, error) {
|
||||
cfg := metric.NewInt64HistogramConfig(options...)
|
||||
p := int64InstProvider{m}
|
||||
i, err := p.lookupHistogram(name, cfg)
|
||||
i, err := p.lookupHistogram(name, cfg, defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -117,7 +118,7 @@ func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (met
|
||||
cfg := metric.NewInt64GaugeConfig(options...)
|
||||
const kind = InstrumentKindGauge
|
||||
p := int64InstProvider{m}
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit())
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -127,7 +128,11 @@ func (m *meter) Int64Gauge(name string, options ...metric.Int64GaugeOption) (met
|
||||
|
||||
// int64ObservableInstrument returns a new observable identified by the Instrument.
|
||||
// It registers callbacks for each reader's pipeline.
|
||||
func (m *meter) int64ObservableInstrument(id Instrument, callbacks []metric.Int64Callback) (int64Observable, error) {
|
||||
func (m *meter) int64ObservableInstrument(
|
||||
id Instrument,
|
||||
allowedKeys []attribute.Key,
|
||||
callbacks []metric.Int64Callback,
|
||||
) (int64Observable, error) {
|
||||
key := instID{
|
||||
Name: id.Name,
|
||||
Description: id.Description,
|
||||
@@ -142,7 +147,7 @@ func (m *meter) int64ObservableInstrument(id Instrument, callbacks []metric.Int6
|
||||
for _, insert := range m.int64Resolver.inserters {
|
||||
// Connect the measure functions for instruments in this pipeline with the
|
||||
// callbacks for this pipeline.
|
||||
in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind))
|
||||
in, err := insert.Instrument(id, allowedKeys, insert.readerDefaultAggregation(id.Kind))
|
||||
if err != nil {
|
||||
return inst, err
|
||||
}
|
||||
@@ -188,7 +193,7 @@ func (m *meter) Int64ObservableCounter(
|
||||
Kind: InstrumentKindObservableCounter,
|
||||
Scope: m.scope,
|
||||
}
|
||||
return m.int64ObservableInstrument(id, cfg.Callbacks())
|
||||
return m.int64ObservableInstrument(id, defaultAttributes(options), cfg.Callbacks())
|
||||
}
|
||||
|
||||
// Int64ObservableUpDownCounter returns a new instrument identified by name and
|
||||
@@ -212,7 +217,7 @@ func (m *meter) Int64ObservableUpDownCounter(
|
||||
Kind: InstrumentKindObservableUpDownCounter,
|
||||
Scope: m.scope,
|
||||
}
|
||||
return m.int64ObservableInstrument(id, cfg.Callbacks())
|
||||
return m.int64ObservableInstrument(id, defaultAttributes(options), cfg.Callbacks())
|
||||
}
|
||||
|
||||
// Int64ObservableGauge returns a new instrument identified by name and
|
||||
@@ -236,7 +241,7 @@ func (m *meter) Int64ObservableGauge(
|
||||
Kind: InstrumentKindObservableGauge,
|
||||
Scope: m.scope,
|
||||
}
|
||||
return m.int64ObservableInstrument(id, cfg.Callbacks())
|
||||
return m.int64ObservableInstrument(id, defaultAttributes(options), cfg.Callbacks())
|
||||
}
|
||||
|
||||
// Float64Counter returns a new instrument identified by name and configured
|
||||
@@ -246,7 +251,7 @@ func (m *meter) Float64Counter(name string, options ...metric.Float64CounterOpti
|
||||
cfg := metric.NewFloat64CounterConfig(options...)
|
||||
const kind = InstrumentKindCounter
|
||||
p := float64InstProvider{m}
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit())
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -264,7 +269,7 @@ func (m *meter) Float64UpDownCounter(
|
||||
cfg := metric.NewFloat64UpDownCounterConfig(options...)
|
||||
const kind = InstrumentKindUpDownCounter
|
||||
p := float64InstProvider{m}
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit())
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -281,7 +286,7 @@ func (m *meter) Float64Histogram(
|
||||
) (metric.Float64Histogram, error) {
|
||||
cfg := metric.NewFloat64HistogramConfig(options...)
|
||||
p := float64InstProvider{m}
|
||||
i, err := p.lookupHistogram(name, cfg)
|
||||
i, err := p.lookupHistogram(name, cfg, defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -296,7 +301,7 @@ func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption)
|
||||
cfg := metric.NewFloat64GaugeConfig(options...)
|
||||
const kind = InstrumentKindGauge
|
||||
p := float64InstProvider{m}
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit())
|
||||
i, err := p.lookup(kind, name, cfg.Description(), cfg.Unit(), defaultAttributes(options))
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
@@ -308,6 +313,7 @@ func (m *meter) Float64Gauge(name string, options ...metric.Float64GaugeOption)
|
||||
// It registers callbacks for each reader's pipeline.
|
||||
func (m *meter) float64ObservableInstrument(
|
||||
id Instrument,
|
||||
allowedKeys []attribute.Key,
|
||||
callbacks []metric.Float64Callback,
|
||||
) (float64Observable, error) {
|
||||
key := instID{
|
||||
@@ -316,7 +322,7 @@ func (m *meter) float64ObservableInstrument(
|
||||
Unit: id.Unit,
|
||||
Kind: id.Kind,
|
||||
}
|
||||
if m.int64ObservableInsts.HasKey(key) && len(callbacks) > 0 {
|
||||
if m.float64ObservableInsts.HasKey(key) && len(callbacks) > 0 {
|
||||
warnRepeatedObservableCallbacks(id)
|
||||
}
|
||||
return m.float64ObservableInsts.Lookup(key, func() (float64Observable, error) {
|
||||
@@ -324,7 +330,7 @@ func (m *meter) float64ObservableInstrument(
|
||||
for _, insert := range m.float64Resolver.inserters {
|
||||
// Connect the measure functions for instruments in this pipeline with the
|
||||
// callbacks for this pipeline.
|
||||
in, err := insert.Instrument(id, insert.readerDefaultAggregation(id.Kind))
|
||||
in, err := insert.Instrument(id, allowedKeys, insert.readerDefaultAggregation(id.Kind))
|
||||
if err != nil {
|
||||
return inst, err
|
||||
}
|
||||
@@ -370,7 +376,7 @@ func (m *meter) Float64ObservableCounter(
|
||||
Kind: InstrumentKindObservableCounter,
|
||||
Scope: m.scope,
|
||||
}
|
||||
return m.float64ObservableInstrument(id, cfg.Callbacks())
|
||||
return m.float64ObservableInstrument(id, defaultAttributes(options), cfg.Callbacks())
|
||||
}
|
||||
|
||||
// Float64ObservableUpDownCounter returns a new instrument identified by name
|
||||
@@ -394,7 +400,7 @@ func (m *meter) Float64ObservableUpDownCounter(
|
||||
Kind: InstrumentKindObservableUpDownCounter,
|
||||
Scope: m.scope,
|
||||
}
|
||||
return m.float64ObservableInstrument(id, cfg.Callbacks())
|
||||
return m.float64ObservableInstrument(id, defaultAttributes(options), cfg.Callbacks())
|
||||
}
|
||||
|
||||
// Float64ObservableGauge returns a new instrument identified by name and
|
||||
@@ -418,7 +424,7 @@ func (m *meter) Float64ObservableGauge(
|
||||
Kind: InstrumentKindObservableGauge,
|
||||
Scope: m.scope,
|
||||
}
|
||||
return m.float64ObservableInstrument(id, cfg.Callbacks())
|
||||
return m.float64ObservableInstrument(id, defaultAttributes(options), cfg.Callbacks())
|
||||
}
|
||||
|
||||
func validateInstrumentName(name string) error {
|
||||
@@ -576,7 +582,8 @@ func (r observer) ObserveFloat64(o metric.Float64Observable, v float64, opts ...
|
||||
|
||||
if _, registered := r.float64[oImpl.observableID]; !registered {
|
||||
if !oImpl.dropAggregation {
|
||||
global.Error(errUnregObserver, "failed to record",
|
||||
global.Error(
|
||||
errUnregObserver, "failed to record",
|
||||
"name", oImpl.name,
|
||||
"description", oImpl.description,
|
||||
"unit", oImpl.unit,
|
||||
@@ -606,7 +613,8 @@ func (r observer) ObserveInt64(o metric.Int64Observable, v int64, opts ...metric
|
||||
|
||||
if _, registered := r.int64[oImpl.observableID]; !registered {
|
||||
if !oImpl.dropAggregation {
|
||||
global.Error(errUnregObserver, "failed to record",
|
||||
global.Error(
|
||||
errUnregObserver, "failed to record",
|
||||
"name", oImpl.name,
|
||||
"description", oImpl.description,
|
||||
"unit", oImpl.unit,
|
||||
@@ -633,7 +641,11 @@ func (noopRegister) Unregister() error {
|
||||
// int64InstProvider provides int64 OpenTelemetry instruments.
|
||||
type int64InstProvider struct{ *meter }
|
||||
|
||||
func (p int64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]aggregate.Measure[int64], error) {
|
||||
func (p int64InstProvider) aggs(
|
||||
kind InstrumentKind,
|
||||
name, desc, u string,
|
||||
allowedKeys []attribute.Key,
|
||||
) ([]aggregate.Measure[int64], error) {
|
||||
inst := Instrument{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
@@ -641,12 +653,13 @@ func (p int64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]ag
|
||||
Kind: kind,
|
||||
Scope: p.scope,
|
||||
}
|
||||
return p.int64Resolver.Aggregators(inst)
|
||||
return p.int64Resolver.Aggregators(inst, allowedKeys)
|
||||
}
|
||||
|
||||
func (p int64InstProvider) histogramAggs(
|
||||
name string,
|
||||
cfg metric.Int64HistogramConfig,
|
||||
allowedKeys []attribute.Key,
|
||||
) ([]aggregate.Measure[int64], error) {
|
||||
boundaries := cfg.ExplicitBucketBoundaries()
|
||||
aggError := AggregationExplicitBucketHistogram{Boundaries: boundaries}.err()
|
||||
@@ -661,32 +674,40 @@ func (p int64InstProvider) histogramAggs(
|
||||
Kind: InstrumentKindHistogram,
|
||||
Scope: p.scope,
|
||||
}
|
||||
measures, err := p.int64Resolver.HistogramAggregators(inst, boundaries)
|
||||
measures, err := p.int64Resolver.HistogramAggregators(inst, allowedKeys, boundaries)
|
||||
return measures, errors.Join(aggError, err)
|
||||
}
|
||||
|
||||
// lookup returns the resolved instrumentImpl.
|
||||
func (p int64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*int64Inst, error) {
|
||||
func (p int64InstProvider) lookup(
|
||||
kind InstrumentKind,
|
||||
name, desc, u string,
|
||||
allowedKeys []attribute.Key,
|
||||
) (*int64Inst, error) {
|
||||
return p.int64Insts.Lookup(instID{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
Unit: u,
|
||||
Kind: kind,
|
||||
}, func() (*int64Inst, error) {
|
||||
aggs, err := p.aggs(kind, name, desc, u)
|
||||
aggs, err := p.aggs(kind, name, desc, u, allowedKeys)
|
||||
return &int64Inst{measures: aggs}, err
|
||||
})
|
||||
}
|
||||
|
||||
// lookupHistogram returns the resolved instrumentImpl.
|
||||
func (p int64InstProvider) lookupHistogram(name string, cfg metric.Int64HistogramConfig) (*int64Inst, error) {
|
||||
func (p int64InstProvider) lookupHistogram(
|
||||
name string,
|
||||
cfg metric.Int64HistogramConfig,
|
||||
allowedKeys []attribute.Key,
|
||||
) (*int64Inst, error) {
|
||||
return p.int64Insts.Lookup(instID{
|
||||
Name: name,
|
||||
Description: cfg.Description(),
|
||||
Unit: cfg.Unit(),
|
||||
Kind: InstrumentKindHistogram,
|
||||
}, func() (*int64Inst, error) {
|
||||
aggs, err := p.histogramAggs(name, cfg)
|
||||
aggs, err := p.histogramAggs(name, cfg, allowedKeys)
|
||||
return &int64Inst{measures: aggs}, err
|
||||
})
|
||||
}
|
||||
@@ -694,7 +715,11 @@ func (p int64InstProvider) lookupHistogram(name string, cfg metric.Int64Histogra
|
||||
// float64InstProvider provides float64 OpenTelemetry instruments.
|
||||
type float64InstProvider struct{ *meter }
|
||||
|
||||
func (p float64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]aggregate.Measure[float64], error) {
|
||||
func (p float64InstProvider) aggs(
|
||||
kind InstrumentKind,
|
||||
name, desc, u string,
|
||||
allowedKeys []attribute.Key,
|
||||
) ([]aggregate.Measure[float64], error) {
|
||||
inst := Instrument{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
@@ -702,12 +727,13 @@ func (p float64InstProvider) aggs(kind InstrumentKind, name, desc, u string) ([]
|
||||
Kind: kind,
|
||||
Scope: p.scope,
|
||||
}
|
||||
return p.float64Resolver.Aggregators(inst)
|
||||
return p.float64Resolver.Aggregators(inst, allowedKeys)
|
||||
}
|
||||
|
||||
func (p float64InstProvider) histogramAggs(
|
||||
name string,
|
||||
cfg metric.Float64HistogramConfig,
|
||||
allowedKeys []attribute.Key,
|
||||
) ([]aggregate.Measure[float64], error) {
|
||||
boundaries := cfg.ExplicitBucketBoundaries()
|
||||
aggError := AggregationExplicitBucketHistogram{Boundaries: boundaries}.err()
|
||||
@@ -722,32 +748,40 @@ func (p float64InstProvider) histogramAggs(
|
||||
Kind: InstrumentKindHistogram,
|
||||
Scope: p.scope,
|
||||
}
|
||||
measures, err := p.float64Resolver.HistogramAggregators(inst, boundaries)
|
||||
measures, err := p.float64Resolver.HistogramAggregators(inst, allowedKeys, boundaries)
|
||||
return measures, errors.Join(aggError, err)
|
||||
}
|
||||
|
||||
// lookup returns the resolved instrumentImpl.
|
||||
func (p float64InstProvider) lookup(kind InstrumentKind, name, desc, u string) (*float64Inst, error) {
|
||||
func (p float64InstProvider) lookup(
|
||||
kind InstrumentKind,
|
||||
name, desc, u string,
|
||||
allowedKeys []attribute.Key,
|
||||
) (*float64Inst, error) {
|
||||
return p.float64Insts.Lookup(instID{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
Unit: u,
|
||||
Kind: kind,
|
||||
}, func() (*float64Inst, error) {
|
||||
aggs, err := p.aggs(kind, name, desc, u)
|
||||
aggs, err := p.aggs(kind, name, desc, u, allowedKeys)
|
||||
return &float64Inst{measures: aggs}, err
|
||||
})
|
||||
}
|
||||
|
||||
// lookupHistogram returns the resolved instrumentImpl.
|
||||
func (p float64InstProvider) lookupHistogram(name string, cfg metric.Float64HistogramConfig) (*float64Inst, error) {
|
||||
func (p float64InstProvider) lookupHistogram(
|
||||
name string,
|
||||
cfg metric.Float64HistogramConfig,
|
||||
allowedKeys []attribute.Key,
|
||||
) (*float64Inst, error) {
|
||||
return p.float64Insts.Lookup(instID{
|
||||
Name: name,
|
||||
Description: cfg.Description(),
|
||||
Unit: cfg.Unit(),
|
||||
Kind: InstrumentKindHistogram,
|
||||
}, func() (*float64Inst, error) {
|
||||
aggs, err := p.histogramAggs(name, cfg)
|
||||
aggs, err := p.histogramAggs(name, cfg, allowedKeys)
|
||||
return &float64Inst{measures: aggs}, err
|
||||
})
|
||||
}
|
||||
@@ -771,3 +805,18 @@ func (o float64Observer) Observe(val float64, opts ...metric.ObserveOption) {
|
||||
c := metric.NewObserveConfig(opts)
|
||||
o.observe(val, c.Attributes())
|
||||
}
|
||||
|
||||
func defaultAttributes[T any](opts []T) []attribute.Key {
|
||||
var keys []attribute.Key
|
||||
var found bool
|
||||
for _, o := range opts {
|
||||
if exp, ok := any(o).(interface{ AllowedKeys() []attribute.Key }); ok {
|
||||
found = true
|
||||
keys = append(keys, exp.AllowedKeys()...)
|
||||
}
|
||||
}
|
||||
if found && keys == nil {
|
||||
return []attribute.Key{}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
+46
-7
@@ -14,8 +14,9 @@ import (
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/metric/internal/observ"
|
||||
"go.opentelemetry.io/otel/sdk/metric/internal/x"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
// Default periodic reader timing.
|
||||
@@ -126,6 +127,9 @@ func NewPeriodicReader(exporter Exporter, options ...PeriodicReaderOption) *Peri
|
||||
},
|
||||
},
|
||||
}
|
||||
if val, ok := x.MetricExportBatchSize.Lookup(); ok {
|
||||
r.batcher = batcher{size: val}
|
||||
}
|
||||
r.externalProducers.Store(conf.producers)
|
||||
|
||||
go func() {
|
||||
@@ -164,6 +168,7 @@ type PeriodicReader struct {
|
||||
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
batcher batcher
|
||||
exporter Exporter
|
||||
flushCh chan chan error
|
||||
|
||||
@@ -235,16 +240,28 @@ func (r *PeriodicReader) cardinalityLimit(kind InstrumentKind) (int, bool) {
|
||||
// collectAndExport gather all metric data related to the periodicReader r from
|
||||
// the SDK and exports it with r's exporter.
|
||||
func (r *PeriodicReader) collectAndExport(ctx context.Context) error {
|
||||
originalCtx := ctx
|
||||
ctx, cancel := context.WithTimeoutCause(ctx, r.timeout, errors.New("reader collect and export timeout"))
|
||||
defer cancel()
|
||||
|
||||
// TODO (#3047): Use a sync.Pool or persistent pointer instead of allocating rm every Collect.
|
||||
rm := r.rmPool.Get().(*metricdata.ResourceMetrics)
|
||||
defer func() {
|
||||
*rm = metricdata.ResourceMetrics{} // erase fields to allow GC to collect them.
|
||||
r.rmPool.Put(rm)
|
||||
}()
|
||||
err := r.Collect(ctx, rm)
|
||||
if err == nil {
|
||||
err = r.export(ctx, rm)
|
||||
if r.batcher.size > 0 {
|
||||
batches := r.batcher.splitResourceMetrics(rm)
|
||||
for _, batch := range batches {
|
||||
// The export timeout is applied individually to each batch by using
|
||||
// the original context.
|
||||
err = errors.Join(err, r.exportWithTimeout(originalCtx, batch))
|
||||
}
|
||||
} else {
|
||||
err = r.exporter.Export(ctx, rm)
|
||||
}
|
||||
}
|
||||
r.rmPool.Put(rm)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -307,7 +324,10 @@ func (r *PeriodicReader) collect(ctx context.Context, p any, rm *metricdata.Reso
|
||||
}
|
||||
|
||||
// export exports metric data m using r's exporter.
|
||||
func (r *PeriodicReader) export(ctx context.Context, m *metricdata.ResourceMetrics) error {
|
||||
func (r *PeriodicReader) exportWithTimeout(ctx context.Context, m *metricdata.ResourceMetrics) error {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeoutCause(ctx, r.timeout, errors.New("reader export timeout"))
|
||||
defer cancel()
|
||||
return r.exporter.Export(ctx, m)
|
||||
}
|
||||
|
||||
@@ -349,7 +369,9 @@ func (r *PeriodicReader) Shutdown(ctx context.Context) error {
|
||||
err := ErrReaderShutdown
|
||||
r.shutdownOnce.Do(func() {
|
||||
// Prioritize the ctx timeout if it is set.
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
originalCtx := ctx
|
||||
_, userProvidedContext := ctx.Deadline()
|
||||
if !userProvidedContext {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeoutCause(ctx, r.timeout, errors.New("reader shutdown timeout"))
|
||||
defer cancel()
|
||||
@@ -369,7 +391,24 @@ func (r *PeriodicReader) Shutdown(ctx context.Context) error {
|
||||
m := r.rmPool.Get().(*metricdata.ResourceMetrics)
|
||||
err = r.collect(ctx, ph, m)
|
||||
if err == nil {
|
||||
err = r.export(ctx, m)
|
||||
if r.batcher.size > 0 {
|
||||
batches := r.batcher.splitResourceMetrics(m)
|
||||
for _, batch := range batches {
|
||||
if userProvidedContext {
|
||||
// Do not apply the export timeout if the user passed a timeout to
|
||||
// Shutdown().
|
||||
err = errors.Join(err, r.exporter.Export(ctx, batch))
|
||||
} else {
|
||||
// The export timeout is applied individually to each batch by using
|
||||
// the original context.
|
||||
err = errors.Join(err, r.exportWithTimeout(originalCtx, batch))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Do not apply the export timeout if the user passed a timeout to
|
||||
// Shutdown().
|
||||
err = r.exporter.Export(ctx, m)
|
||||
}
|
||||
}
|
||||
r.rmPool.Put(m)
|
||||
}
|
||||
|
||||
+21
-5
@@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric/embedded"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
@@ -236,7 +237,11 @@ func newInserter[N int64 | float64](p *pipeline, vc *cache[string, instID]) *ins
|
||||
//
|
||||
// If an instrument is determined to use a Drop aggregation, that instrument is
|
||||
// not inserted nor returned.
|
||||
func (i *inserter[N]) Instrument(inst Instrument, readerAggregation Aggregation) ([]aggregate.Measure[N], error) {
|
||||
func (i *inserter[N]) Instrument(
|
||||
inst Instrument,
|
||||
allowedKeys []attribute.Key,
|
||||
readerAggregation Aggregation,
|
||||
) ([]aggregate.Measure[N], error) {
|
||||
var (
|
||||
matched bool
|
||||
measures []aggregate.Measure[N]
|
||||
@@ -279,6 +284,12 @@ func (i *inserter[N]) Instrument(inst Instrument, readerAggregation Aggregation)
|
||||
Description: inst.Description,
|
||||
Unit: inst.Unit,
|
||||
}
|
||||
// allowedKeys == nil indicates that the WithDefaultAttributes option was not passed,
|
||||
// and all keys are allowed. An empty (non-nil) slice indicates that the option was passed
|
||||
// with an empty set of keys, and no keys are allowed.
|
||||
if allowedKeys != nil {
|
||||
stream.AttributeFilter = attribute.NewAllowKeysFilter(allowedKeys...)
|
||||
}
|
||||
in, _, e := i.cachedAggregator(inst.Scope, inst.Kind, stream, readerAggregation)
|
||||
if e != nil {
|
||||
if err == nil {
|
||||
@@ -388,6 +399,7 @@ func (i *inserter[N]) cachedAggregator(
|
||||
b := aggregate.Builder[N]{
|
||||
Temporality: i.pipeline.reader.temporality(kind),
|
||||
ReservoirFunc: reservoirFunc[N](
|
||||
kind,
|
||||
stream.ExemplarReservoirProviderSelector(stream.Aggregation),
|
||||
i.pipeline.exemplarFilter,
|
||||
),
|
||||
@@ -661,12 +673,12 @@ func newResolver[N int64 | float64](p pipelines, vc *cache[string, instID]) reso
|
||||
|
||||
// Aggregators returns the Aggregators that must be updated by the instrument
|
||||
// defined by key.
|
||||
func (r resolver[N]) Aggregators(id Instrument) ([]aggregate.Measure[N], error) {
|
||||
func (r resolver[N]) Aggregators(id Instrument, allowedKeys []attribute.Key) ([]aggregate.Measure[N], error) {
|
||||
var measures []aggregate.Measure[N]
|
||||
|
||||
var err error
|
||||
for _, i := range r.inserters {
|
||||
in, e := i.Instrument(id, i.readerDefaultAggregation(id.Kind))
|
||||
in, e := i.Instrument(id, allowedKeys, i.readerDefaultAggregation(id.Kind))
|
||||
if e != nil {
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
@@ -678,7 +690,11 @@ func (r resolver[N]) Aggregators(id Instrument) ([]aggregate.Measure[N], error)
|
||||
// HistogramAggregators returns the histogram Aggregators that must be updated by the instrument
|
||||
// defined by key. If boundaries were provided on instrument instantiation, those take precedence
|
||||
// over boundaries provided by the reader.
|
||||
func (r resolver[N]) HistogramAggregators(id Instrument, boundaries []float64) ([]aggregate.Measure[N], error) {
|
||||
func (r resolver[N]) HistogramAggregators(
|
||||
id Instrument,
|
||||
allowedKeys []attribute.Key,
|
||||
boundaries []float64,
|
||||
) ([]aggregate.Measure[N], error) {
|
||||
var measures []aggregate.Measure[N]
|
||||
|
||||
var err error
|
||||
@@ -688,7 +704,7 @@ func (r resolver[N]) HistogramAggregators(id Instrument, boundaries []float64) (
|
||||
histAgg.Boundaries = boundaries
|
||||
agg = histAgg
|
||||
}
|
||||
in, e := i.Instrument(id, agg)
|
||||
in, e := i.Instrument(id, allowedKeys, agg)
|
||||
if e != nil {
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
|
||||
+4
-2
@@ -47,7 +47,8 @@ func NewMeterProvider(options ...Option) *MeterProvider {
|
||||
shutdown: sdown,
|
||||
}
|
||||
// Log after creation so all readers show correctly they are registered.
|
||||
global.Info("MeterProvider created",
|
||||
global.Info(
|
||||
"MeterProvider created",
|
||||
"Resource", conf.res,
|
||||
"Readers", conf.readers,
|
||||
"Views", len(conf.views),
|
||||
@@ -82,7 +83,8 @@ func (mp *MeterProvider) Meter(name string, options ...metric.MeterOption) metri
|
||||
Attributes: c.InstrumentationAttributes(),
|
||||
}
|
||||
|
||||
global.Info("Meter created",
|
||||
global.Info(
|
||||
"Meter created",
|
||||
"Name", s.Name,
|
||||
"Version", s.Version,
|
||||
"SchemaURL", s.SchemaURL,
|
||||
|
||||
+9
@@ -183,6 +183,15 @@ type AggregationSelector func(InstrumentKind) Aggregation
|
||||
// mapping: Counter ⇨ Sum, Observable Counter ⇨ Sum, UpDownCounter ⇨ Sum,
|
||||
// Observable UpDownCounter ⇨ Sum, Observable Gauge ⇨ LastValue,
|
||||
// Histogram ⇨ ExplicitBucketHistogram.
|
||||
//
|
||||
// The default ExplicitBucketHistogram boundaries are designed for
|
||||
// millisecond-scale latency values. Boundaries are interpreted relative to the
|
||||
// values recorded for the instrument and are not rescaled when an instrument is
|
||||
// created with a different unit (e.g. via
|
||||
// [go.opentelemetry.io/otel/metric.WithUnit]). Instrumentation authors should
|
||||
// supply appropriate boundaries per instrument via
|
||||
// [go.opentelemetry.io/otel/metric.WithExplicitBucketBoundaries]; end users
|
||||
// can also override boundaries for a specific instrument with a [View].
|
||||
func DefaultAggregationSelector(ik InstrumentKind) Aggregation {
|
||||
switch ik {
|
||||
case InstrumentKindCounter,
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package metric // import "go.opentelemetry.io/otel/sdk/metric"
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
)
|
||||
|
||||
// batcher splits metrics into batches.
|
||||
type batcher struct {
|
||||
size int
|
||||
}
|
||||
|
||||
// splitResourceMetrics splits a metricdata.ResourceMetrics into multiple ResourceMetrics, sequentially,
|
||||
// ensuring no ResourceMetrics has more than `size` data points. It does not mutate the `src` object.
|
||||
func (b batcher) splitResourceMetrics(src *metricdata.ResourceMetrics) []*metricdata.ResourceMetrics {
|
||||
if b.size <= 0 || len(src.ScopeMetrics) == 0 {
|
||||
return []*metricdata.ResourceMetrics{src}
|
||||
}
|
||||
var batches []*metricdata.ResourceMetrics
|
||||
var currentBatch *metricdata.ResourceMetrics
|
||||
currentPoints := 0
|
||||
|
||||
for i := 0; i < len(src.ScopeMetrics); i++ {
|
||||
sm := src.ScopeMetrics[i]
|
||||
|
||||
take := b.size - currentPoints
|
||||
smChunks := b.splitScopeMetrics(sm, take)
|
||||
|
||||
for _, chunk := range smChunks {
|
||||
if currentBatch == nil {
|
||||
currentBatch = &metricdata.ResourceMetrics{Resource: src.Resource}
|
||||
batches = append(batches, currentBatch)
|
||||
}
|
||||
currentBatch.ScopeMetrics = append(currentBatch.ScopeMetrics, chunk)
|
||||
currentPoints += scopeMetricsDPC(chunk)
|
||||
|
||||
if currentPoints == b.size {
|
||||
currentBatch = nil
|
||||
currentPoints = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return batches
|
||||
}
|
||||
|
||||
// splitScopeMetrics splits a metricdata.ScopeMetrics into chunks. The first chunk will have at most firstSize data points.
|
||||
func (b batcher) splitScopeMetrics(sm metricdata.ScopeMetrics, firstSize int) []metricdata.ScopeMetrics {
|
||||
smPoints := scopeMetricsDPC(sm)
|
||||
if smPoints <= firstSize {
|
||||
return []metricdata.ScopeMetrics{sm}
|
||||
}
|
||||
|
||||
var chunks []metricdata.ScopeMetrics
|
||||
var currentChunk *metricdata.ScopeMetrics
|
||||
currentPoints := 0
|
||||
targetSize := firstSize
|
||||
|
||||
for i := 0; i < len(sm.Metrics); i++ {
|
||||
m := sm.Metrics[i]
|
||||
|
||||
take := targetSize - currentPoints
|
||||
mChunks := b.splitMetric(m, take)
|
||||
|
||||
for _, mc := range mChunks {
|
||||
if currentChunk == nil {
|
||||
chunks = append(chunks, metricdata.ScopeMetrics{Scope: sm.Scope})
|
||||
currentChunk = &chunks[len(chunks)-1]
|
||||
}
|
||||
currentChunk.Metrics = append(currentChunk.Metrics, mc)
|
||||
currentPoints += metricDPC(mc)
|
||||
|
||||
if currentPoints == targetSize {
|
||||
currentChunk = nil
|
||||
currentPoints = 0
|
||||
targetSize = b.size
|
||||
}
|
||||
}
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
|
||||
// splitMetric splits a metricdata.Metrics into chunks. The first chunk will have at most firstSize data points.
|
||||
func (b batcher) splitMetric(m metricdata.Metrics, firstSize int) []metricdata.Metrics {
|
||||
mPoints := metricDPC(m)
|
||||
if mPoints <= firstSize {
|
||||
return []metricdata.Metrics{m}
|
||||
}
|
||||
|
||||
var chunks []metricdata.Metrics
|
||||
mRemaining := mPoints
|
||||
mOffset := 0
|
||||
take := firstSize
|
||||
|
||||
for mRemaining > 0 {
|
||||
if take > mRemaining {
|
||||
take = mRemaining
|
||||
}
|
||||
chunks = append(chunks, copyMetricData(m, mOffset, take))
|
||||
mRemaining -= take
|
||||
mOffset += take
|
||||
take = b.size
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
|
||||
// copyMetricData creates a copy of the metricdata.Metrics with the specified offset and number of datapoints to take.
|
||||
func copyMetricData(m metricdata.Metrics, offset, take int) metricdata.Metrics {
|
||||
dest := metricdata.Metrics{
|
||||
Name: m.Name,
|
||||
Description: m.Description,
|
||||
Unit: m.Unit,
|
||||
}
|
||||
switch a := m.Data.(type) {
|
||||
case metricdata.Gauge[int64]:
|
||||
dest.Data = metricdata.Gauge[int64]{DataPoints: a.DataPoints[offset : offset+take]}
|
||||
case metricdata.Gauge[float64]:
|
||||
dest.Data = metricdata.Gauge[float64]{DataPoints: a.DataPoints[offset : offset+take]}
|
||||
case metricdata.Sum[int64]:
|
||||
dest.Data = metricdata.Sum[int64]{
|
||||
DataPoints: a.DataPoints[offset : offset+take],
|
||||
Temporality: a.Temporality,
|
||||
IsMonotonic: a.IsMonotonic,
|
||||
}
|
||||
case metricdata.Sum[float64]:
|
||||
dest.Data = metricdata.Sum[float64]{
|
||||
DataPoints: a.DataPoints[offset : offset+take],
|
||||
Temporality: a.Temporality,
|
||||
IsMonotonic: a.IsMonotonic,
|
||||
}
|
||||
case metricdata.Histogram[int64]:
|
||||
dest.Data = metricdata.Histogram[int64]{
|
||||
DataPoints: a.DataPoints[offset : offset+take],
|
||||
Temporality: a.Temporality,
|
||||
}
|
||||
case metricdata.Histogram[float64]:
|
||||
dest.Data = metricdata.Histogram[float64]{
|
||||
DataPoints: a.DataPoints[offset : offset+take],
|
||||
Temporality: a.Temporality,
|
||||
}
|
||||
case metricdata.ExponentialHistogram[int64]:
|
||||
dest.Data = metricdata.ExponentialHistogram[int64]{
|
||||
DataPoints: a.DataPoints[offset : offset+take],
|
||||
Temporality: a.Temporality,
|
||||
}
|
||||
case metricdata.ExponentialHistogram[float64]:
|
||||
dest.Data = metricdata.ExponentialHistogram[float64]{
|
||||
DataPoints: a.DataPoints[offset : offset+take],
|
||||
Temporality: a.Temporality,
|
||||
}
|
||||
case metricdata.Summary:
|
||||
dest.Data = metricdata.Summary{DataPoints: a.DataPoints[offset : offset+take]}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
// scopeMetricsDPC calculates the total number of data points in the metricdata.ScopeMetrics.
|
||||
func scopeMetricsDPC(sm metricdata.ScopeMetrics) int {
|
||||
dataPointCount := 0
|
||||
ms := sm.Metrics
|
||||
for k := range ms {
|
||||
dataPointCount += metricDPC(ms[k])
|
||||
}
|
||||
return dataPointCount
|
||||
}
|
||||
|
||||
// metricDPC calculates the total number of data points in the metricdata.Metrics.
|
||||
func metricDPC(m metricdata.Metrics) int {
|
||||
switch a := m.Data.(type) {
|
||||
case metricdata.Gauge[int64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.Gauge[float64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.Sum[int64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.Sum[float64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.Histogram[int64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.Histogram[float64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.ExponentialHistogram[int64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.ExponentialHistogram[float64]:
|
||||
return len(a.DataPoints)
|
||||
case metricdata.Summary:
|
||||
return len(a.DataPoints)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+1
-1
@@ -5,5 +5,5 @@ package metric // import "go.opentelemetry.io/otel/sdk/metric"
|
||||
|
||||
// version is the current release version of the metric SDK in use.
|
||||
func version() string {
|
||||
return "1.43.0"
|
||||
return "1.44.0"
|
||||
}
|
||||
|
||||
+4
@@ -22,6 +22,10 @@ var (
|
||||
// should be collected for certain instruments. It returns true and the exact
|
||||
// Stream to use for matching Instruments. Otherwise, if the view does not
|
||||
// match, false is returned.
|
||||
//
|
||||
// Note that attributes filtered out by a View may still appear on Exemplars,
|
||||
// because Exemplars are recorded with the dropped measurement attributes
|
||||
// when View attribute filtering is applied.
|
||||
type View func(Instrument) (Stream, bool)
|
||||
|
||||
// NewView returns a View that applies the Stream mask for all instruments that
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -79,7 +79,7 @@ func (sd stringDetector) Detect(context.Context) (*Resource, error) {
|
||||
}
|
||||
a := sd.K.String(value)
|
||||
if !a.Valid() {
|
||||
return nil, fmt.Errorf("invalid attribute: %q -> %q", a.Key, a.Value.Emit())
|
||||
return nil, fmt.Errorf("invalid attribute: %q -> %q", a.Key, a.Value.String())
|
||||
}
|
||||
return NewWithAttributes(sd.schemaURL, sd.K.String(value)), nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
type containerIDProvider func() (string, error)
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
type hostIDProvider func() (string, error)
|
||||
|
||||
+5
-2
@@ -5,10 +5,13 @@
|
||||
|
||||
package resource // import "go.opentelemetry.io/otel/sdk/resource"
|
||||
|
||||
import "os/exec"
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func execCommand(name string, arg ...string) (string, error) {
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd := exec.CommandContext(context.Background(), name, arg...)
|
||||
b, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
type osDescriptionProvider func() (string, error)
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@ func uname() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s %s %s %s",
|
||||
return fmt.Sprintf(
|
||||
"%s %s %s %s %s",
|
||||
unix.ByteSliceToString(utsName.Sysname[:]),
|
||||
unix.ByteSliceToString(utsName.Nodename[:]),
|
||||
unix.ByteSliceToString(utsName.Release[:]),
|
||||
|
||||
+4
-2
@@ -16,7 +16,8 @@ import (
|
||||
// resembles the one displayed by the Version Reporter Applet (winver.exe).
|
||||
func platformOSDescription() (string, error) {
|
||||
k, err := registry.OpenKey(
|
||||
registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -37,7 +38,8 @@ func platformOSDescription() (string, error) {
|
||||
displayVersion += " "
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s(%s) [Version %s.%s.%s.%s]",
|
||||
return fmt.Sprintf(
|
||||
"%s %s(%s) [Version %s.%s.%s.%s]",
|
||||
productName,
|
||||
displayVersion,
|
||||
releaseID,
|
||||
|
||||
+3
-2
@@ -11,7 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -164,7 +164,8 @@ func (processRuntimeVersionDetector) Detect(context.Context) (*Resource, error)
|
||||
// Detect returns a *Resource that describes the runtime of this process.
|
||||
func (processRuntimeDescriptionDetector) Detect(context.Context) (*Resource, error) {
|
||||
runtimeDescription := fmt.Sprintf(
|
||||
"go version %s %s/%s", runtimeVersion(), runtimeOS(), runtimeArch())
|
||||
"go version %s %s/%s", runtimeVersion(), runtimeOS(), runtimeArch(),
|
||||
)
|
||||
|
||||
return NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
|
||||
+6
-4
@@ -163,19 +163,21 @@ func (bsp *batchSpanProcessor) Shutdown(ctx context.Context) error {
|
||||
bsp.stopOnce.Do(func() {
|
||||
bsp.stopped.Store(true)
|
||||
wait := make(chan struct{})
|
||||
// exportErr is written by the goroutine before closing wait.
|
||||
// It is only read in the <-wait case, so there is no race.
|
||||
var exportErr error
|
||||
go func() {
|
||||
close(bsp.stopCh)
|
||||
bsp.stopWait.Wait()
|
||||
if bsp.e != nil {
|
||||
if err := bsp.e.Shutdown(ctx); err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
exportErr = bsp.e.Shutdown(ctx)
|
||||
}
|
||||
close(wait)
|
||||
}()
|
||||
// Wait until the wait group is done or the context is cancelled
|
||||
// Wait until the channel is ready or the context is canceled.
|
||||
select {
|
||||
case <-wait:
|
||||
err = exportErr
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
}
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -13,8 +13,8 @@ import (
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/sdk/internal/x"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Generated
Vendored
+3
-2
@@ -13,8 +13,8 @@ import (
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/sdk/internal/x"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
var measureAttrsPool = sync.Pool{
|
||||
@@ -86,6 +86,7 @@ func (ssp *SSP) addOption(err error) []metric.AddOption {
|
||||
}
|
||||
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
clear(*attrs)
|
||||
*attrs = (*attrs)[:0] // reset the slice for reuse
|
||||
measureAttrsPool.Put(attrs)
|
||||
}()
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/sdk"
|
||||
"go.opentelemetry.io/otel/sdk/internal/x"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
|
||||
+8
-1
@@ -82,6 +82,10 @@ type TracerProvider struct {
|
||||
|
||||
var _ trace.TracerProvider = &TracerProvider{}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewTracerProvider returns a new and configured TracerProvider.
|
||||
//
|
||||
// By default the returned TracerProvider is configured with:
|
||||
@@ -99,6 +103,9 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
|
||||
o = applyTracerProviderEnvConfigs(o)
|
||||
|
||||
for _, opt := range opts {
|
||||
if _, ok := opt.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
o = opt.apply(o)
|
||||
}
|
||||
|
||||
@@ -310,7 +317,7 @@ func (p *TracerProvider) Shutdown(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (p *TracerProvider) getSpanProcessors() spanProcessorStates {
|
||||
return *(p.spanProcessors.Load())
|
||||
return *p.spanProcessors.Load()
|
||||
}
|
||||
|
||||
// TracerProviderOption configures a TracerProvider.
|
||||
|
||||
+3
-2
@@ -297,8 +297,9 @@ func (pb parentBased) ShouldSample(p SamplingParameters) SamplingResult {
|
||||
}
|
||||
|
||||
func (pb parentBased) Description() string {
|
||||
return fmt.Sprintf("ParentBased{root:%s,remoteParentSampled:%s,"+
|
||||
"remoteParentNotSampled:%s,localParentSampled:%s,localParentNotSampled:%s}",
|
||||
return fmt.Sprintf(
|
||||
"ParentBased{root:%s,remoteParentSampled:%s,"+
|
||||
"remoteParentNotSampled:%s,localParentSampled:%s,localParentNotSampled:%s}",
|
||||
pb.root.Description(),
|
||||
pb.config.remoteParentSampled.Description(),
|
||||
pb.config.remoteParentNotSampled.Description(),
|
||||
|
||||
+92
-5
@@ -20,7 +20,7 @@ import (
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/embedded"
|
||||
)
|
||||
@@ -346,10 +346,12 @@ func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
|
||||
}
|
||||
}
|
||||
|
||||
// truncateAttr returns a truncated version of attr. Only string and string
|
||||
// slice attribute values are truncated. String values are truncated to at
|
||||
// most a length of limit. Each string slice value is truncated in this fashion
|
||||
// (the slice length itself is unaffected).
|
||||
// truncateAttr returns a truncated version of attr. Only string, string
|
||||
// slice, byte slice, and slice attribute values are truncated. String values are truncated
|
||||
// to at most a length of limit. Each string slice value is truncated in this
|
||||
// fashion (the slice length itself is unaffected), and byte slice values are truncated to at most
|
||||
// limit bytes. For slice attribute values, the limit is applied to each
|
||||
// element recursively.
|
||||
//
|
||||
// No truncation is performed for a negative limit.
|
||||
func truncateAttr(limit int, attr attribute.KeyValue) attribute.KeyValue {
|
||||
@@ -366,10 +368,95 @@ func truncateAttr(limit int, attr attribute.KeyValue) attribute.KeyValue {
|
||||
v[i] = truncate(limit, v[i])
|
||||
}
|
||||
return attr.Key.StringSlice(v)
|
||||
case attribute.BYTESLICE:
|
||||
v := attr.Value.AsString()
|
||||
if len(v) > limit {
|
||||
return attr.Key.ByteSlice([]byte(v[:limit]))
|
||||
}
|
||||
return attr
|
||||
case attribute.SLICE:
|
||||
v := attr.Value.AsSlice()
|
||||
if !slices.ContainsFunc(v, func(e attribute.Value) bool { return needsTruncation(limit, e) }) {
|
||||
return attr
|
||||
}
|
||||
newV := make([]attribute.Value, len(v))
|
||||
for i, elem := range v {
|
||||
newV[i] = truncateValue(limit, elem)
|
||||
}
|
||||
return attr.Key.Slice(newV...)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// truncateValue returns a truncated version of v. Only string, string slice,
|
||||
// byte slice, and (recursively) slice values are modified.
|
||||
//
|
||||
// No truncation is performed for a negative limit.
|
||||
func truncateValue(limit int, v attribute.Value) attribute.Value {
|
||||
switch v.Type() {
|
||||
case attribute.STRING:
|
||||
return attribute.StringValue(truncate(limit, v.AsString()))
|
||||
case attribute.STRINGSLICE:
|
||||
ss := v.AsStringSlice()
|
||||
for i := range ss {
|
||||
ss[i] = truncate(limit, ss[i])
|
||||
}
|
||||
return attribute.StringSliceValue(ss)
|
||||
|
||||
case attribute.BYTESLICE:
|
||||
// len(v.AsString()) is identical to len(v.AsByteSlice()) but
|
||||
// avoids allocating the full slice before truncation.
|
||||
s := v.AsString()
|
||||
if limit >= 0 && len(s) > limit {
|
||||
return attribute.ByteSliceValue([]byte(s[:limit]))
|
||||
}
|
||||
case attribute.SLICE:
|
||||
sl := v.AsSlice()
|
||||
if !slices.ContainsFunc(sl, func(e attribute.Value) bool { return needsTruncation(limit, e) }) {
|
||||
return v
|
||||
}
|
||||
newSl := make([]attribute.Value, len(sl))
|
||||
for i, elem := range sl {
|
||||
newSl[i] = truncateValue(limit, elem)
|
||||
}
|
||||
return attribute.SliceValue(newSl...)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// stringNeedsTruncation reports whether s would be modified by truncate for the
|
||||
// given limit.
|
||||
func stringNeedsTruncation(limit int, s string) bool {
|
||||
if limit < 0 || len(s) <= limit {
|
||||
return false
|
||||
}
|
||||
return utf8.RuneCountInString(s) > limit || !utf8.ValidString(s)
|
||||
}
|
||||
|
||||
// needsTruncation reports whether v would be modified by truncateValue for the
|
||||
// given limit.
|
||||
func needsTruncation(limit int, v attribute.Value) bool {
|
||||
switch v.Type() {
|
||||
case attribute.STRING:
|
||||
return stringNeedsTruncation(limit, v.AsString())
|
||||
case attribute.BYTESLICE:
|
||||
// len(v.AsString()) is identical to len(v.AsByteSlice()) but
|
||||
// avoids memory allocation.
|
||||
if limit >= 0 && len(v.AsString()) > limit {
|
||||
return true
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
for _, s := range v.AsStringSlice() {
|
||||
if stringNeedsTruncation(limit, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case attribute.SLICE:
|
||||
return slices.ContainsFunc(v.AsSlice(), func(e attribute.Value) bool { return needsTruncation(limit, e) })
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// truncate returns a truncated version of s such that it contains less than
|
||||
// the limit number of characters. Truncation is applied by returning the limit
|
||||
// number of valid characters contained in s.
|
||||
|
||||
+4
-2
@@ -35,8 +35,10 @@ const (
|
||||
type SpanLimits struct {
|
||||
// AttributeValueLengthLimit is the maximum allowed attribute value length.
|
||||
//
|
||||
// This limit only applies to string and string slice attribute values.
|
||||
// Any string longer than this value will be truncated to this length.
|
||||
// This limit only applies to string, string slice, byte slice, and slice attribute
|
||||
// values. Any string and byte slice longer than this value will be truncated to this
|
||||
// length. For slice attribute values, the limit is applied to each string and byte slice
|
||||
// element recursively.
|
||||
//
|
||||
// Setting this to a negative value means no limit is applied.
|
||||
AttributeValueLengthLimit int
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@ package sdk // import "go.opentelemetry.io/otel/sdk"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry SDK in use.
|
||||
func Version() string {
|
||||
return "1.43.0"
|
||||
return "1.44.0"
|
||||
}
|
||||
|
||||
+8
-4
@@ -1447,9 +1447,11 @@ func AWSExtendedRequestID(val string) attribute.KeyValue {
|
||||
// AWSKinesisStreamName returns an attribute KeyValue conforming to the
|
||||
// "aws.kinesis.stream_name" semantic conventions. It represents the name of the
|
||||
// AWS Kinesis [stream] the request refers to. Corresponds to the `--stream-name`
|
||||
// parameter of the Kinesis [describe-stream] operation.
|
||||
//
|
||||
// parameter of the Kinesis [describe-stream] operation.
|
||||
//
|
||||
// [stream]: https://docs.aws.amazon.com/streams/latest/dev/introduction.html
|
||||
//
|
||||
// [describe-stream]: https://docs.aws.amazon.com/cli/latest/reference/kinesis/describe-stream.html
|
||||
func AWSKinesisStreamName(val string) attribute.KeyValue {
|
||||
return AWSKinesisStreamNameKey.String(val)
|
||||
@@ -1459,7 +1461,8 @@ func AWSKinesisStreamName(val string) attribute.KeyValue {
|
||||
// "aws.lambda.invoked_arn" semantic conventions. It represents the full invoked
|
||||
// ARN as provided on the `Context` passed to the function (
|
||||
// `Lambda-Runtime-Invoked-Function-Arn` header on the `/runtime/invocation/next`
|
||||
// applicable).
|
||||
//
|
||||
// applicable).
|
||||
func AWSLambdaInvokedARN(val string) attribute.KeyValue {
|
||||
return AWSLambdaInvokedARNKey.String(val)
|
||||
}
|
||||
@@ -2635,7 +2638,8 @@ func CloudRegion(val string) attribute.KeyValue {
|
||||
// "cloud.resource_id" semantic conventions. It represents the cloud
|
||||
// provider-specific native identifier of the monitored cloud resource (e.g. an
|
||||
// [ARN] on AWS, a [fully qualified resource ID] on Azure, a [full resource name]
|
||||
// on GCP).
|
||||
//
|
||||
// on GCP).
|
||||
//
|
||||
// [ARN]: https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
|
||||
// [fully qualified resource ID]: https://learn.microsoft.com/rest/api/resources/resources/get-by-id
|
||||
@@ -15190,4 +15194,4 @@ func ZOSSmfID(val string) attribute.KeyValue {
|
||||
// to which the z/OS system belongs too.
|
||||
func ZOSSysplexName(val string) attribute.KeyValue {
|
||||
return ZOSSysplexNameKey.String(val)
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# Semconv v1.40.0
|
||||
|
||||
[](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.40.0)
|
||||
Generated
Vendored
+3
-13
@@ -1,7 +1,7 @@
|
||||
<!-- Generated. DO NOT MODIFY. -->
|
||||
# Migration from v1.39.0 to v1.40.0
|
||||
# Migration from v1.40.0 to v1.41.0
|
||||
|
||||
The `go.opentelemetry.io/otel/semconv/v1.40.0` package should be a drop-in replacement for `go.opentelemetry.io/otel/semconv/v1.39.0` with the following exceptions.
|
||||
The `go.opentelemetry.io/otel/semconv/v1.41.0` package should be a drop-in replacement for `go.opentelemetry.io/otel/semconv/v1.40.0` with the following exceptions.
|
||||
|
||||
## Removed
|
||||
|
||||
@@ -11,17 +11,7 @@ Refer to the [OpenTelemetry Semantic Conventions documentation] for deprecation
|
||||
If the type is not listed in the documentation as deprecated, it has been removed in this version due to lack of applicability or use.
|
||||
If you use any of these non-deprecated declarations in your Go application, please [open an issue] describing your use-case.
|
||||
|
||||
- `ErrorMessage`
|
||||
- `ErrorMessageKey`
|
||||
- `RPCMessageCompressedSize`
|
||||
- `RPCMessageCompressedSizeKey`
|
||||
- `RPCMessageID`
|
||||
- `RPCMessageIDKey`
|
||||
- `RPCMessageTypeKey`
|
||||
- `RPCMessageTypeReceived`
|
||||
- `RPCMessageTypeSent`
|
||||
- `RPCMessageUncompressedSize`
|
||||
- `RPCMessageUncompressedSizeKey`
|
||||
- `DeploymentEnvironmentName`
|
||||
|
||||
[OpenTelemetry Semantic Conventions documentation]: https://github.com/open-telemetry/semantic-conventions
|
||||
[open an issue]: https://github.com/open-telemetry/opentelemetry-go/issues/new?template=Blank+issue
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Semconv v1.41.0
|
||||
|
||||
[](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.41.0)
|
||||
Generated
Vendored
+494
-70
@@ -3,7 +3,7 @@
|
||||
|
||||
// Code generated from semantic convention specification. DO NOT EDIT.
|
||||
|
||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
|
||||
import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
@@ -950,7 +950,7 @@ const (
|
||||
// of the [AWS Lambda EvenSource Mapping]. An event source is mapped to a lambda
|
||||
// function. It's contents are read by Lambda and used to trigger a function.
|
||||
// This isn't available in the lambda execution context or the lambda runtime
|
||||
// environtment. This is going to be populated by the AWS SDK for each language
|
||||
// environment. This is going to be populated by the AWS SDK for each language
|
||||
// when that UUID is present. Some of these operations are
|
||||
// Create/Delete/Get/List/Update EventSourceMapping.
|
||||
//
|
||||
@@ -1186,7 +1186,7 @@ const (
|
||||
|
||||
// AWSSecretsmanagerSecretARNKey is the attribute Key conforming to the
|
||||
// "aws.secretsmanager.secret.arn" semantic conventions. It represents the ARN
|
||||
// of the Secret stored in the Secrets Mangger.
|
||||
// of the Secret stored in the Secrets Manager.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
@@ -1515,7 +1515,7 @@ func AWSLambdaInvokedARN(val string) attribute.KeyValue {
|
||||
// of the [AWS Lambda EvenSource Mapping]. An event source is mapped to a lambda
|
||||
// function. It's contents are read by Lambda and used to trigger a function.
|
||||
// This isn't available in the lambda execution context or the lambda runtime
|
||||
// environtment. This is going to be populated by the AWS SDK for each language
|
||||
// environment. This is going to be populated by the AWS SDK for each language
|
||||
// when that UUID is present. Some of these operations are
|
||||
// Create/Delete/Get/List/Update EventSourceMapping.
|
||||
//
|
||||
@@ -1609,7 +1609,7 @@ func AWSS3UploadID(val string) attribute.KeyValue {
|
||||
|
||||
// AWSSecretsmanagerSecretARN returns an attribute KeyValue conforming to the
|
||||
// "aws.secretsmanager.secret.arn" semantic conventions. It represents the ARN of
|
||||
// the Secret stored in the Secrets Mangger.
|
||||
// the Secret stored in the Secrets Manager.
|
||||
func AWSSecretsmanagerSecretARN(val string) attribute.KeyValue {
|
||||
return AWSSecretsmanagerSecretARNKey.String(val)
|
||||
}
|
||||
@@ -2196,6 +2196,11 @@ const (
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "12097"
|
||||
// Note: For a given pipeline run and task, the `cicd.pipeline.task.run.id` MUST
|
||||
// be unique within that run. For the same task across different runs of the
|
||||
// same pipeline, the `cicd.pipeline.task.run.id` MAY remain the same, enabling
|
||||
// correlation of `cicd.pipeline.task.run.result` values across multiple
|
||||
// pipeline runs.
|
||||
CICDPipelineTaskRunIDKey = attribute.Key("cicd.pipeline.task.run.id")
|
||||
|
||||
// CICDPipelineTaskRunResultKey is the attribute Key conforming to the
|
||||
@@ -3431,7 +3436,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "a3bf90e006b2"
|
||||
//
|
||||
@@ -3467,7 +3472,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "gcr.io/opentelemetry/operator"
|
||||
ContainerImageNameKey = attribute.Key("container.image.name")
|
||||
@@ -3478,7 +3483,7 @@ const (
|
||||
//
|
||||
// Type: string[]
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples:
|
||||
// "example@sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb",
|
||||
@@ -3497,7 +3502,7 @@ const (
|
||||
//
|
||||
// Type: string[]
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "v1.27.1", "3.5.7-0"
|
||||
//
|
||||
@@ -3518,7 +3523,7 @@ const (
|
||||
// ContainerRuntimeDescriptionKey is the attribute Key conforming to the
|
||||
// "container.runtime.description" semantic conventions. It represents a
|
||||
// description about the runtime which could include, for example details about
|
||||
// the CRI/API version being used or other customisations.
|
||||
// the CRI/API version being used or other customizations.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
@@ -3649,7 +3654,7 @@ func ContainerName(val string) attribute.KeyValue {
|
||||
// ContainerRuntimeDescription returns an attribute KeyValue conforming to the
|
||||
// "container.runtime.description" semantic conventions. It represents a
|
||||
// description about the runtime which could include, for example details about
|
||||
// the CRI/API version being used or other customisations.
|
||||
// the CRI/API version being used or other customizations.
|
||||
func ContainerRuntimeDescription(val string) attribute.KeyValue {
|
||||
return ContainerRuntimeDescriptionKey.String(val)
|
||||
}
|
||||
@@ -4260,9 +4265,9 @@ const (
|
||||
// "deployment.environment.name" semantic conventions. It represents the name of
|
||||
// the [deployment environment] (aka deployment tier).
|
||||
//
|
||||
// Type: string
|
||||
// Type: Enum
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
// Stability: Stable
|
||||
//
|
||||
// Examples: "staging", "production"
|
||||
// Note: `deployment.environment.name` does not affect the uniqueness
|
||||
@@ -4312,15 +4317,6 @@ const (
|
||||
DeploymentStatusKey = attribute.Key("deployment.status")
|
||||
)
|
||||
|
||||
// DeploymentEnvironmentName returns an attribute KeyValue conforming to the
|
||||
// "deployment.environment.name" semantic conventions. It represents the name of
|
||||
// the [deployment environment] (aka deployment tier).
|
||||
//
|
||||
// [deployment environment]: https://wikipedia.org/wiki/Deployment_environment
|
||||
func DeploymentEnvironmentName(val string) attribute.KeyValue {
|
||||
return DeploymentEnvironmentNameKey.String(val)
|
||||
}
|
||||
|
||||
// DeploymentID returns an attribute KeyValue conforming to the "deployment.id"
|
||||
// semantic conventions. It represents the id of the deployment.
|
||||
func DeploymentID(val string) attribute.KeyValue {
|
||||
@@ -4334,6 +4330,22 @@ func DeploymentName(val string) attribute.KeyValue {
|
||||
return DeploymentNameKey.String(val)
|
||||
}
|
||||
|
||||
// Enum values for deployment.environment.name
|
||||
var (
|
||||
// Production environment
|
||||
// Stability: stable
|
||||
DeploymentEnvironmentNameProduction = DeploymentEnvironmentNameKey.String("production")
|
||||
// Staging environment
|
||||
// Stability: stable
|
||||
DeploymentEnvironmentNameStaging = DeploymentEnvironmentNameKey.String("staging")
|
||||
// Testing environment
|
||||
// Stability: stable
|
||||
DeploymentEnvironmentNameTest = DeploymentEnvironmentNameKey.String("test")
|
||||
// Development environment
|
||||
// Stability: stable
|
||||
DeploymentEnvironmentNameDevelopment = DeploymentEnvironmentNameKey.String("development")
|
||||
)
|
||||
|
||||
// Enum values for deployment.status
|
||||
var (
|
||||
// failed
|
||||
@@ -4645,6 +4657,12 @@ const (
|
||||
// When `error.type` is set to a type (e.g., an exception type), its
|
||||
// canonical class name identifying the type within the artifact SHOULD be used.
|
||||
//
|
||||
// If the recorded error type is a wrapper that is not meaningful for
|
||||
// failure classification, instrumentation MAY use the type of the inner
|
||||
// error instead. For example, in Go, errors created with `fmt.Errorf`
|
||||
// using `%w` MAY be unwrapped when the wrapper type does not help
|
||||
// classify the failure.
|
||||
//
|
||||
// Instrumentations SHOULD document the list of errors they report.
|
||||
//
|
||||
// The cardinality of `error.type` within one instrumentation library SHOULD be
|
||||
@@ -4718,6 +4736,11 @@ const (
|
||||
// Stability: Stable
|
||||
//
|
||||
// Examples: "java.net.ConnectException", "OSError"
|
||||
// Note: If the recorded exception type is a wrapper that is not meaningful for
|
||||
// failure classification, instrumentation MAY use the type of the inner
|
||||
// exception instead. For example, in Go, errors created with `fmt.Errorf`
|
||||
// using `%w` MAY be unwrapped when the wrapper type does not help
|
||||
// classify the failure.
|
||||
ExceptionTypeKey = attribute.Key("exception.type")
|
||||
)
|
||||
|
||||
@@ -6667,6 +6690,17 @@ const (
|
||||
// Examples: "forest", "lived"
|
||||
GenAIRequestStopSequencesKey = attribute.Key("gen_ai.request.stop_sequences")
|
||||
|
||||
// GenAIRequestStreamKey is the attribute Key conforming to the
|
||||
// "gen_ai.request.stream" semantic conventions. It represents the indicates
|
||||
// whether the GenAI request was made in streaming mode.
|
||||
//
|
||||
// Type: boolean
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples:
|
||||
GenAIRequestStreamKey = attribute.Key("gen_ai.request.stream")
|
||||
|
||||
// GenAIRequestTemperatureKey is the attribute Key conforming to the
|
||||
// "gen_ai.request.temperature" semantic conventions. It represents the
|
||||
// temperature setting for the GenAI request.
|
||||
@@ -6734,6 +6768,19 @@ const (
|
||||
// Examples: "gpt-4-0613"
|
||||
GenAIResponseModelKey = attribute.Key("gen_ai.response.model")
|
||||
|
||||
// GenAIResponseTimeToFirstChunkKey is the attribute Key conforming to the
|
||||
// "gen_ai.response.time_to_first_chunk" semantic conventions. It represents the
|
||||
// time to first chunk in a streaming response, measured from request issuance,
|
||||
// in seconds. The value is measured from when the client issues the generation
|
||||
// request to when the first chunk is received in the response stream.
|
||||
//
|
||||
// Type: double
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: 0.5, 1.2
|
||||
GenAIResponseTimeToFirstChunkKey = attribute.Key("gen_ai.response.time_to_first_chunk")
|
||||
|
||||
// GenAIRetrievalDocumentsKey is the attribute Key conforming to the
|
||||
// "gen_ai.retrieval.documents" semantic conventions. It represents the
|
||||
// documents retrieved.
|
||||
@@ -6875,7 +6922,7 @@ const (
|
||||
|
||||
// GenAIToolDefinitionsKey is the attribute Key conforming to the
|
||||
// "gen_ai.tool.definitions" semantic conventions. It represents the list of
|
||||
// source system tool definitions available to the GenAI agent or model.
|
||||
// tool definitions available to the GenAI agent or model.
|
||||
//
|
||||
// Type: any
|
||||
// RequirementLevel: Recommended
|
||||
@@ -6887,19 +6934,18 @@ const (
|
||||
// "description": "The city and state, e.g. San Francisco, CA"\n },\n "unit":
|
||||
// {\n "type": "string",\n "enum": [\n "celsius",\n "fahrenheit"\n ]\n }\n },\n
|
||||
// "required": [\n "location",\n "unit"\n ]\n }\n }\n]\n"
|
||||
// Note: The value of this attribute matches source system tool definition
|
||||
// format.
|
||||
// Note: Instrumentations MUST follow [Tool Definitions JSON Schema].
|
||||
//
|
||||
// It's expected to be an array of objects where each object represents a tool
|
||||
// definition. In case a serialized string is available
|
||||
// to the instrumentation, the instrumentation SHOULD do the best effort to
|
||||
// deserialize it to an array. When recorded on spans, it MAY be recorded as a
|
||||
// JSON string if structured format is not supported and SHOULD be recorded in
|
||||
// structured form otherwise.
|
||||
// When the attribute is recorded on events, it MUST be recorded in structured
|
||||
// form. When recorded on spans, it MAY be recorded as a JSON string if
|
||||
// structured
|
||||
// format is not supported and SHOULD be recorded in structured form otherwise.
|
||||
//
|
||||
// Since this attribute could be large, it's NOT RECOMMENDED to populate
|
||||
// it by default. Instrumentations MAY provide a way to enable
|
||||
// populating this attribute.
|
||||
// non-required properties by default. Instrumentations MAY provide a way
|
||||
// to enable populating optional properties.
|
||||
//
|
||||
// [Tool Definitions JSON Schema]: /docs/gen-ai/gen-ai-tool-definitions.json
|
||||
GenAIToolDefinitionsKey = attribute.Key("gen_ai.tool.definitions")
|
||||
|
||||
// GenAIToolDescriptionKey is the attribute Key conforming to the
|
||||
@@ -6997,6 +7043,32 @@ const (
|
||||
//
|
||||
// Examples: 180
|
||||
GenAIUsageOutputTokensKey = attribute.Key("gen_ai.usage.output_tokens")
|
||||
|
||||
// GenAIUsageReasoningOutputTokensKey is the attribute Key conforming to the
|
||||
// "gen_ai.usage.reasoning.output_tokens" semantic conventions. It represents
|
||||
// the number of output tokens used for reasoning (e.g. chain-of-thought,
|
||||
// extended thinking).
|
||||
//
|
||||
// Type: int
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: 50
|
||||
// Note: The value SHOULD be included in `gen_ai.usage.output_tokens`.
|
||||
GenAIUsageReasoningOutputTokensKey = attribute.Key("gen_ai.usage.reasoning.output_tokens")
|
||||
|
||||
// GenAIWorkflowNameKey is the attribute Key conforming to the
|
||||
// "gen_ai.workflow.name" semantic conventions. It represents the human-readable
|
||||
// name of the GenAI workflow provided by the application.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "multi_agent_rag", "customer_support_pipeline"
|
||||
// Note: This attribute can be populated in different frameworks eg: name of the
|
||||
// first chain in LangChain OR name of the crew in CrewAI.
|
||||
GenAIWorkflowNameKey = attribute.Key("gen_ai.workflow.name")
|
||||
)
|
||||
|
||||
// GenAIAgentDescription returns an attribute KeyValue conforming to the
|
||||
@@ -7139,6 +7211,13 @@ func GenAIRequestStopSequences(val ...string) attribute.KeyValue {
|
||||
return GenAIRequestStopSequencesKey.StringSlice(val)
|
||||
}
|
||||
|
||||
// GenAIRequestStream returns an attribute KeyValue conforming to the
|
||||
// "gen_ai.request.stream" semantic conventions. It represents the indicates
|
||||
// whether the GenAI request was made in streaming mode.
|
||||
func GenAIRequestStream(val bool) attribute.KeyValue {
|
||||
return GenAIRequestStreamKey.Bool(val)
|
||||
}
|
||||
|
||||
// GenAIRequestTemperature returns an attribute KeyValue conforming to the
|
||||
// "gen_ai.request.temperature" semantic conventions. It represents the
|
||||
// temperature setting for the GenAI request.
|
||||
@@ -7182,6 +7261,15 @@ func GenAIResponseModel(val string) attribute.KeyValue {
|
||||
return GenAIResponseModelKey.String(val)
|
||||
}
|
||||
|
||||
// GenAIResponseTimeToFirstChunk returns an attribute KeyValue conforming to the
|
||||
// "gen_ai.response.time_to_first_chunk" semantic conventions. It represents the
|
||||
// time to first chunk in a streaming response, measured from request issuance,
|
||||
// in seconds. The value is measured from when the client issues the generation
|
||||
// request to when the first chunk is received in the response stream.
|
||||
func GenAIResponseTimeToFirstChunk(val float64) attribute.KeyValue {
|
||||
return GenAIResponseTimeToFirstChunkKey.Float64(val)
|
||||
}
|
||||
|
||||
// GenAIRetrievalQueryText returns an attribute KeyValue conforming to the
|
||||
// "gen_ai.retrieval.query.text" semantic conventions. It represents the query
|
||||
// text used for retrieval.
|
||||
@@ -7245,6 +7333,21 @@ func GenAIUsageOutputTokens(val int) attribute.KeyValue {
|
||||
return GenAIUsageOutputTokensKey.Int(val)
|
||||
}
|
||||
|
||||
// GenAIUsageReasoningOutputTokens returns an attribute KeyValue conforming to
|
||||
// the "gen_ai.usage.reasoning.output_tokens" semantic conventions. It represents
|
||||
// the number of output tokens used for reasoning (e.g. chain-of-thought,
|
||||
// extended thinking).
|
||||
func GenAIUsageReasoningOutputTokens(val int) attribute.KeyValue {
|
||||
return GenAIUsageReasoningOutputTokensKey.Int(val)
|
||||
}
|
||||
|
||||
// GenAIWorkflowName returns an attribute KeyValue conforming to the
|
||||
// "gen_ai.workflow.name" semantic conventions. It represents the human-readable
|
||||
// name of the GenAI workflow provided by the application.
|
||||
func GenAIWorkflowName(val string) attribute.KeyValue {
|
||||
return GenAIWorkflowNameKey.String(val)
|
||||
}
|
||||
|
||||
// Enum values for gen_ai.operation.name
|
||||
var (
|
||||
// Chat completion operation such as [OpenAI Chat API]
|
||||
@@ -7281,6 +7384,9 @@ var (
|
||||
// Execute a tool
|
||||
// Stability: development
|
||||
GenAIOperationNameExecuteTool = GenAIOperationNameKey.String("execute_tool")
|
||||
// Invoke GenAI workflow
|
||||
// Stability: development
|
||||
GenAIOperationNameInvokeWorkflow = GenAIOperationNameKey.String("invoke_workflow")
|
||||
)
|
||||
|
||||
// Enum values for gen_ai.output.type
|
||||
@@ -7335,7 +7441,7 @@ var (
|
||||
// [Azure OpenAI]
|
||||
// Stability: development
|
||||
//
|
||||
// [Azure OpenAI]: https://azure.microsoft.com/products/ai-services/openai-service/
|
||||
// [Azure OpenAI]: https://learn.microsoft.com/en-us/azure/ai-services/openai/overview
|
||||
GenAIProviderNameAzureAIOpenAI = GenAIProviderNameKey.String("azure.ai.openai")
|
||||
// [IBM Watsonx AI]
|
||||
// Stability: development
|
||||
@@ -7551,6 +7657,44 @@ var (
|
||||
|
||||
// Namespace: go
|
||||
const (
|
||||
// GoCPUDetailedStateKey is the attribute Key conforming to the
|
||||
// "go.cpu.detailed_state" semantic conventions. It represents the detailed
|
||||
// state of the CPU.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "gc/pause", "gc/mark/assist"
|
||||
// Note: Value SHOULD match the specific CPU class reported by the Go runtime
|
||||
// under `/cpu/classes/...`. The list of possible values is subject to change
|
||||
// with the Go version used.
|
||||
GoCPUDetailedStateKey = attribute.Key("go.cpu.detailed_state")
|
||||
|
||||
// GoCPUStateKey is the attribute Key conforming to the "go.cpu.state" semantic
|
||||
// conventions. It represents the state of the CPU.
|
||||
//
|
||||
// Type: Enum
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "user", "gc"
|
||||
GoCPUStateKey = attribute.Key("go.cpu.state")
|
||||
|
||||
// GoMemoryDetailedTypeKey is the attribute Key conforming to the
|
||||
// "go.memory.detailed_type" semantic conventions. It represents the detailed
|
||||
// type of memory.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "heap/objects", "heap/free"
|
||||
// Note: Value SHOULD match the specific memory class reported by the Go runtime
|
||||
// under `/memory/classes/...`. The list of possible values is subject to change
|
||||
// with the Go version used.
|
||||
GoMemoryDetailedTypeKey = attribute.Key("go.memory.detailed_type")
|
||||
|
||||
// GoMemoryTypeKey is the attribute Key conforming to the "go.memory.type"
|
||||
// semantic conventions. It represents the type of memory.
|
||||
//
|
||||
@@ -7562,6 +7706,36 @@ const (
|
||||
GoMemoryTypeKey = attribute.Key("go.memory.type")
|
||||
)
|
||||
|
||||
// GoCPUDetailedState returns an attribute KeyValue conforming to the
|
||||
// "go.cpu.detailed_state" semantic conventions. It represents the detailed state
|
||||
// of the CPU.
|
||||
func GoCPUDetailedState(val string) attribute.KeyValue {
|
||||
return GoCPUDetailedStateKey.String(val)
|
||||
}
|
||||
|
||||
// GoMemoryDetailedType returns an attribute KeyValue conforming to the
|
||||
// "go.memory.detailed_type" semantic conventions. It represents the detailed
|
||||
// type of memory.
|
||||
func GoMemoryDetailedType(val string) attribute.KeyValue {
|
||||
return GoMemoryDetailedTypeKey.String(val)
|
||||
}
|
||||
|
||||
// Enum values for go.cpu.state
|
||||
var (
|
||||
// CPU time spent running user Go code.
|
||||
// Stability: development
|
||||
GoCPUStateUser = GoCPUStateKey.String("user")
|
||||
// CPU time spent performing garbage collection tasks.
|
||||
// Stability: development
|
||||
GoCPUStateGC = GoCPUStateKey.String("gc")
|
||||
// CPU time spent returning unused memory to the underlying platform.
|
||||
// Stability: development
|
||||
GoCPUStateScavenge = GoCPUStateKey.String("scavenge")
|
||||
// Available CPU time not spent executing any Go or Go runtime code.
|
||||
// Stability: development
|
||||
GoCPUStateIdle = GoCPUStateKey.String("idle")
|
||||
)
|
||||
|
||||
// Enum values for go.memory.type
|
||||
var (
|
||||
// Memory allocated from the heap that is reserved for stack space, whether or
|
||||
@@ -7584,7 +7758,8 @@ const (
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: query findBookById { bookById(id: ?) { name } }
|
||||
// Note: The value may be sanitized to exclude sensitive information.
|
||||
// Note: If instrumentation can reliably identify and redact sensitive
|
||||
// information it SHOULD do it.
|
||||
GraphQLDocumentKey = attribute.Key("graphql.document")
|
||||
|
||||
// GraphQLOperationNameKey is the attribute Key conforming to the
|
||||
@@ -8335,7 +8510,7 @@ var (
|
||||
const (
|
||||
// HwBatteryCapacityKey is the attribute Key conforming to the
|
||||
// "hw.battery.capacity" semantic conventions. It represents the design capacity
|
||||
// in Watts-hours or Amper-hours.
|
||||
// in Watts-hours or Ampere-hours.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
@@ -8637,7 +8812,7 @@ const (
|
||||
|
||||
// HwBatteryCapacity returns an attribute KeyValue conforming to the
|
||||
// "hw.battery.capacity" semantic conventions. It represents the design capacity
|
||||
// in Watts-hours or Amper-hours.
|
||||
// in Watts-hours or Ampere-hours.
|
||||
func HwBatteryCapacity(val string) attribute.KeyValue {
|
||||
return HwBatteryCapacityKey.String(val)
|
||||
}
|
||||
@@ -9026,7 +9201,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry-cluster"
|
||||
K8SClusterNameKey = attribute.Key("k8s.cluster.name")
|
||||
@@ -9037,7 +9212,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "218fc5a9-a5f1-4b54-aa05-46717d0ab26d"
|
||||
// Note: K8s doesn't have support for obtaining a cluster ID. If this is ever
|
||||
@@ -9073,7 +9248,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "redis"
|
||||
K8SContainerNameKey = attribute.Key("k8s.container.name")
|
||||
@@ -9085,7 +9260,7 @@ const (
|
||||
//
|
||||
// Type: int
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples:
|
||||
K8SContainerRestartCountKey = attribute.Key("k8s.container.restart_count")
|
||||
@@ -9136,7 +9311,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry"
|
||||
K8SCronJobNameKey = attribute.Key("k8s.cronjob.name")
|
||||
@@ -9146,7 +9321,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SCronJobUIDKey = attribute.Key("k8s.cronjob.uid")
|
||||
@@ -9157,7 +9332,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry"
|
||||
K8SDaemonSetNameKey = attribute.Key("k8s.daemonset.name")
|
||||
@@ -9167,7 +9342,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SDaemonSetUIDKey = attribute.Key("k8s.daemonset.uid")
|
||||
@@ -9178,7 +9353,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry"
|
||||
K8SDeploymentNameKey = attribute.Key("k8s.deployment.name")
|
||||
@@ -9189,7 +9364,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SDeploymentUIDKey = attribute.Key("k8s.deployment.uid")
|
||||
@@ -9279,7 +9454,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry"
|
||||
K8SJobNameKey = attribute.Key("k8s.job.name")
|
||||
@@ -9289,7 +9464,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SJobUIDKey = attribute.Key("k8s.job.uid")
|
||||
@@ -9300,7 +9475,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "default"
|
||||
K8SNamespaceNameKey = attribute.Key("k8s.namespace.name")
|
||||
@@ -9365,27 +9540,128 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "node-1"
|
||||
K8SNodeNameKey = attribute.Key("k8s.node.name")
|
||||
|
||||
// K8SNodeSystemContainerNameKey is the attribute Key conforming to the
|
||||
// "k8s.node.system_container.name" semantic conventions. It represents the name
|
||||
// of the system container running on the K8s Node.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "kubelet", "runtime", "pods", "misc"
|
||||
K8SNodeSystemContainerNameKey = attribute.Key("k8s.node.system_container.name")
|
||||
|
||||
// K8SNodeUIDKey is the attribute Key conforming to the "k8s.node.uid" semantic
|
||||
// conventions. It represents the UID of the Node.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "1eb3a0c6-0477-4080-a9cb-0cb7db65c6a2"
|
||||
K8SNodeUIDKey = attribute.Key("k8s.node.uid")
|
||||
|
||||
// K8SPersistentvolumeNameKey is the attribute Key conforming to the
|
||||
// "k8s.persistentvolume.name" semantic conventions. It represents the name of
|
||||
// the PersistentVolume.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "pv-data-01"
|
||||
K8SPersistentvolumeNameKey = attribute.Key("k8s.persistentvolume.name")
|
||||
|
||||
// K8SPersistentvolumeReclaimPolicyKey is the attribute Key conforming to the
|
||||
// "k8s.persistentvolume.reclaim_policy" semantic conventions. It represents the
|
||||
// reclaim policy of the PersistentVolume.
|
||||
//
|
||||
// Type: Enum
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "Delete", "Retain", "Recycle"
|
||||
// Note: This attribute aligns with the `persistentVolumeReclaimPolicy` field of
|
||||
// the
|
||||
// [K8s PersistentVolumeSpec].
|
||||
//
|
||||
// [K8s PersistentVolumeSpec]: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-v1/#PersistentVolumeSpec
|
||||
K8SPersistentvolumeReclaimPolicyKey = attribute.Key("k8s.persistentvolume.reclaim_policy")
|
||||
|
||||
// K8SPersistentvolumeStatusPhaseKey is the attribute Key conforming to the
|
||||
// "k8s.persistentvolume.status.phase" semantic conventions. It represents the
|
||||
// phase of the PersistentVolume.
|
||||
//
|
||||
// Type: Enum
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "Pending", "Available", "Bound", "Released", "Failed"
|
||||
// Note: This attribute aligns with the `phase` field of the
|
||||
// [K8s PersistentVolumeStatus].
|
||||
//
|
||||
// [K8s PersistentVolumeStatus]: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-v1/#PersistentVolumeStatus
|
||||
K8SPersistentvolumeStatusPhaseKey = attribute.Key("k8s.persistentvolume.status.phase")
|
||||
|
||||
// K8SPersistentvolumeUIDKey is the attribute Key conforming to the
|
||||
// "k8s.persistentvolume.uid" semantic conventions. It represents the UID of the
|
||||
// PersistentVolume.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SPersistentvolumeUIDKey = attribute.Key("k8s.persistentvolume.uid")
|
||||
|
||||
// K8SPersistentvolumeclaimNameKey is the attribute Key conforming to the
|
||||
// "k8s.persistentvolumeclaim.name" semantic conventions. It represents the name
|
||||
// of the PersistentVolumeClaim.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "pvc-data-01"
|
||||
K8SPersistentvolumeclaimNameKey = attribute.Key("k8s.persistentvolumeclaim.name")
|
||||
|
||||
// K8SPersistentvolumeclaimStatusPhaseKey is the attribute Key conforming to the
|
||||
// "k8s.persistentvolumeclaim.status.phase" semantic conventions. It represents
|
||||
// the phase of the PersistentVolumeClaim.
|
||||
//
|
||||
// Type: Enum
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "Pending", "Bound", "Lost"
|
||||
// Note: This attribute aligns with the `phase` field of the
|
||||
// [K8s PersistentVolumeClaimStatus].
|
||||
//
|
||||
// [K8s PersistentVolumeClaimStatus]: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-claim-v1/#PersistentVolumeClaimStatus
|
||||
K8SPersistentvolumeclaimStatusPhaseKey = attribute.Key("k8s.persistentvolumeclaim.status.phase")
|
||||
|
||||
// K8SPersistentvolumeclaimUIDKey is the attribute Key conforming to the
|
||||
// "k8s.persistentvolumeclaim.uid" semantic conventions. It represents the UID
|
||||
// of the PersistentVolumeClaim.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SPersistentvolumeclaimUIDKey = attribute.Key("k8s.persistentvolumeclaim.uid")
|
||||
|
||||
// K8SPodHostnameKey is the attribute Key conforming to the "k8s.pod.hostname"
|
||||
// semantic conventions. It represents the specifies the hostname of the Pod.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "collector-gateway"
|
||||
// Note: The K8s Pod spec has an optional hostname field, which can be used to
|
||||
@@ -9405,7 +9681,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "172.18.0.2"
|
||||
// Note: This attribute aligns with the `podIP` field of the
|
||||
@@ -9419,7 +9695,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry-pod-autoconf"
|
||||
K8SPodNameKey = attribute.Key("k8s.pod.name")
|
||||
@@ -9430,7 +9706,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "2025-12-04T08:41:03Z"
|
||||
// Note: Date and time at which the object was acknowledged by the Kubelet.
|
||||
@@ -9474,7 +9750,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SPodUIDKey = attribute.Key("k8s.pod.uid")
|
||||
@@ -9485,7 +9761,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry"
|
||||
K8SReplicaSetNameKey = attribute.Key("k8s.replicaset.name")
|
||||
@@ -9496,7 +9772,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SReplicaSetUIDKey = attribute.Key("k8s.replicaset.uid")
|
||||
@@ -9709,7 +9985,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "opentelemetry"
|
||||
K8SStatefulSetNameKey = attribute.Key("k8s.statefulset.name")
|
||||
@@ -9720,7 +9996,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Beta
|
||||
// Stability: Release_Candidate
|
||||
//
|
||||
// Examples: "275ecb36-5aa8-4c2a-9c47-d8bb681b9aff"
|
||||
K8SStatefulSetUIDKey = attribute.Key("k8s.statefulset.uid")
|
||||
@@ -10005,12 +10281,80 @@ func K8SNodeName(val string) attribute.KeyValue {
|
||||
return K8SNodeNameKey.String(val)
|
||||
}
|
||||
|
||||
// K8SNodeSystemContainerName returns an attribute KeyValue conforming to the
|
||||
// "k8s.node.system_container.name" semantic conventions. It represents the name
|
||||
// of the system container running on the K8s Node.
|
||||
func K8SNodeSystemContainerName(val string) attribute.KeyValue {
|
||||
return K8SNodeSystemContainerNameKey.String(val)
|
||||
}
|
||||
|
||||
// K8SNodeUID returns an attribute KeyValue conforming to the "k8s.node.uid"
|
||||
// semantic conventions. It represents the UID of the Node.
|
||||
func K8SNodeUID(val string) attribute.KeyValue {
|
||||
return K8SNodeUIDKey.String(val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeAnnotation returns an attribute KeyValue conforming to the
|
||||
// "k8s.persistentvolume.annotation" semantic conventions. It represents the
|
||||
// annotation placed on the PersistentVolume, the `<key>` being the annotation
|
||||
// name, the value being the annotation value, even if the value is empty.
|
||||
func K8SPersistentvolumeAnnotation(key string, val string) attribute.KeyValue {
|
||||
return attribute.String("k8s.persistentvolume.annotation."+key, val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeLabel returns an attribute KeyValue conforming to the
|
||||
// "k8s.persistentvolume.label" semantic conventions. It represents the label
|
||||
// placed on the PersistentVolume, the `<key>` being the label name, the value
|
||||
// being the label value, even if the value is empty.
|
||||
func K8SPersistentvolumeLabel(key string, val string) attribute.KeyValue {
|
||||
return attribute.String("k8s.persistentvolume.label."+key, val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeName returns an attribute KeyValue conforming to the
|
||||
// "k8s.persistentvolume.name" semantic conventions. It represents the name of
|
||||
// the PersistentVolume.
|
||||
func K8SPersistentvolumeName(val string) attribute.KeyValue {
|
||||
return K8SPersistentvolumeNameKey.String(val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeUID returns an attribute KeyValue conforming to the
|
||||
// "k8s.persistentvolume.uid" semantic conventions. It represents the UID of the
|
||||
// PersistentVolume.
|
||||
func K8SPersistentvolumeUID(val string) attribute.KeyValue {
|
||||
return K8SPersistentvolumeUIDKey.String(val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeclaimAnnotation returns an attribute KeyValue conforming to
|
||||
// the "k8s.persistentvolumeclaim.annotation" semantic conventions. It represents
|
||||
// the annotation placed on the PersistentVolumeClaim, the `<key>` being the
|
||||
// annotation name, the value being the annotation value, even if the value is
|
||||
// empty.
|
||||
func K8SPersistentvolumeclaimAnnotation(key string, val string) attribute.KeyValue {
|
||||
return attribute.String("k8s.persistentvolumeclaim.annotation."+key, val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeclaimLabel returns an attribute KeyValue conforming to the
|
||||
// "k8s.persistentvolumeclaim.label" semantic conventions. It represents the
|
||||
// label placed on the PersistentVolumeClaim, the `<key>` being the label name,
|
||||
// the value being the label value, even if the value is empty.
|
||||
func K8SPersistentvolumeclaimLabel(key string, val string) attribute.KeyValue {
|
||||
return attribute.String("k8s.persistentvolumeclaim.label."+key, val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeclaimName returns an attribute KeyValue conforming to the
|
||||
// "k8s.persistentvolumeclaim.name" semantic conventions. It represents the name
|
||||
// of the PersistentVolumeClaim.
|
||||
func K8SPersistentvolumeclaimName(val string) attribute.KeyValue {
|
||||
return K8SPersistentvolumeclaimNameKey.String(val)
|
||||
}
|
||||
|
||||
// K8SPersistentvolumeclaimUID returns an attribute KeyValue conforming to the
|
||||
// "k8s.persistentvolumeclaim.uid" semantic conventions. It represents the UID of
|
||||
// the PersistentVolumeClaim.
|
||||
func K8SPersistentvolumeclaimUID(val string) attribute.KeyValue {
|
||||
return K8SPersistentvolumeclaimUIDKey.String(val)
|
||||
}
|
||||
|
||||
// K8SPodAnnotation returns an attribute KeyValue conforming to the
|
||||
// "k8s.pod.annotation" semantic conventions. It represents the annotation placed
|
||||
// on the Pod, the `<key>` being the annotation name, the value being the
|
||||
@@ -10318,6 +10662,51 @@ var (
|
||||
K8SNodeConditionTypeNetworkUnavailable = K8SNodeConditionTypeKey.String("NetworkUnavailable")
|
||||
)
|
||||
|
||||
// Enum values for k8s.persistentvolume.reclaim_policy
|
||||
var (
|
||||
// The volume will be deleted when released from its claim.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeReclaimPolicyDelete = K8SPersistentvolumeReclaimPolicyKey.String("Delete")
|
||||
// The volume will be recycled (basic scrub) when released from its claim.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeReclaimPolicyRecycle = K8SPersistentvolumeReclaimPolicyKey.String("Recycle")
|
||||
// The volume will be retained when released from its claim.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeReclaimPolicyRetain = K8SPersistentvolumeReclaimPolicyKey.String("Retain")
|
||||
)
|
||||
|
||||
// Enum values for k8s.persistentvolume.status.phase
|
||||
var (
|
||||
// The volume is available and not yet bound to a claim.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeStatusPhaseAvailable = K8SPersistentvolumeStatusPhaseKey.String("Available")
|
||||
// The volume is bound to a claim.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeStatusPhaseBound = K8SPersistentvolumeStatusPhaseKey.String("Bound")
|
||||
// The volume has failed its automatic reclamation.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeStatusPhaseFailed = K8SPersistentvolumeStatusPhaseKey.String("Failed")
|
||||
// The volume is being provisioned.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeStatusPhasePending = K8SPersistentvolumeStatusPhaseKey.String("Pending")
|
||||
// The claim has been deleted but the volume is not yet available.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeStatusPhaseReleased = K8SPersistentvolumeStatusPhaseKey.String("Released")
|
||||
)
|
||||
|
||||
// Enum values for k8s.persistentvolumeclaim.status.phase
|
||||
var (
|
||||
// The claim is bound to a volume.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeclaimStatusPhaseBound = K8SPersistentvolumeclaimStatusPhaseKey.String("Bound")
|
||||
// The claim has lost its underlying volume (the volume does not exist anymore).
|
||||
// Stability: development
|
||||
K8SPersistentvolumeclaimStatusPhaseLost = K8SPersistentvolumeclaimStatusPhaseKey.String("Lost")
|
||||
// The claim has not yet been bound to a volume.
|
||||
// Stability: development
|
||||
K8SPersistentvolumeclaimStatusPhasePending = K8SPersistentvolumeclaimStatusPhaseKey.String("Pending")
|
||||
)
|
||||
|
||||
// Enum values for k8s.pod.status.phase
|
||||
var (
|
||||
// The pod has been accepted by the system, but one or more of the containers
|
||||
@@ -12669,7 +13058,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
// Stability: Stable
|
||||
//
|
||||
// Examples: "browser.mouse.click", "device.app.lifecycle"
|
||||
// Note: This attribute SHOULD be used by non-OTLP exporters when destination
|
||||
@@ -13240,14 +13629,27 @@ const (
|
||||
|
||||
// ProcessExecutableBuildIDHtlhashKey is the attribute Key conforming to the
|
||||
// "process.executable.build_id.htlhash" semantic conventions. It represents the
|
||||
// profiling specific build ID for executables. See the OTel specification for
|
||||
// Profiles for more information.
|
||||
// deterministic build ID for executables.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "600DCAFE4A110000F2BF38C493F5FB92"
|
||||
// Note: GNU and Go build IDs may be stripped or unavailable in some
|
||||
// environments
|
||||
// (e.g., Alpine Linux, Docker images). This attribute provides a deterministic
|
||||
// build ID computed by hashing the first and last 4096 bytes of the file
|
||||
// along with its length:
|
||||
//
|
||||
// ```
|
||||
// Input ← Concat(File[:4096], File[-4096:], BigEndianUInt64(Len(File)))
|
||||
// Digest ← SHA256(Input)
|
||||
// BuildID ← Digest[:16]
|
||||
// ```
|
||||
//
|
||||
// The result is the first 16 bytes (128 bits) of the SHA256 digest,
|
||||
// represented as a hex string.
|
||||
ProcessExecutableBuildIDHtlhashKey = attribute.Key("process.executable.build_id.htlhash")
|
||||
|
||||
// ProcessExecutableNameKey is the attribute Key conforming to the
|
||||
@@ -13603,8 +14005,7 @@ func ProcessExecutableBuildIDGo(val string) attribute.KeyValue {
|
||||
|
||||
// ProcessExecutableBuildIDHtlhash returns an attribute KeyValue conforming to
|
||||
// the "process.executable.build_id.htlhash" semantic conventions. It represents
|
||||
// the profiling specific build ID for executables. See the OTel specification
|
||||
// for Profiles for more information.
|
||||
// the deterministic build ID for executables.
|
||||
func ProcessExecutableBuildIDHtlhash(val string) attribute.KeyValue {
|
||||
return ProcessExecutableBuildIDHtlhashKey.String(val)
|
||||
}
|
||||
@@ -14317,9 +14718,11 @@ const (
|
||||
// Examples: "shoppingcart"
|
||||
// Note: MUST be the same for all instances of horizontally scaled services. If
|
||||
// the value was not specified, SDKs MUST fallback to `unknown_service:`
|
||||
// concatenated with [`process.executable.name`], e.g. `unknown_service:bash`.
|
||||
// If `process.executable.name` is not available, the value MUST be set to
|
||||
// concatenated with the process executable name, e.g. `unknown_service:bash`.
|
||||
// If the process executable name is not available, the value MUST be set to
|
||||
// `unknown_service`.
|
||||
// The process executable name is the name of the process executable, the same
|
||||
// value as described by the [`process.executable.name`] resource attribute.
|
||||
//
|
||||
// [`process.executable.name`]: process.md
|
||||
ServiceNameKey = attribute.Key("service.name")
|
||||
@@ -14643,6 +15046,17 @@ const (
|
||||
// Examples: "ext4"
|
||||
SystemFilesystemTypeKey = attribute.Key("system.filesystem.type")
|
||||
|
||||
// SystemMemoryLinuxHugepagesStateKey is the attribute Key conforming to the
|
||||
// "system.memory.linux.hugepages.state" semantic conventions. It represents the
|
||||
// Linux HugePages memory state.
|
||||
//
|
||||
// Type: Enum
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
//
|
||||
// Examples: "free", "used"
|
||||
SystemMemoryLinuxHugepagesStateKey = attribute.Key("system.memory.linux.hugepages.state")
|
||||
|
||||
// SystemMemoryLinuxSlabStateKey is the attribute Key conforming to the
|
||||
// "system.memory.linux.slab.state" semantic conventions. It represents the
|
||||
// Linux Slab memory state.
|
||||
@@ -14753,6 +15167,16 @@ var (
|
||||
SystemFilesystemTypeExt4 = SystemFilesystemTypeKey.String("ext4")
|
||||
)
|
||||
|
||||
// Enum values for system.memory.linux.hugepages.state
|
||||
var (
|
||||
// free
|
||||
// Stability: development
|
||||
SystemMemoryLinuxHugepagesStateFree = SystemMemoryLinuxHugepagesStateKey.String("free")
|
||||
// used
|
||||
// Stability: development
|
||||
SystemMemoryLinuxHugepagesStateUsed = SystemMemoryLinuxHugepagesStateKey.String("used")
|
||||
)
|
||||
|
||||
// Enum values for system.memory.linux.slab.state
|
||||
var (
|
||||
// reclaimable
|
||||
@@ -14817,7 +15241,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
// Stability: Stable
|
||||
//
|
||||
// Examples: "parts-unlimited-java"
|
||||
// Note: Official auto instrumentation agents and distributions SHOULD set the
|
||||
@@ -14832,7 +15256,7 @@ const (
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Recommended
|
||||
// Stability: Development
|
||||
// Stability: Stable
|
||||
//
|
||||
// Examples: "1.2.3"
|
||||
TelemetryDistroVersionKey = attribute.Key("telemetry.distro.version")
|
||||
Generated
Vendored
+4
-2
@@ -1,9 +1,11 @@
|
||||
// Code generated from semantic convention specification. DO NOT EDIT.
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package semconv implements OpenTelemetry semantic conventions.
|
||||
//
|
||||
// OpenTelemetry semantic conventions are agreed standardized naming
|
||||
// patterns for OpenTelemetry things. This package represents the v1.40.0
|
||||
// patterns for OpenTelemetry things. This package represents the v1.41.0
|
||||
// version of the OpenTelemetry semantic conventions.
|
||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user