diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3a2c28ed2..0880f2b7bdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The next release will require at least [Go 1.23]. - Added metrics support, and emit all stable metrics from the [Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md) in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#6648) - Add support for configuring `Insecure` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6658) - Support for the `OTEL_HTTP_CLIENT_COMPATIBILITY_MODE=http/dup` environment variable in `instrumentation/net/http/httptrace/otelhttptrace` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6720) +- Add support for configuring propagators in `go.opentelemetry.io/contrib/config`. (#6727) - Support for the `OTEL_HTTP_CLIENT_COMPATIBILITY_MODE=http/dup` environment variable in `instrumentation/github.com/emicklei/go-restful/otelrestful` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6710) - Added metrics support, and emit all stable metrics from the [Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-metrics.md) in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6747) - Support [Go 1.24]. (#6765) diff --git a/config/go.mod b/config/go.mod index 32e6bd4c21e..dc8c33d3e92 100644 --- a/config/go.mod +++ b/config/go.mod @@ -5,6 +5,11 @@ go 1.22.0 require ( github.com/prometheus/client_golang v1.20.5 github.com/stretchr/testify v1.10.0 + go.opentelemetry.io/contrib/propagators/autoprop v0.59.0 + go.opentelemetry.io/contrib/propagators/aws v1.34.0 + go.opentelemetry.io/contrib/propagators/b3 v1.34.0 + go.opentelemetry.io/contrib/propagators/jaeger v1.34.0 + go.opentelemetry.io/contrib/propagators/ot v1.34.0 go.opentelemetry.io/otel v1.34.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.10.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.10.0 @@ -44,6 +49,7 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect @@ -51,3 +57,13 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b // indirect google.golang.org/protobuf v1.36.5 // indirect ) + +replace go.opentelemetry.io/contrib/propagators/autoprop => ../propagators/autoprop + +replace go.opentelemetry.io/contrib/propagators/jaeger => ../propagators/jaeger + +replace go.opentelemetry.io/contrib/propagators/b3 => ../propagators/b3 + +replace go.opentelemetry.io/contrib/propagators/aws => ../propagators/aws + +replace go.opentelemetry.io/contrib/propagators/ot => ../propagators/ot diff --git a/config/go.sum b/config/go.sum index fee4aa65bde..0c9d7467e4f 100644 --- a/config/go.sum +++ b/config/go.sum @@ -85,6 +85,8 @@ go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 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= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= diff --git a/config/v0.3.0/config.go b/config/v0.3.0/config.go index 1dd458d5a4a..e9e49d58002 100644 --- a/config/v0.3.0/config.go +++ b/config/v0.3.0/config.go @@ -17,6 +17,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" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" ) @@ -46,6 +47,7 @@ type SDK struct { meterProvider metric.MeterProvider tracerProvider trace.TracerProvider loggerProvider log.LoggerProvider + propagator propagation.TextMapPropagator shutdown shutdownFunc } @@ -64,6 +66,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) @@ -73,6 +80,7 @@ var noopSDK = SDK{ loggerProvider: nooplog.LoggerProvider{}, meterProvider: noopmetric.MeterProvider{}, tracerProvider: nooptrace.TracerProvider{}, + propagator: propagation.NewCompositeTextMapPropagator(), shutdown: func(ctx context.Context) error { return nil }, } @@ -86,6 +94,11 @@ func NewSDK(opts ...ConfigurationOption) (SDK, error) { return noopSDK, nil } + prop, err := propagator(o) + if err != nil { + return noopSDK, err + } + r := newResource(o.opentelemetryConfig.Resource) mp, mpShutdown, err := meterProvider(o, r) @@ -107,6 +120,7 @@ func NewSDK(opts ...ConfigurationOption) (SDK, error) { meterProvider: mp, tracerProvider: tp, loggerProvider: lp, + propagator: prop, shutdown: func(ctx context.Context) error { return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) }, diff --git a/config/v0.3.0/config_test.go b/config/v0.3.0/config_test.go index a25cfc9ff88..ccbd71bd271 100644 --- a/config/v0.3.0/config_test.go +++ b/config/v0.3.0/config_test.go @@ -15,8 +15,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/contrib/propagators/b3" 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" @@ -30,6 +32,7 @@ func TestNewSDK(t *testing.T) { wantTracerProvider any wantMeterProvider any wantLoggerProvider any + wantPropagators any wantErr error wantShutdownErr error }{ @@ -38,6 +41,7 @@ func TestNewSDK(t *testing.T) { wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagators: propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}), }, { name: "with-configuration", @@ -47,11 +51,15 @@ func TestNewSDK(t *testing.T) { TracerProvider: &TracerProvider{}, MeterProvider: &MeterProvider{}, LoggerProvider: &LoggerProvider{}, + Propagator: &Propagator{ + Composite: []*string{ptr("b3")}, + }, }), }, wantTracerProvider: &sdktrace.TracerProvider{}, wantMeterProvider: &sdkmetric.MeterProvider{}, wantLoggerProvider: &sdklog.LoggerProvider{}, + wantPropagators: b3.New(), }, { name: "with-sdk-disabled", @@ -67,15 +75,19 @@ func TestNewSDK(t *testing.T) { wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), + wantPropagators: propagation.NewCompositeTextMapPropagator(), }, } for _, tt := range tests { - sdk, err := NewSDK(tt.cfg...) - require.Equal(t, tt.wantErr, err) - assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) - assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) - assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) - require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(context.Background())) + t.Run(tt.name, func(t *testing.T) { + sdk, err := NewSDK(tt.cfg...) + require.Equal(t, tt.wantErr, err) + assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) + assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) + assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) + assert.Equal(t, tt.wantPropagators, sdk.Propagator()) + require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(context.Background())) + }) } } diff --git a/config/v0.3.0/propagation.go b/config/v0.3.0/propagation.go new file mode 100644 index 00000000000..700981f51cd --- /dev/null +++ b/config/v0.3.0/propagation.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" + +import ( + "errors" + + "go.opentelemetry.io/contrib/propagators/autoprop" + "go.opentelemetry.io/otel/propagation" +) + +var ( + errInvalidPropagatorEmpty = errors.New("invalid propagator name: empty") + errInvalidPropagatorNil = errors.New("invalid propagator name: nil") +) + +func propagator(cfg configOptions) (propagation.TextMapPropagator, error) { + if cfg.opentelemetryConfig.Propagator == nil { + return autoprop.NewTextMapPropagator(), nil + } + + n := len(cfg.opentelemetryConfig.Propagator.Composite) + if n == 0 { + return autoprop.NewTextMapPropagator(), nil + } + + names := make([]string, 0, n) + for _, name := range cfg.opentelemetryConfig.Propagator.Composite { + if name == nil { + return nil, errInvalidPropagatorNil + } + if *name == "" { + return nil, errInvalidPropagatorEmpty + } + + names = append(names, *name) + } + + return autoprop.TextMapPropagator(names...) +} diff --git a/config/v0.3.0/propagation_test.go b/config/v0.3.0/propagation_test.go new file mode 100644 index 00000000000..0ff9bf61ca9 --- /dev/null +++ b/config/v0.3.0/propagation_test.go @@ -0,0 +1,195 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/contrib/propagators/aws/xray" + "go.opentelemetry.io/contrib/propagators/b3" + "go.opentelemetry.io/contrib/propagators/jaeger" + "go.opentelemetry.io/contrib/propagators/ot" + "go.opentelemetry.io/otel/propagation" +) + +func TestPropagator(t *testing.T) { + tests := []struct { + name string + cfg configOptions + want propagation.TextMapPropagator + wantErr bool + errMsg string + }{ + { + name: "nil propagator config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: nil, + }, + }, + want: propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}), + wantErr: false, + }, + { + name: "valid tracecontext", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("tracecontext")}, + }, + }, + }, + want: propagation.TraceContext{}, + wantErr: false, + }, + { + name: "valid baggage", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("baggage")}, + }, + }, + }, + want: propagation.Baggage{}, + wantErr: false, + }, + { + name: "valid b3", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("b3")}, + }, + }, + }, + want: b3.New(), + wantErr: false, + }, + { + name: "valid b3multi", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("b3multi")}, + }, + }, + }, + want: b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)), + wantErr: false, + }, + { + name: "valid jaeger", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("jaeger")}, + }, + }, + }, + want: jaeger.Jaeger{}, + wantErr: false, + }, + { + name: "valid xray", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("xray")}, + }, + }, + }, + want: xray.Propagator{}, + wantErr: false, + }, + { + name: "valid ottrace", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("ottrace")}, + }, + }, + }, + want: ot.OT{}, + wantErr: false, + }, + { + name: "multiple propagators", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("tracecontext"), ptr("baggage"), ptr("b3")}, + }, + }, + }, + want: propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}, b3.New()), + wantErr: false, + }, + { + name: "empty composite", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{}, + }, + }, + }, + want: propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}), + wantErr: false, + }, + { + name: "empty propagator name", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr(""), ptr("tracecontext")}, + }, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "nil propagator name", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{nil, ptr("tracecontext")}, + }, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "unsupported propagator", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + Propagator: &Propagator{ + Composite: []*string{ptr("unknown")}, + }, + }, + }, + want: propagation.NewCompositeTextMapPropagator(), + wantErr: true, + errMsg: "unknown propagator", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := propagator(tt.cfg) + if tt.wantErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.errMsg) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +}