From 5012c8beb80b952ba30c0823a2907c2dbaf5127b Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Mon, 7 Jul 2025 09:36:50 +0800 Subject: [PATCH 1/2] otelconf/v0.3.0: add support for provider options Support users passing {Logger,Meter,Tracer}ProviderOptions into NewSDK. These options will be combined with configuration file-based options; the latter will take precedence. --- CHANGELOG.md | 1 + otelconf/v0.3.0/config.go | 41 ++++++++++++++++++++++-- otelconf/v0.3.0/log.go | 5 ++- otelconf/v0.3.0/log_test.go | 55 ++++++++++++++++++++++++++++++++ otelconf/v0.3.0/metric.go | 4 +-- otelconf/v0.3.0/metric_test.go | 54 +++++++++++++++++++++++++++++++ otelconf/v0.3.0/trace.go | 5 ++- otelconf/v0.3.0/trace_test.go | 58 +++++++++++++++++++++++++++++++++- 8 files changed, 210 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa2aad6743..fabee20fe9d 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 - Add the unit `ns` to deprecated runtime metrics `process.runtime.go.gc.pause_total_ns` and `process.runtime.go.gc.pause_ns` in `go.opentelemetry.io/contrib/instrumentation/runtime`. (#7490) +- Added support for passing additional provider options to otelconf.NewSDK (#7552) diff --git a/otelconf/v0.3.0/config.go b/otelconf/v0.3.0/config.go index 275c1448743..fbecc4b3481 100644 --- a/otelconf/v0.3.0/config.go +++ b/otelconf/v0.3.0/config.go @@ -19,6 +19,9 @@ import ( nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" + sdklog "go.opentelemetry.io/otel/sdk/log" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" ) @@ -32,8 +35,11 @@ const ( ) type configOptions struct { - ctx context.Context - opentelemetryConfig OpenTelemetryConfiguration + ctx context.Context + opentelemetryConfig OpenTelemetryConfiguration + loggerProviderOptions []sdklog.LoggerProviderOption + meterProviderOptions []sdkmetric.Option + tracerProviderOptions []sdktrace.TracerProviderOption } type shutdownFunc func(context.Context) error @@ -80,7 +86,9 @@ var noopSDK = SDK{ // NewSDK creates SDK providers based on the configuration model. func NewSDK(opts ...ConfigurationOption) (SDK, error) { - o := configOptions{} + o := configOptions{ + ctx: context.Background(), + } for _, opt := range opts { o = opt.apply(o) } @@ -143,6 +151,33 @@ func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) Configuratio }) } +// WithLoggerProviderOptions appends LoggerProviderOptions used for constructing +// the LoggerProvider. OpenTelemetryConfiguration takes precedence over these options. +func WithLoggerProviderOptions(opts ...sdklog.LoggerProviderOption) ConfigurationOption { + return configurationOptionFunc(func(c configOptions) configOptions { + c.loggerProviderOptions = append(c.loggerProviderOptions, opts...) + return c + }) +} + +// WithMeterProviderOptions appends metric.Options used for constructing the +// MeterProvider. OpenTelemetryConfiguration takes precedence over these options. +func WithMeterProviderOptions(opts ...sdkmetric.Option) ConfigurationOption { + return configurationOptionFunc(func(c configOptions) configOptions { + c.meterProviderOptions = append(c.meterProviderOptions, opts...) + return c + }) +} + +// WithTracerProviderOptions appends TracerProviderOptions used for constructing +// the TracerProvider. OpenTelemetryConfiguration takes precedence over these options. +func WithTracerProviderOptions(opts ...sdktrace.TracerProviderOption) ConfigurationOption { + return configurationOptionFunc(func(c configOptions) configOptions { + c.tracerProviderOptions = append(c.tracerProviderOptions, opts...) + return c + }) +} + // ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration. func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { var cfg OpenTelemetryConfiguration diff --git a/otelconf/v0.3.0/log.go b/otelconf/v0.3.0/log.go index 185d424942f..615ed64fde4 100644 --- a/otelconf/v0.3.0/log.go +++ b/otelconf/v0.3.0/log.go @@ -25,9 +25,8 @@ func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvid if cfg.opentelemetryConfig.LoggerProvider == nil { return noop.NewLoggerProvider(), noopShutdown, nil } - opts := []sdklog.LoggerProviderOption{ - sdklog.WithResource(res), - } + opts := append(cfg.loggerProviderOptions, sdklog.WithResource(res)) + var errs []error for _, processor := range cfg.opentelemetryConfig.LoggerProvider.Processors { sp, err := logProcessor(cfg.ctx, processor) diff --git a/otelconf/v0.3.0/log_test.go b/otelconf/v0.3.0/log_test.go index 21f46c4a413..e3ce06da50a 100644 --- a/otelconf/v0.3.0/log_test.go +++ b/otelconf/v0.3.0/log_test.go @@ -4,11 +4,14 @@ package otelconf import ( + "bytes" "context" "crypto/tls" "crypto/x509" "errors" "net" + "net/http" + "net/http/httptest" "os" "path/filepath" "reflect" @@ -22,6 +25,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" @@ -706,6 +710,57 @@ func TestLogProcessor(t *testing.T) { } } +func TestLoggerProviderOptions(t *testing.T) { + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + calls++ + })) + defer srv.Close() + + cfg := OpenTelemetryConfiguration{ + LoggerProvider: &LoggerProvider{ + Processors: []LogRecordProcessor{{ + Simple: &SimpleLogRecordProcessor{ + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr(srv.URL), + Insecure: ptr(true), + }, + }, + }, + }}, + }, + } + + var buf bytes.Buffer + stdoutlogExporter, err := stdoutlog.New(stdoutlog.WithWriter(&buf)) + require.NoError(t, err) + + res := resource.NewSchemaless(attribute.String("foo", "bar")) + sdk, err := NewSDK( + WithOpenTelemetryConfiguration(cfg), + WithLoggerProviderOptions(sdklog.WithProcessor(sdklog.NewSimpleProcessor(stdoutlogExporter))), + WithLoggerProviderOptions(sdklog.WithResource(res)), + ) + require.NoError(t, err) + defer func() { + assert.NoError(t, sdk.Shutdown(context.Background())) + }() + + // The exporter, which we passed in as an extra option to NewSDK, + // should be wired up to the provider in addition to the + // configuration-based OTLP exporter. + logger := sdk.LoggerProvider().Logger("test") + logger.Emit(context.Background(), log.Record{}) + assert.NotZero(t, buf) + assert.Equal(t, 1, calls) + // Options provided by WithMeterProviderOptions may be overridden + // by configuration, e.g. the resource is always defined via + // configuration. + assert.NotContains(t, buf.String(), "foo") +} + func Test_otlpGRPCLogExporter(t *testing.T) { if runtime.GOOS == "windows" { // TODO (#7446): Fix the flakiness on Windows. diff --git a/otelconf/v0.3.0/metric.go b/otelconf/v0.3.0/metric.go index 9a617966b55..dc0cc4df880 100644 --- a/otelconf/v0.3.0/metric.go +++ b/otelconf/v0.3.0/metric.go @@ -42,9 +42,7 @@ func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvi if cfg.opentelemetryConfig.MeterProvider == nil { return noop.NewMeterProvider(), noopShutdown, nil } - opts := []sdkmetric.Option{ - sdkmetric.WithResource(res), - } + opts := append(cfg.meterProviderOptions, sdkmetric.WithResource(res)) var errs []error for _, reader := range cfg.opentelemetryConfig.MeterProvider.Readers { diff --git a/otelconf/v0.3.0/metric_test.go b/otelconf/v0.3.0/metric_test.go index e81f6e55b2b..c7db71db9df 100644 --- a/otelconf/v0.3.0/metric_test.go +++ b/otelconf/v0.3.0/metric_test.go @@ -4,6 +4,7 @@ package otelconf import ( + "bytes" "context" "crypto/tls" "crypto/x509" @@ -11,6 +12,7 @@ import ( "fmt" "net" "net/http" + "net/http/httptest" "os" "path/filepath" "reflect" @@ -100,6 +102,58 @@ func TestMeterProvider(t *testing.T) { } } +func TestMeterProviderOptions(t *testing.T) { + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + calls++ + })) + defer srv.Close() + + cfg := OpenTelemetryConfiguration{ + MeterProvider: &MeterProvider{ + Readers: []MetricReader{{ + Periodic: &PeriodicMetricReader{ + Exporter: PushMetricExporter{ + OTLP: &OTLPMetric{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr(srv.URL), + Insecure: ptr(true), + }, + }, + }, + }}, + }, + } + + var buf bytes.Buffer + stdoutmetricExporter, err := stdoutmetric.New(stdoutmetric.WithWriter(&buf)) + require.NoError(t, err) + + res := resource.NewSchemaless(attribute.String("foo", "bar")) + sdk, err := NewSDK( + WithOpenTelemetryConfiguration(cfg), + WithMeterProviderOptions(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(stdoutmetricExporter))), + WithMeterProviderOptions(sdkmetric.WithResource(res)), + ) + require.NoError(t, err) + defer func() { + assert.NoError(t, sdk.Shutdown(context.Background())) + // The exporter, which we passed in as an extra option to NewSDK, + // should be wired up to the provider in addition to the + // configuration-based OTLP exporter. + assert.NotZero(t, buf) + assert.Equal(t, 1, calls) // flushed on shutdown + + // Options provided by WithMeterProviderOptions may be overridden + // by configuration, e.g. the resource is always defined via + // configuration. + assert.NotContains(t, buf.String(), "foo") + }() + + counter, _ := sdk.MeterProvider().Meter("test").Int64Counter("counter") + counter.Add(context.Background(), 1) +} + func TestReader(t *testing.T) { consoleExporter, err := stdoutmetric.New( stdoutmetric.WithPrettyPrint(), diff --git a/otelconf/v0.3.0/trace.go b/otelconf/v0.3.0/trace.go index 0fb618e8e57..ab0c40c0d79 100644 --- a/otelconf/v0.3.0/trace.go +++ b/otelconf/v0.3.0/trace.go @@ -27,9 +27,8 @@ func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProv if cfg.opentelemetryConfig.TracerProvider == nil { return noop.NewTracerProvider(), noopShutdown, nil } - opts := []sdktrace.TracerProviderOption{ - sdktrace.WithResource(res), - } + opts := append(cfg.tracerProviderOptions, sdktrace.WithResource(res)) + var errs []error for _, processor := range cfg.opentelemetryConfig.TracerProvider.Processors { sp, err := spanProcessor(cfg.ctx, processor) diff --git a/otelconf/v0.3.0/trace_test.go b/otelconf/v0.3.0/trace_test.go index 23e8a5c306e..5547b114c0a 100644 --- a/otelconf/v0.3.0/trace_test.go +++ b/otelconf/v0.3.0/trace_test.go @@ -4,11 +4,14 @@ package otelconf import ( + "bytes" "context" "crypto/tls" "crypto/x509" "errors" "net" + "net/http" + "net/http/httptest" "os" "path/filepath" "reflect" @@ -22,6 +25,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" @@ -33,7 +37,7 @@ import ( v1 "go.opentelemetry.io/proto/otlp/collector/trace/v1" ) -func TestTracerPovider(t *testing.T) { +func TestTracerProvider(t *testing.T) { tests := []struct { name string cfg configOptions @@ -116,6 +120,58 @@ func TestTracerPovider(t *testing.T) { } } +func TestTracerProviderOptions(t *testing.T) { + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + calls++ + })) + defer srv.Close() + + cfg := OpenTelemetryConfiguration{ + TracerProvider: &TracerProvider{ + Processors: []SpanProcessor{{ + Simple: &SimpleSpanProcessor{ + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: ptr("http/protobuf"), + Endpoint: ptr(srv.URL), + Insecure: ptr(true), + }, + }, + }, + }}, + }, + } + + var buf bytes.Buffer + stdouttraceExporter, err := stdouttrace.New(stdouttrace.WithWriter(&buf)) + require.NoError(t, err) + + res := resource.NewSchemaless(attribute.String("foo", "bar")) + sdk, err := NewSDK( + WithOpenTelemetryConfiguration(cfg), + WithTracerProviderOptions(sdktrace.WithSyncer(stdouttraceExporter)), + WithTracerProviderOptions(sdktrace.WithResource(res)), + ) + require.NoError(t, err) + defer func() { + assert.NoError(t, sdk.Shutdown(context.Background())) + }() + + // The exporter, which we passed in as an extra option to NewSDK, + // should be wired up to the provider in addition to the + // configuration-based OTLP exporter. + tracer := sdk.TracerProvider().Tracer("test") + _, span := tracer.Start(context.Background(), "span") + span.End() + assert.NotZero(t, buf) + assert.Equal(t, 1, calls) + // Options provided by WithMeterProviderOptions may be overridden + // by configuration, e.g. the resource is always defined via + // configuration. + assert.NotContains(t, buf.String(), "foo") +} + func TestSpanProcessor(t *testing.T) { consoleExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint(), From 438b4dd38c5648fdd6040e671886b229a7d7698b Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Mon, 7 Jul 2025 16:05:47 +0800 Subject: [PATCH 2/2] Update CHANGELOG.md Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fabee20fe9d..493eab6d2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add the unit `ns` to deprecated runtime metrics `process.runtime.go.gc.pause_total_ns` and `process.runtime.go.gc.pause_ns` in `go.opentelemetry.io/contrib/instrumentation/runtime`. (#7490) -- Added support for passing additional provider options to otelconf.NewSDK (#7552) +- Add the `WithLoggerProviderOptions`, `WithMeterProviderOptions` and `WithTracerProviderOptions` options to `NewSDK` to allow passing custom options to providers in `go.opentelemetry.io/contrib/otelconf`. (#7552)