From 81f988ac853468e6a883021ee68012940e411222 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 14 Aug 2025 13:49:17 -0700 Subject: [PATCH] Flatten stdouttrace initSelfObservability into New --- exporters/stdout/stdouttrace/trace.go | 65 +++++++++++----------- exporters/stdout/stdouttrace/trace_test.go | 44 +++++++++++++++ 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/exporters/stdout/stdouttrace/trace.go b/exporters/stdout/stdouttrace/trace.go index 3b2200abaee..e191ae45a9d 100644 --- a/exporters/stdout/stdouttrace/trace.go +++ b/exporters/stdout/stdouttrace/trace.go @@ -6,6 +6,7 @@ package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdoutt import ( "context" "encoding/json" + "errors" "fmt" "sync" "sync/atomic" @@ -42,9 +43,39 @@ func New(options ...Option) (*Exporter, error) { encoder: enc, timestamps: cfg.Timestamps, } - exporter.initSelfObservability() - return exporter, nil + if !x.SelfObservability.Enabled() { + return exporter, nil + } + + exporter.selfObservabilityEnabled = true + exporter.selfObservabilityAttrs = []attribute.KeyValue{ + semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, nextExporterID())), + semconv.OTelComponentTypeKey.String(otelComponentType), + } + + mp := otel.GetMeterProvider() + m := mp.Meter( + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace", + metric.WithInstrumentationVersion(sdk.Version()), + metric.WithSchemaURL(semconv.SchemaURL), + ) + + var err, e error + if exporter.spanInflightMetric, e = otelconv.NewSDKExporterSpanInflight(m); e != nil { + e = fmt.Errorf("failed to create span inflight metric: %w", e) + err = errors.Join(err, e) + } + if exporter.spanExportedMetric, e = otelconv.NewSDKExporterSpanExported(m); e != nil { + e = fmt.Errorf("failed to create span exported metric: %w", e) + err = errors.Join(err, e) + } + if exporter.operationDurationMetric, e = otelconv.NewSDKExporterOperationDuration(m); e != nil { + e = fmt.Errorf("failed to create operation duration metric: %w", e) + err = errors.Join(err, e) + } + + return exporter, err } // Exporter is an implementation of trace.SpanSyncer that writes spans to stdout. @@ -63,36 +94,6 @@ type Exporter struct { operationDurationMetric otelconv.SDKExporterOperationDuration } -// initSelfObservability initializes self-observability for the exporter if enabled. -func (e *Exporter) initSelfObservability() { - if !x.SelfObservability.Enabled() { - return - } - - e.selfObservabilityEnabled = true - e.selfObservabilityAttrs = []attribute.KeyValue{ - semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, nextExporterID())), - semconv.OTelComponentTypeKey.String(otelComponentType), - } - - mp := otel.GetMeterProvider() - m := mp.Meter("go.opentelemetry.io/otel/exporters/stdout/stdouttrace", - metric.WithInstrumentationVersion(sdk.Version()), - metric.WithSchemaURL(semconv.SchemaURL), - ) - - var err error - if e.spanInflightMetric, err = otelconv.NewSDKExporterSpanInflight(m); err != nil { - otel.Handle(err) - } - if e.spanExportedMetric, err = otelconv.NewSDKExporterSpanExported(m); err != nil { - otel.Handle(err) - } - if e.operationDurationMetric, err = otelconv.NewSDKExporterOperationDuration(m); err != nil { - otel.Handle(err) - } -} - // ExportSpans writes spans in json format to stdout. func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) { if e.selfObservabilityEnabled { diff --git a/exporters/stdout/stdouttrace/trace_test.go b/exporters/stdout/stdouttrace/trace_test.go index 6c609c0116b..c3857387076 100644 --- a/exporters/stdout/stdouttrace/trace_test.go +++ b/exporters/stdout/stdouttrace/trace_test.go @@ -18,6 +18,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + mapi "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" @@ -443,3 +444,46 @@ func TestSelfObservability(t *testing.T) { }) } } + +type errMeterProvider struct { + mapi.MeterProvider + + err error +} + +func (m *errMeterProvider) Meter(string, ...mapi.MeterOption) mapi.Meter { + return &errMeter{err: m.err} +} + +type errMeter struct { + mapi.Meter + + err error +} + +func (m *errMeter) Int64UpDownCounter(string, ...mapi.Int64UpDownCounterOption) (mapi.Int64UpDownCounter, error) { + return nil, m.err +} + +func (m *errMeter) Int64Counter(string, ...mapi.Int64CounterOption) (mapi.Int64Counter, error) { + return nil, m.err +} + +func (m *errMeter) Float64Histogram(string, ...mapi.Float64HistogramOption) (mapi.Float64Histogram, error) { + return nil, m.err +} + +func TestSelfObservabilityInstrumentErrors(t *testing.T) { + orig := otel.GetMeterProvider() + t.Cleanup(func() { otel.SetMeterProvider(orig) }) + mp := &errMeterProvider{err: assert.AnError} + otel.SetMeterProvider(mp) + + t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + _, err := stdouttrace.New() + require.ErrorIs(t, err, assert.AnError, "new instrument errors") + + assert.ErrorContains(t, err, "inflight metric") + assert.ErrorContains(t, err, "span exported metric") + assert.ErrorContains(t, err, "operation duration metric") +}