diff --git a/CHANGELOG.md b/CHANGELOG.md index 43568022bde..af31f0489c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - `WithMetricAttributesFn` option in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to define dynamic attributes on auto-instrumented metrics. (#8191) +- Add support for configuring propagators in `go.opentelemetry.io/contrib/otelconf`. (#8281) ### Fixed diff --git a/otelconf/config.go b/otelconf/config.go index 0c29ca9cb04..503fae1e430 100644 --- a/otelconf/config.go +++ b/otelconf/config.go @@ -13,6 +13,7 @@ import ( nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/propagation" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" @@ -31,6 +32,7 @@ type SDK struct { meterProvider metric.MeterProvider tracerProvider trace.TracerProvider loggerProvider log.LoggerProvider + propagator propagation.TextMapPropagator shutdown shutdownFunc } @@ -49,6 +51,11 @@ func (s *SDK) LoggerProvider() log.LoggerProvider { return s.loggerProvider } +// Propagator returns a configured propagation.TextMapPropagator. +func (s *SDK) Propagator() propagation.TextMapPropagator { + return s.propagator +} + // Shutdown calls shutdown on all configured providers. func (s *SDK) Shutdown(ctx context.Context) error { return s.shutdown(ctx) @@ -58,6 +65,7 @@ var noopSDK = SDK{ loggerProvider: nooplog.LoggerProvider{}, meterProvider: noopmetric.MeterProvider{}, tracerProvider: nooptrace.TracerProvider{}, + propagator: propagation.NewCompositeTextMapPropagator(), shutdown: func(context.Context) error { return nil }, } @@ -105,6 +113,11 @@ func NewSDK(opts ...ConfigurationOption) (SDK, error) { return noopSDK, err } + p, err := newPropagator(o.opentelemetryConfig.Propagator) + if err != nil { + return noopSDK, err + } + mp, mpShutdown, err := meterProvider(o, r) if err != nil { return noopSDK, err @@ -124,6 +137,7 @@ func NewSDK(opts ...ConfigurationOption) (SDK, error) { meterProvider: mp, tracerProvider: tp, loggerProvider: lp, + propagator: p, shutdown: func(ctx context.Context) error { return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) }, diff --git a/otelconf/config_test.go b/otelconf/config_test.go index fc2b1c424fe..2a00855d2fa 100644 --- a/otelconf/config_test.go +++ b/otelconf/config_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" lognoop "go.opentelemetry.io/otel/log/noop" metricnoop "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/propagation" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" @@ -356,6 +357,7 @@ func TestNewSDK(t *testing.T) { wantTracerProvider any wantMeterProvider any wantLoggerProvider any + wantPropagator any wantErr error wantShutdownErr error }{ @@ -364,6 +366,7 @@ func TestNewSDK(t *testing.T) { wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagator: propagation.NewCompositeTextMapPropagator(), }, { name: "with-configuration", @@ -373,11 +376,13 @@ func TestNewSDK(t *testing.T) { TracerProvider: &TracerProviderJson{}, MeterProvider: &MeterProviderJson{}, LoggerProvider: &LoggerProviderJson{}, + Propagator: &PropagatorJson{}, }), }, wantTracerProvider: &sdktrace.TracerProvider{}, wantMeterProvider: &sdkmetric.MeterProvider{}, wantLoggerProvider: &sdklog.LoggerProvider{}, + wantPropagator: propagation.NewCompositeTextMapPropagator(), }, { name: "with-sdk-disabled", @@ -393,6 +398,7 @@ func TestNewSDK(t *testing.T) { wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagator: propagation.NewCompositeTextMapPropagator(), }, { name: "invalid resource", @@ -409,6 +415,24 @@ func TestNewSDK(t *testing.T) { wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagator: propagation.NewCompositeTextMapPropagator(), + }, + { + name: "invalid propagator", + cfg: []ConfigurationOption{ + WithContext(t.Context()), + WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ + TracerProvider: &TracerProviderJson{}, + MeterProvider: &MeterProviderJson{}, + LoggerProvider: &LoggerProviderJson{}, + Propagator: &LoggerProviderJson{}, + }), + }, + wantErr: newErrInvalid("propagator"), + wantTracerProvider: tracenoop.NewTracerProvider(), + wantMeterProvider: metricnoop.NewMeterProvider(), + wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagator: propagation.NewCompositeTextMapPropagator(), }, { name: "invalid logger provider", @@ -425,6 +449,7 @@ func TestNewSDK(t *testing.T) { wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagator: propagation.NewCompositeTextMapPropagator(), }, { name: "invalid tracer provider", @@ -438,6 +463,7 @@ func TestNewSDK(t *testing.T) { wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagator: propagation.NewCompositeTextMapPropagator(), }, } for _, tt := range tests { @@ -446,6 +472,7 @@ func TestNewSDK(t *testing.T) { assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) + assert.IsType(t, tt.wantPropagator, sdk.Propagator()) require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(t.Context())) } } diff --git a/otelconf/example_test.go b/otelconf/example_test.go index 0d9b67e67f6..5a9ac2f014c 100644 --- a/otelconf/example_test.go +++ b/otelconf/example_test.go @@ -44,4 +44,6 @@ func Example() { otel.SetTracerProvider(s.TracerProvider()) otel.SetMeterProvider(s.MeterProvider()) global.SetLoggerProvider(s.LoggerProvider()) + // Set the global propagator. + otel.SetTextMapPropagator(s.Propagator()) } diff --git a/otelconf/go.mod b/otelconf/go.mod index 02d58774d6e..a5340e7ac05 100644 --- a/otelconf/go.mod +++ b/otelconf/go.mod @@ -6,6 +6,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/prometheus/otlptranslator v1.0.0 github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/contrib/propagators/autoprop v0.64.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 @@ -26,6 +27,7 @@ require ( go.opentelemetry.io/otel/trace v1.39.0 go.opentelemetry.io/proto/otlp v1.9.0 go.yaml.in/yaml/v3 v3.0.4 + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 google.golang.org/grpc v1.78.0 ) @@ -44,7 +46,12 @@ require ( github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.19.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/propagators/aws v1.39.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.39.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect diff --git a/otelconf/go.sum b/otelconf/go.sum index 11cef41ec38..d95fb1967fb 100644 --- a/otelconf/go.sum +++ b/otelconf/go.sum @@ -47,6 +47,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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/propagators/autoprop v0.64.0 h1:VVrb1ErDD0Tlh/0K0rUqjky1e8AekjspTFN9sU2ekaA= +go.opentelemetry.io/contrib/propagators/autoprop v0.64.0/go.mod h1:QCsOQk+9Ep8Mkp4/aPtSzUT0dc8SaPYzBAE6o1jYuSE= +go.opentelemetry.io/contrib/propagators/aws v1.39.0 h1:IvNR8pAVGpkK1CHMjU/YE6B6TlnAPGFvogkMWRWU6wo= +go.opentelemetry.io/contrib/propagators/aws v1.39.0/go.mod h1:TUsFCERuGM4IGhJG9w+9l0nzmHUKHuaDYYNF6mtNgjY= +go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk= +go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY= +go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo= +go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8= +go.opentelemetry.io/contrib/propagators/ot v1.39.0 h1:vKTve1W/WKPVp1fzJamhCDDECt+5upJJ65bPyWoddGg= +go.opentelemetry.io/contrib/propagators/ot v1.39.0/go.mod h1:FH5VB2N19duNzh1Q8ks6CsZFyu3LFhNLiA9lPxyEkvU= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM= @@ -89,10 +99,14 @@ go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjce go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= diff --git a/otelconf/propagator.go b/otelconf/propagator.go new file mode 100644 index 00000000000..5fd58805b26 --- /dev/null +++ b/otelconf/propagator.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelconf // import "go.opentelemetry.io/contrib/otelconf" + +import ( + "strings" + + "go.opentelemetry.io/otel/propagation" + "golang.org/x/exp/maps" + + "go.opentelemetry.io/contrib/propagators/autoprop" +) + +func newPropagator(prop OpenTelemetryConfigurationPropagator) (propagation.TextMapPropagator, error) { + if prop == nil { + return propagation.NewCompositeTextMapPropagator(), nil + } + + p, ok := prop.(*PropagatorJson) + if !ok { + return nil, newErrInvalid("propagator") + } + + n := len(p.Composite) + if n == 0 && p.CompositeList == nil { + return propagation.NewCompositeTextMapPropagator(), nil + } + + names := map[string]struct{}{} + for _, propagator := range p.Composite { + if propagator.B3 != nil { + names["b3"] = struct{}{} + } + if propagator.B3Multi != nil { + names["b3multi"] = struct{}{} + } + if propagator.Baggage != nil { + names["baggage"] = struct{}{} + } + if propagator.Jaeger != nil { + names["jaeger"] = struct{}{} + } + if propagator.Ottrace != nil { + names["ottrace"] = struct{}{} + } + if propagator.Tracecontext != nil { + names["tracecontext"] = struct{}{} + } + } + + if p.CompositeList != nil { + for _, v := range strings.Split(*p.CompositeList, ",") { + names[v] = struct{}{} + } + } + + if len(names) == 0 { + return autoprop.NewTextMapPropagator(), nil + } + + return autoprop.TextMapPropagator(maps.Keys(names)...) +} diff --git a/otelconf/propagator_test.go b/otelconf/propagator_test.go new file mode 100644 index 00000000000..48fc1144ad3 --- /dev/null +++ b/otelconf/propagator_test.go @@ -0,0 +1,178 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelconf + +import ( + "slices" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPropagator(t *testing.T) { + tests := []struct { + name string + cfg OpenTelemetryConfigurationPropagator + want []string + wantErr bool + errMsg string + }{ + { + name: "nil propagator config", + cfg: nil, + want: []string{}, + wantErr: false, + }, + { + name: "valid tracecontext", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + { + Tracecontext: TraceContextPropagator{}, + }, + }, + }, + want: []string{"traceparent", "tracestate"}, + wantErr: false, + }, + { + name: "valid baggage", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + { + Baggage: BaggagePropagator{}, + }, + }, + }, + want: []string{"baggage"}, + wantErr: false, + }, + { + name: "valid b3", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + { + B3: B3Propagator{}, + }, + }, + }, + want: []string{"x-b3-traceid", "x-b3-spanid", "x-b3-sampled", "x-b3-flags"}, + wantErr: false, + }, + { + name: "valid b3multi", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + { + B3Multi: B3MultiPropagator{}, + }, + }, + }, + want: []string{"x-b3-traceid", "x-b3-spanid", "x-b3-sampled", "x-b3-flags"}, + wantErr: false, + }, + { + name: "valid jaeger", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + { + Jaeger: JaegerPropagator{}, + }, + }, + }, + want: []string{"uber-trace-id"}, + wantErr: false, + }, + { + name: "valid ottrace", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + { + Ottrace: OpenTracingPropagator{}, + }, + }, + }, + want: []string{"ot-tracer-traceid", "ot-tracer-spanid", "ot-tracer-sampled"}, + wantErr: false, + }, + { + name: "multiple propagators", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + { + Tracecontext: TraceContextPropagator{}, + }, + { + Baggage: BaggagePropagator{}, + }, + { + B3: B3Propagator{}, + }, + }, + }, + want: []string{"tracestate", "baggage", "x-b3-traceid", "x-b3-spanid", "x-b3-sampled", "x-b3-flags", "traceparent"}, + wantErr: false, + }, + { + name: "empty composite", + cfg: &PropagatorJson{ + Composite: []TextMapPropagator{ + {}, + }, + }, + want: []string{"tracestate", "baggage", "traceparent"}, + wantErr: false, + }, + { + name: "multiple propagators via composite_list", + cfg: &PropagatorJson{ + CompositeList: ptr("tracecontext,baggage,b3"), + }, + want: []string{"tracestate", "baggage", "x-b3-traceid", "x-b3-spanid", "x-b3-sampled", "x-b3-flags", "traceparent"}, + wantErr: false, + }, + { + name: "valid xray", + cfg: &PropagatorJson{ + CompositeList: ptr("xray"), + }, + want: []string{"X-Amzn-Trace-Id"}, + wantErr: false, + }, + { + name: "empty propagator name", + cfg: &PropagatorJson{ + CompositeList: ptr(""), + }, + want: []string{}, + wantErr: true, + errMsg: "unknown propagator", + }, + { + name: "unsupported propagator", + cfg: &PropagatorJson{ + CompositeList: ptr("random-garbage,baggage,b3"), + }, + want: []string{}, + wantErr: true, + errMsg: "unknown propagator", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newPropagator(tt.cfg) + if tt.wantErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.errMsg) + return + } + assert.NoError(t, err) + slices.Sort(tt.want) + gotFields := got.Fields() + slices.Sort(gotFields) + assert.Equal(t, tt.want, gotFields) + }) + } +}