From 7fdebbe3edc0e37118378bd1c1a3dfa50b70dc0d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 11 Sep 2025 01:01:19 -0700 Subject: [PATCH] Rename Self-Observability as just Observability (#7302) Self-Observability is a redundant term, the self being instrumented is always the self that observability is being provided for. Remove this redundancy. Continue to provide backwards compatibility for any users already using `OTEL_GO_X_SELF_OBSERVABILITY` to enable the feature. --------- Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 1 + CONTRIBUTING.md | 32 ++++++------- .../internal/observ/instrumentation.go | 2 +- .../internal/observ/instrumentation_test.go | 8 ++-- .../stdout/stdouttrace/internal/x/README.md | 8 ++-- exporters/stdout/stdouttrace/internal/x/x.go | 45 +++++++++++-------- .../stdout/stdouttrace/internal/x/x_test.go | 19 ++++---- exporters/stdout/stdouttrace/trace_test.go | 8 ++-- sdk/log/instrumentation.go | 2 +- sdk/log/internal/x/README.md | 8 ++-- sdk/log/internal/x/x.go | 45 +++++++++++-------- sdk/log/internal/x/x_test.go | 19 ++++---- sdk/log/logger_bench_test.go | 2 +- sdk/log/logger_test.go | 32 ++++++------- sdk/trace/batch_span_processor.go | 22 ++++----- sdk/trace/batch_span_processor_test.go | 18 ++++---- sdk/trace/internal/x/README.md | 8 ++-- sdk/trace/internal/x/x.go | 45 +++++++++++-------- sdk/trace/internal/x/x_test.go | 19 ++++---- sdk/trace/provider.go | 14 +++--- sdk/trace/provider_test.go | 14 +++--- sdk/trace/span.go | 2 +- sdk/trace/span_test.go | 4 +- sdk/trace/trace_test.go | 14 +++--- sdk/trace/tracer.go | 10 ++--- 25 files changed, 219 insertions(+), 182 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3abe4588..8494643ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed +- Rename the `OTEL_GO_X_SELF_OBSERVABILITY` environment variable to `OTEL_GO_X_OBSERVABILITY` in `go.opentelemetry.io/otel/sdk/trace`, `go.opentelemetry.io/otel/sdk/log`, and `go.opentelemetry.io/otel/exporters/stdout/stdouttrace`. (#7302) - Improve performance of histogram `Record` in `go.opentelemetry.io/otel/sdk/metric` when min and max are disabled using `NoMinMax`. (#7306) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ffec029a..7bc06ec11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -684,8 +684,8 @@ This section outlines the best practices for building instrumentation in OpenTel #### Environment Variable Activation -Self-observability features are currently experimental. -They should be disabled by default and activated through the `OTEL_GO_X_SELF_OBSERVABILITY` environment variable. +Observability features are currently experimental. +They should be disabled by default and activated through the `OTEL_GO_X_OBSERVABILITY` environment variable. This follows the established experimental feature pattern used throughout the SDK. Components should check for this environment variable using a consistent pattern: @@ -693,8 +693,8 @@ Components should check for this environment variable using a consistent pattern ```go import "go.opentelemetry.io/otel/*/internal/x" -if x.SelfObservability.Enabled() { - // Initialize self-observability metrics +if x.Observability.Enabled() { + // Initialize observability metrics } ``` @@ -769,7 +769,7 @@ type instrumentation struct { } func newInstrumentation() (*instrumentation, error) { - if !x.SelfObservability.Enabled() { + if !x.Observability.Enabled() { return nil, nil } @@ -796,7 +796,7 @@ func newInstrumentation() (*instrumentation, error) { // ❌ Avoid this pattern. func (c *Component) initObservability() { // Initialize observability metrics - if !x.SelfObservability.Enabled() { + if !x.Observability.Enabled() { return } @@ -932,17 +932,17 @@ Demonstrate the impact (allocs/op, B/op, ns/op) in enabled/disabled scenarios: func BenchmarkExportSpans(b *testing.B) { scenarios := []struct { name string - selfObsEnabled bool + obsEnabled bool }{ - {"SelfObsDisabled", false}, - {"SelfObsEnabled", true}, + {"ObsDisabled", false}, + {"ObsEnabled", true}, } for _, scenario := range scenarios { b.Run(scenario.name, func(b *testing.B) { b.Setenv( - "OTEL_GO_X_SELF_OBSERVABILITY", - strconv.FormatBool(scenario.selfObsEnabled), + "OTEL_GO_X_OBSERVABILITY", + strconv.FormatBool(scenario.obsEnabled), ) exporter := NewExporter() @@ -965,7 +965,7 @@ Errors should be reported back to the caller if possible, and partial failures s ```go func newInstrumentation() (*instrumentation, error) { - if !x.SelfObservability.Enabled() { + if !x.Observability.Enabled() { return nil, nil } @@ -981,7 +981,7 @@ func newInstrumentation() (*instrumentation, error) { ```go // ❌ Avoid this pattern. func newInstrumentation() *instrumentation { - if !x.SelfObservability.Enabled() { + if !x.Observability.Enabled() { return nil, nil } @@ -1038,7 +1038,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) #### Semantic Conventions Compliance -All self-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). +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.37.0/otelconv/metric.go). @@ -1087,7 +1087,7 @@ See [stdouttrace exporter example](./exporters/stdout/stdouttrace/internal/gen.g Use deterministic testing with isolated state: ```go -func TestSelfObservability(t *testing.T) { +func TestObservability(t *testing.T) { // Restore state after test to ensure this does not affect other tests. prev := otel.GetMeterProvider() t.Cleanup(func() { otel.SetMeterProvider(prev) }) @@ -1098,7 +1098,7 @@ func TestSelfObservability(t *testing.T) { otel.SetMeterProvider(meterProvider) // Use t.Setenv to ensure environment variable is restored after test. - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") // Reset component ID counter to ensure deterministic component names. componentIDCounter.Store(0) diff --git a/exporters/stdout/stdouttrace/internal/observ/instrumentation.go b/exporters/stdout/stdouttrace/internal/observ/instrumentation.go index 286df2a33..abcb86e86 100644 --- a/exporters/stdout/stdouttrace/internal/observ/instrumentation.go +++ b/exporters/stdout/stdouttrace/internal/observ/instrumentation.go @@ -98,7 +98,7 @@ type Instrumentation struct { // // If the experimental observability is disabled, nil is returned. func NewInstrumentation(id int64) (*Instrumentation, error) { - if !x.SelfObservability.Enabled() { + if !x.Observability.Enabled() { return nil, nil } diff --git a/exporters/stdout/stdouttrace/internal/observ/instrumentation_test.go b/exporters/stdout/stdouttrace/internal/observ/instrumentation_test.go index 33ea3ed96..51e1a6050 100644 --- a/exporters/stdout/stdouttrace/internal/observ/instrumentation_test.go +++ b/exporters/stdout/stdouttrace/internal/observ/instrumentation_test.go @@ -65,7 +65,7 @@ func TestNewInstrumentationObservabiltyErrors(t *testing.T) { mp := &errMeterProvider{err: assert.AnError} otel.SetMeterProvider(mp) - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") _, err := observ.NewInstrumentation(ID) require.ErrorIs(t, err, assert.AnError, "new instrument errors") @@ -76,7 +76,7 @@ func TestNewInstrumentationObservabiltyErrors(t *testing.T) { } func TestNewInstrumentationObservabiltyDisabled(t *testing.T) { - // Do not set OTEL_GO_X_SELF_OBSERVABILITY. + // Do not set OTEL_GO_X_OBSERVABILITY. got, err := observ.NewInstrumentation(ID) assert.NoError(t, err) assert.Nil(t, got) @@ -85,7 +85,7 @@ func TestNewInstrumentationObservabiltyDisabled(t *testing.T) { func setup(t *testing.T) (*observ.Instrumentation, func() metricdata.ScopeMetrics) { t.Helper() - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") original := otel.GetMeterProvider() t.Cleanup(func() { otel.SetMeterProvider(original) }) @@ -220,7 +220,7 @@ func TestInstrumentationExportSpansPartialErrored(t *testing.T) { } func BenchmarkInstrumentationExportSpans(b *testing.B) { - b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + b.Setenv("OTEL_GO_X_OBSERVABILITY", "true") inst, err := observ.NewInstrumentation(ID) if err != nil { b.Fatalf("failed to create instrumentation: %v", err) diff --git a/exporters/stdout/stdouttrace/internal/x/README.md b/exporters/stdout/stdouttrace/internal/x/README.md index 6b7d1aec8..c41ea34bf 100644 --- a/exporters/stdout/stdouttrace/internal/x/README.md +++ b/exporters/stdout/stdouttrace/internal/x/README.md @@ -8,13 +8,13 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for ## Features -- [Self-Observability](#self-observability) +- [Observability](#observability) -### Self-Observability +### Observability -The `stdouttrace` exporter provides a self-observability feature that allows you to monitor the SDK itself. +The `stdouttrace` exporter can be configured to provide observability about itself using OpenTelemetry metrics. -To opt-in, set the environment variable `OTEL_GO_X_SELF_OBSERVABILITY` to `true`. +To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`. When enabled, the SDK will create the following metrics using the global `MeterProvider`: diff --git a/exporters/stdout/stdouttrace/internal/x/x.go b/exporters/stdout/stdouttrace/internal/x/x.go index 55bb98a96..3dfbf069c 100644 --- a/exporters/stdout/stdouttrace/internal/x/x.go +++ b/exporters/stdout/stdouttrace/internal/x/x.go @@ -9,37 +9,44 @@ import ( "strings" ) -// SelfObservability is an experimental feature flag that determines if SDK -// self-observability metrics are enabled. +// Observability is an experimental feature flag that determines if exporter +// observability metrics are enabled. // -// To enable this feature set the OTEL_GO_X_SELF_OBSERVABILITY environment variable +// 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 SelfObservability = newFeature("SELF_OBSERVABILITY", func(v string) (string, bool) { - if strings.EqualFold(v, "true") { - return v, true - } - return "", false -}) +var Observability = newFeature( + []string{"OBSERVABILITY", "SELF_OBSERVABILITY"}, + func(v string) (string, bool) { + if strings.EqualFold(v, "true") { + return v, true + } + return "", false + }, +) // 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 + keys []string parse func(v string) (T, bool) } -func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] { +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]{ - key: envKeyRoot + suffix, + keys: keys, parse: parse, } } -// Key returns the environment variable key that needs to be set to enable the +// Keys returns the environment variable keys that can be set to enable the // feature. -func (f Feature[T]) Key() string { return f.key } +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 @@ -49,11 +56,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) { // // > 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 + for _, key := range f.keys { + vRaw := os.Getenv(key) + if vRaw != "" { + return f.parse(vRaw) + } } - return f.parse(vRaw) + return v, ok } // Enabled reports whether the feature is enabled. diff --git a/exporters/stdout/stdouttrace/internal/x/x_test.go b/exporters/stdout/stdouttrace/internal/x/x_test.go index 15124ca91..27e45afe5 100644 --- a/exporters/stdout/stdouttrace/internal/x/x_test.go +++ b/exporters/stdout/stdouttrace/internal/x/x_test.go @@ -10,15 +10,18 @@ import ( "github.com/stretchr/testify/require" ) -func TestSelfObservability(t *testing.T) { - const key = "OTEL_GO_X_SELF_OBSERVABILITY" - require.Equal(t, key, SelfObservability.Key()) +func TestObservability(t *testing.T) { + const key = "OTEL_GO_X_OBSERVABILITY" + require.Contains(t, Observability.Keys(), key) - t.Run("100", run(setenv(key, "100"), assertDisabled(SelfObservability))) - t.Run("true", run(setenv(key, "true"), assertEnabled(SelfObservability, "true"))) - t.Run("True", run(setenv(key, "True"), assertEnabled(SelfObservability, "True"))) - t.Run("false", run(setenv(key, "false"), assertDisabled(SelfObservability))) - t.Run("empty", run(assertDisabled(SelfObservability))) + const altKey = "OTEL_GO_X_SELF_OBSERVABILITY" + require.Contains(t, Observability.Keys(), altKey) + + t.Run("100", run(setenv(key, "100"), assertDisabled(Observability))) + t.Run("true", run(setenv(key, "true"), assertEnabled(Observability, "true"))) + t.Run("True", run(setenv(key, "True"), assertEnabled(Observability, "True"))) + t.Run("false", run(setenv(key, "false"), assertDisabled(Observability))) + t.Run("empty", run(assertDisabled(Observability))) } func run(steps ...func(*testing.T)) func(*testing.T) { diff --git a/exporters/stdout/stdouttrace/trace_test.go b/exporters/stdout/stdouttrace/trace_test.go index 763a0c9cd..c98b3ae54 100644 --- a/exporters/stdout/stdouttrace/trace_test.go +++ b/exporters/stdout/stdouttrace/trace_test.go @@ -239,7 +239,7 @@ func TestExporterShutdownNoError(t *testing.T) { } } -func TestSelfObservability(t *testing.T) { +func TestObservability(t *testing.T) { defaultCallExportSpans := func(t *testing.T, exporter *stdouttrace.Exporter) { require.NoError(t, exporter.ExportSpans(context.Background(), tracetest.SpanStubs{ {Name: "/foo"}, @@ -338,7 +338,7 @@ func TestSelfObservability(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.enabled { - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") // Reset component name counter for each test. _ = counter.SetExporterID(0) @@ -389,8 +389,8 @@ func BenchmarkExporterExportSpans(b *testing.B) { _ = err } - b.Run("SelfObservability", func(b *testing.B) { - b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + b.Run("Observability", func(b *testing.B) { + b.Setenv("OTEL_GO_X_OBSERVABILITY", "true") run(b) }) diff --git a/sdk/log/instrumentation.go b/sdk/log/instrumentation.go index 0a82ef8d5..d40dbab59 100644 --- a/sdk/log/instrumentation.go +++ b/sdk/log/instrumentation.go @@ -18,7 +18,7 @@ import ( // newRecordCounterIncr returns a function that increments the log record // counter metric. If observability is disabled, it returns nil. func newRecordCounterIncr() (func(context.Context), error) { - if !x.SelfObservability.Enabled() { + if !x.Observability.Enabled() { return nil, nil } diff --git a/sdk/log/internal/x/README.md b/sdk/log/internal/x/README.md index 83e9e7b4c..33176f78e 100644 --- a/sdk/log/internal/x/README.md +++ b/sdk/log/internal/x/README.md @@ -8,13 +8,13 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for ## Features -- [Self-Observability](#self-observability) +- [Observability](#observability) -### Self-Observability +### Observability -The Logs SDK provides a self-observability feature that allows you to monitor the SDK itself. +The Logs SDK can be configured to provide observability about itself using OpenTelemetry metrics. -To opt-in, set the environment variable `OTEL_GO_X_SELF_OBSERVABILITY` to `true`. +To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`. When enabled, the SDK will create the following metrics using the global `MeterProvider`: diff --git a/sdk/log/internal/x/x.go b/sdk/log/internal/x/x.go index 5f01b275d..79eb83792 100644 --- a/sdk/log/internal/x/x.go +++ b/sdk/log/internal/x/x.go @@ -9,37 +9,44 @@ import ( "strings" ) -// SelfObservability is an experimental feature flag that determines if SDK -// self-observability metrics are enabled. +// Observability is an experimental feature flag that determines if SDK +// observability metrics are enabled. // -// To enable this feature set the OTEL_GO_X_SELF_OBSERVABILITY environment variable +// 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 SelfObservability = newFeature("SELF_OBSERVABILITY", func(v string) (string, bool) { - if strings.EqualFold(v, "true") { - return v, true - } - return "", false -}) +var Observability = newFeature( + []string{"OBSERVABILITY", "SELF_OBSERVABILITY"}, + func(v string) (string, bool) { + if strings.EqualFold(v, "true") { + return v, true + } + return "", false + }, +) // 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 + keys []string parse func(v string) (T, bool) } -func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] { +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]{ - key: envKeyRoot + suffix, + keys: keys, parse: parse, } } -// Key returns the environment variable key that needs to be set to enable the +// Keys returns the environment variable keys that can be set to enable the // feature. -func (f Feature[T]) Key() string { return f.key } +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 @@ -49,11 +56,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) { // // > 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 + for _, key := range f.keys { + vRaw := os.Getenv(key) + if vRaw != "" { + return f.parse(vRaw) + } } - return f.parse(vRaw) + return v, ok } // Enabled reports whether the feature is enabled. diff --git a/sdk/log/internal/x/x_test.go b/sdk/log/internal/x/x_test.go index 15124ca91..27e45afe5 100644 --- a/sdk/log/internal/x/x_test.go +++ b/sdk/log/internal/x/x_test.go @@ -10,15 +10,18 @@ import ( "github.com/stretchr/testify/require" ) -func TestSelfObservability(t *testing.T) { - const key = "OTEL_GO_X_SELF_OBSERVABILITY" - require.Equal(t, key, SelfObservability.Key()) +func TestObservability(t *testing.T) { + const key = "OTEL_GO_X_OBSERVABILITY" + require.Contains(t, Observability.Keys(), key) - t.Run("100", run(setenv(key, "100"), assertDisabled(SelfObservability))) - t.Run("true", run(setenv(key, "true"), assertEnabled(SelfObservability, "true"))) - t.Run("True", run(setenv(key, "True"), assertEnabled(SelfObservability, "True"))) - t.Run("false", run(setenv(key, "false"), assertDisabled(SelfObservability))) - t.Run("empty", run(assertDisabled(SelfObservability))) + const altKey = "OTEL_GO_X_SELF_OBSERVABILITY" + require.Contains(t, Observability.Keys(), altKey) + + t.Run("100", run(setenv(key, "100"), assertDisabled(Observability))) + t.Run("true", run(setenv(key, "true"), assertEnabled(Observability, "true"))) + t.Run("True", run(setenv(key, "True"), assertEnabled(Observability, "True"))) + t.Run("false", run(setenv(key, "false"), assertDisabled(Observability))) + t.Run("empty", run(assertDisabled(Observability))) } func run(steps ...func(*testing.T)) func(*testing.T) { diff --git a/sdk/log/logger_bench_test.go b/sdk/log/logger_bench_test.go index d7bf70291..d29628496 100644 --- a/sdk/log/logger_bench_test.go +++ b/sdk/log/logger_bench_test.go @@ -93,7 +93,7 @@ func BenchmarkLoggerEmitObservability(b *testing.B) { b.Run("Disabled", run(newLogger(lp, scope))) b.Run("Enabled", func(b *testing.B) { - b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + b.Setenv("OTEL_GO_X_OBSERVABILITY", "true") run(newLogger(lp, scope))(b) }) diff --git a/sdk/log/logger_test.go b/sdk/log/logger_test.go index 0d6b2d136..cacf314c2 100644 --- a/sdk/log/logger_test.go +++ b/sdk/log/logger_test.go @@ -402,30 +402,30 @@ func TestLoggerEnabled(t *testing.T) { } } -func TestLoggerSelfObservability(t *testing.T) { +func TestLoggerObservability(t *testing.T) { testCases := []struct { - name string - selfObservabilityEnabled bool - records []log.Record - wantLogRecordCount int64 + name string + enabled bool + records []log.Record + wantLogRecordCount int64 }{ { - name: "Disabled", - selfObservabilityEnabled: false, - records: []log.Record{{}, {}}, - wantLogRecordCount: 0, + name: "Disabled", + enabled: false, + records: []log.Record{{}, {}}, + wantLogRecordCount: 0, }, { - name: "Enabled", - selfObservabilityEnabled: true, - records: []log.Record{{}, {}, {}, {}, {}}, - wantLogRecordCount: 5, + name: "Enabled", + enabled: true, + records: []log.Record{{}, {}, {}, {}, {}}, + wantLogRecordCount: 5, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", strconv.FormatBool(tc.selfObservabilityEnabled)) + t.Setenv("OTEL_GO_X_OBSERVABILITY", strconv.FormatBool(tc.enabled)) prev := otel.GetMeterProvider() t.Cleanup(func() { otel.SetMeterProvider(prev) @@ -469,7 +469,7 @@ func TestLoggerSelfObservability(t *testing.T) { } } -func TestNewLoggerSelfObservabilityErrorHandled(t *testing.T) { +func TestNewLoggerObservabilityErrorHandled(t *testing.T) { errHandler := otel.GetErrorHandler() t.Cleanup(func() { otel.SetErrorHandler(errHandler) @@ -483,7 +483,7 @@ func TestNewLoggerSelfObservabilityErrorHandled(t *testing.T) { t.Cleanup(func() { otel.SetMeterProvider(orig) }) otel.SetMeterProvider(&errMeterProvider{err: assert.AnError}) - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") l := newLogger(NewLoggerProvider(), instrumentation.Scope{}) _ = l require.Len(t, errs, 1) diff --git a/sdk/trace/batch_span_processor.go b/sdk/trace/batch_span_processor.go index 9bc3e525d..a5ae6925d 100644 --- a/sdk/trace/batch_span_processor.go +++ b/sdk/trace/batch_span_processor.go @@ -78,10 +78,10 @@ type batchSpanProcessor struct { queue chan ReadOnlySpan dropped uint32 - selfObservabilityEnabled bool - callbackRegistration metric.Registration - spansProcessedCounter otelconv.SDKProcessorSpanProcessed - componentNameAttr attribute.KeyValue + observabilityEnabled bool + callbackRegistration metric.Registration + spansProcessedCounter otelconv.SDKProcessorSpanProcessed + componentNameAttr attribute.KeyValue batch []ReadOnlySpan batchMutex sync.Mutex @@ -124,8 +124,8 @@ func NewBatchSpanProcessor(exporter SpanExporter, options ...BatchSpanProcessorO stopCh: make(chan struct{}), } - if x.SelfObservability.Enabled() { - bsp.selfObservabilityEnabled = true + if x.Observability.Enabled() { + bsp.observabilityEnabled = true bsp.componentNameAttr = componentName() var err error @@ -172,7 +172,7 @@ func newBSPObs( qMax int64, ) (otelconv.SDKProcessorSpanProcessed, metric.Registration, error) { meter := otel.GetMeterProvider().Meter( - selfObsScopeName, + obsScopeName, metric.WithInstrumentationVersion(sdk.Version()), metric.WithSchemaURL(semconv.SchemaURL), ) @@ -242,7 +242,7 @@ func (bsp *batchSpanProcessor) Shutdown(ctx context.Context) error { case <-ctx.Done(): err = ctx.Err() } - if bsp.selfObservabilityEnabled { + if bsp.observabilityEnabled { err = errors.Join(err, bsp.callbackRegistration.Unregister()) } }) @@ -357,7 +357,7 @@ func (bsp *batchSpanProcessor) exportSpans(ctx context.Context) error { if l := len(bsp.batch); l > 0 { global.Debug("exporting spans", "count", len(bsp.batch), "total_dropped", atomic.LoadUint32(&bsp.dropped)) - if bsp.selfObservabilityEnabled { + if bsp.observabilityEnabled { bsp.spansProcessedCounter.Add(ctx, int64(l), bsp.componentNameAttr, bsp.spansProcessedCounter.AttrComponentType(otelconv.ComponentTypeBatchingSpanProcessor)) @@ -470,7 +470,7 @@ func (bsp *batchSpanProcessor) enqueueBlockOnQueueFull(ctx context.Context, sd R case bsp.queue <- sd: return true case <-ctx.Done(): - if bsp.selfObservabilityEnabled { + if bsp.observabilityEnabled { bsp.spansProcessedCounter.Add(ctx, 1, bsp.componentNameAttr, bsp.spansProcessedCounter.AttrComponentType(otelconv.ComponentTypeBatchingSpanProcessor), @@ -490,7 +490,7 @@ func (bsp *batchSpanProcessor) enqueueDrop(ctx context.Context, sd ReadOnlySpan) return true default: atomic.AddUint32(&bsp.dropped, 1) - if bsp.selfObservabilityEnabled { + if bsp.observabilityEnabled { bsp.spansProcessedCounter.Add(ctx, 1, bsp.componentNameAttr, bsp.spansProcessedCounter.AttrComponentType(otelconv.ComponentTypeBatchingSpanProcessor), diff --git a/sdk/trace/batch_span_processor_test.go b/sdk/trace/batch_span_processor_test.go index cef4c8198..543b63155 100644 --- a/sdk/trace/batch_span_processor_test.go +++ b/sdk/trace/batch_span_processor_test.go @@ -654,7 +654,7 @@ var dropSpanMetricsView = sdkmetric.NewView( ) func TestBatchSpanProcessorMetricsDisabled(t *testing.T) { - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "false") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "false") tp := basicTracerProvider(t) reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider( @@ -693,7 +693,7 @@ func TestBatchSpanProcessorMetricsDisabled(t *testing.T) { } func TestBatchSpanProcessorMetrics(t *testing.T) { - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") tp := basicTracerProvider(t) reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider( @@ -719,16 +719,16 @@ func TestBatchSpanProcessorMetrics(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, me.waitForSpans(ctx, 2)) - assertSelfObsScopeMetrics(t, internalBsp.componentNameAttr, reader, + assertObsScopeMetrics(t, internalBsp.componentNameAttr, reader, expectMetrics{queueCapacity: 2, queueSize: 0, successProcessed: 2}) // Generate 3 spans. 2 fill the queue, and 1 is dropped because the queue is full. generateSpan(t, tr, testOption{genNumSpans: 3}) - assertSelfObsScopeMetrics(t, internalBsp.componentNameAttr, reader, + assertObsScopeMetrics(t, internalBsp.componentNameAttr, reader, expectMetrics{queueCapacity: 2, queueSize: 2, queueFullProcessed: 1, successProcessed: 2}) } func TestBatchSpanProcessorBlockingMetrics(t *testing.T) { - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") tp := basicTracerProvider(t) reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider( @@ -756,7 +756,7 @@ func TestBatchSpanProcessorBlockingMetrics(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() assert.NoError(t, me.waitForSpans(ctx, 2)) - assertSelfObsScopeMetrics(t, internalBsp.componentNameAttr, reader, + assertObsScopeMetrics(t, internalBsp.componentNameAttr, reader, expectMetrics{queueCapacity: 2, queueSize: 0, successProcessed: 2}) // Generate 2 spans to fill the queue. generateSpan(t, tr, testOption{genNumSpans: 2}) @@ -764,14 +764,14 @@ func TestBatchSpanProcessorBlockingMetrics(t *testing.T) { // Generate a span which blocks because the queue is full. generateSpan(t, tr, testOption{genNumSpans: 1}) }() - assertSelfObsScopeMetrics(t, internalBsp.componentNameAttr, reader, + assertObsScopeMetrics(t, internalBsp.componentNameAttr, reader, expectMetrics{queueCapacity: 2, queueSize: 2, successProcessed: 2}) // Use ForceFlush to force the span that is blocking on the full queue to be dropped. ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() assert.Error(t, tp.ForceFlush(ctx)) - assertSelfObsScopeMetrics(t, internalBsp.componentNameAttr, reader, + assertObsScopeMetrics(t, internalBsp.componentNameAttr, reader, expectMetrics{queueCapacity: 2, queueSize: 2, queueFullProcessed: 1, successProcessed: 2}) } @@ -782,7 +782,7 @@ type expectMetrics struct { queueFullProcessed int64 } -func assertSelfObsScopeMetrics(t *testing.T, componentNameAttr attribute.KeyValue, reader sdkmetric.Reader, +func assertObsScopeMetrics(t *testing.T, componentNameAttr attribute.KeyValue, reader sdkmetric.Reader, expectation expectMetrics, ) { t.Helper() diff --git a/sdk/trace/internal/x/README.md b/sdk/trace/internal/x/README.md index feec16fa6..bd798efc5 100644 --- a/sdk/trace/internal/x/README.md +++ b/sdk/trace/internal/x/README.md @@ -8,13 +8,13 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for ## Features -- [Self-Observability](#self-observability) +- [Observability](#observability) -### Self-Observability +### Observability -The SDK provides a self-observability feature that allows you to monitor the SDK itself. +The SDK can be configured to provide observability about itself using OpenTelemetry metrics. -To opt-in, set the environment variable `OTEL_GO_X_SELF_OBSERVABILITY` to `true`. +To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`. When enabled, the SDK will create the following metrics using the global `MeterProvider`: diff --git a/sdk/trace/internal/x/x.go b/sdk/trace/internal/x/x.go index 2fcbbcc66..fb7334c2b 100644 --- a/sdk/trace/internal/x/x.go +++ b/sdk/trace/internal/x/x.go @@ -9,37 +9,44 @@ import ( "strings" ) -// SelfObservability is an experimental feature flag that determines if SDK -// self-observability metrics are enabled. +// Observability is an experimental feature flag that determines if SDK +// observability metrics are enabled. // -// To enable this feature set the OTEL_GO_X_SELF_OBSERVABILITY environment variable +// 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 SelfObservability = newFeature("SELF_OBSERVABILITY", func(v string) (string, bool) { - if strings.EqualFold(v, "true") { - return v, true - } - return "", false -}) +var Observability = newFeature( + []string{"OBSERVABILITY", "SELF_OBSERVABILITY"}, + func(v string) (string, bool) { + if strings.EqualFold(v, "true") { + return v, true + } + return "", false + }, +) // 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 + keys []string parse func(v string) (T, bool) } -func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] { +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]{ - key: envKeyRoot + suffix, + keys: keys, parse: parse, } } -// Key returns the environment variable key that needs to be set to enable the +// Keys returns the environment variable keys that can be set to enable the // feature. -func (f Feature[T]) Key() string { return f.key } +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 @@ -49,11 +56,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) { // // > 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 + for _, key := range f.keys { + vRaw := os.Getenv(key) + if vRaw != "" { + return f.parse(vRaw) + } } - return f.parse(vRaw) + return v, ok } // Enabled reports whether the feature is enabled. diff --git a/sdk/trace/internal/x/x_test.go b/sdk/trace/internal/x/x_test.go index 15124ca91..27e45afe5 100644 --- a/sdk/trace/internal/x/x_test.go +++ b/sdk/trace/internal/x/x_test.go @@ -10,15 +10,18 @@ import ( "github.com/stretchr/testify/require" ) -func TestSelfObservability(t *testing.T) { - const key = "OTEL_GO_X_SELF_OBSERVABILITY" - require.Equal(t, key, SelfObservability.Key()) +func TestObservability(t *testing.T) { + const key = "OTEL_GO_X_OBSERVABILITY" + require.Contains(t, Observability.Keys(), key) - t.Run("100", run(setenv(key, "100"), assertDisabled(SelfObservability))) - t.Run("true", run(setenv(key, "true"), assertEnabled(SelfObservability, "true"))) - t.Run("True", run(setenv(key, "True"), assertEnabled(SelfObservability, "True"))) - t.Run("false", run(setenv(key, "false"), assertDisabled(SelfObservability))) - t.Run("empty", run(assertDisabled(SelfObservability))) + const altKey = "OTEL_GO_X_SELF_OBSERVABILITY" + require.Contains(t, Observability.Keys(), altKey) + + t.Run("100", run(setenv(key, "100"), assertDisabled(Observability))) + t.Run("true", run(setenv(key, "true"), assertEnabled(Observability, "true"))) + t.Run("True", run(setenv(key, "True"), assertEnabled(Observability, "True"))) + t.Run("false", run(setenv(key, "false"), assertDisabled(Observability))) + t.Run("empty", run(assertDisabled(Observability))) } func run(steps ...func(*testing.T)) func(*testing.T) { diff --git a/sdk/trace/provider.go b/sdk/trace/provider.go index 37ce2ac87..1735764f1 100644 --- a/sdk/trace/provider.go +++ b/sdk/trace/provider.go @@ -26,7 +26,7 @@ import ( const ( defaultTracerName = "go.opentelemetry.io/otel/sdk/tracer" - selfObsScopeName = "go.opentelemetry.io/otel/sdk/trace" + obsScopeName = "go.opentelemetry.io/otel/sdk/trace" ) // tracerProviderConfig. @@ -163,15 +163,15 @@ func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T t, ok := p.namedTracer[is] if !ok { t = &tracer{ - provider: p, - instrumentationScope: is, - selfObservabilityEnabled: x.SelfObservability.Enabled(), + provider: p, + instrumentationScope: is, + observabilityEnabled: x.Observability.Enabled(), } - if t.selfObservabilityEnabled { + if t.observabilityEnabled { var err error t.spanLiveMetric, t.spanStartedMetric, err = newInst() if err != nil { - msg := "failed to create self-observability metrics for tracer: %w" + msg := "failed to create observability metrics for tracer: %w" err := fmt.Errorf(msg, err) otel.Handle(err) } @@ -203,7 +203,7 @@ func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T func newInst() (otelconv.SDKSpanLive, otelconv.SDKSpanStarted, error) { m := otel.GetMeterProvider().Meter( - selfObsScopeName, + obsScopeName, metric.WithInstrumentationVersion(sdk.Version()), metric.WithSchemaURL(semconv.SchemaURL), ) diff --git a/sdk/trace/provider_test.go b/sdk/trace/provider_test.go index 9fa1923a7..7fabad7ab 100644 --- a/sdk/trace/provider_test.go +++ b/sdk/trace/provider_test.go @@ -401,18 +401,18 @@ func TestTracerProviderReturnsSameTracer(t *testing.T) { assert.Same(t, t2, t5) } -func TestTracerProviderSelfObservability(t *testing.T) { +func TestTracerProviderObservability(t *testing.T) { handler.Reset() p := NewTracerProvider() - // Enable self-observability - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + // Enable observability + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") tr := p.Tracer("test-tracer") require.IsType(t, &tracer{}, tr) tStruct := tr.(*tracer) - assert.True(t, tStruct.selfObservabilityEnabled, "Self-observability should be enabled") + assert.True(t, tStruct.observabilityEnabled, "observability should be enabled") // Verify instruments are created assert.NotNil(t, tStruct.spanLiveMetric, "spanLiveMetric should be created") @@ -423,7 +423,7 @@ func TestTracerProviderSelfObservability(t *testing.T) { assert.Empty(t, handlerErrs, "No errors should occur during instrument creation") } -func TestTracerProviderSelfObservabilityErrorsHandled(t *testing.T) { +func TestTracerProviderObservabilityErrorsHandled(t *testing.T) { handler.Reset() orig := otel.GetMeterProvider() @@ -432,8 +432,8 @@ func TestTracerProviderSelfObservabilityErrorsHandled(t *testing.T) { p := NewTracerProvider() - // Enable self-observability - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + // Enable observability + t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") // Create a tracer to trigger instrument creation. tr := p.Tracer("test-tracer") diff --git a/sdk/trace/span.go b/sdk/trace/span.go index b376051fb..491e510de 100644 --- a/sdk/trace/span.go +++ b/sdk/trace/span.go @@ -496,7 +496,7 @@ func (s *recordingSpan) End(options ...trace.SpanEndOption) { } s.mu.Unlock() - if s.tracer.selfObservabilityEnabled { + if s.tracer.observabilityEnabled { defer func() { // Add the span to the context to ensure the metric is recorded // with the correct span context. diff --git a/sdk/trace/span_test.go b/sdk/trace/span_test.go index ac9f9a44b..26a7a889b 100644 --- a/sdk/trace/span_test.go +++ b/sdk/trace/span_test.go @@ -408,9 +408,9 @@ func BenchmarkSpanEnd(b *testing.B) { name: "Default", }, { - name: "SelfObservabilityEnabled", + name: "ObservabilityEnabled", env: map[string]string{ - "OTEL_GO_X_SELF_OBSERVABILITY": "True", + "OTEL_GO_X_OBSERVABILITY": "True", }, }, } diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index 7da0d13d6..61a679e09 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -2246,7 +2246,7 @@ func TestAddLinkToNonRecordingSpan(t *testing.T) { } } -func TestSelfObservability(t *testing.T) { +func TestObservability(t *testing.T) { testCases := []struct { name string test func(t *testing.T, scopeMetrics func() metricdata.ScopeMetrics) @@ -2723,7 +2723,7 @@ func TestSelfObservability(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "True") + t.Setenv("OTEL_GO_X_OBSERVABILITY", "True") prev := otel.GetMeterProvider() t.Cleanup(func() { otel.SetMeterProvider(prev) }) @@ -2749,8 +2749,8 @@ type ctxKeyT string // ctxKey is a context key used to store and retrieve values in the context. var ctxKey = ctxKeyT("testKey") -func TestSelfObservabilityContextPropagation(t *testing.T) { - t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "True") +func TestObservabilityContextPropagation(t *testing.T) { + t.Setenv("OTEL_GO_X_OBSERVABILITY", "True") prev := otel.GetMeterProvider() t.Cleanup(func() { otel.SetMeterProvider(prev) }) @@ -2805,7 +2805,7 @@ func TestSelfObservabilityContextPropagation(t *testing.T) { tp := NewTracerProvider() wrap := func(parentCtx context.Context, name string, fn func(context.Context)) { - const tracer = "TestSelfObservabilityContextPropagation" + const tracer = "TestObservabilityContextPropagation" ctx, s := tp.Tracer(tracer).Start(parentCtx, name) defer s.End() fn(ctx) @@ -2903,9 +2903,9 @@ func BenchmarkTraceStart(b *testing.B) { }, }, { - name: "SelfObservabilityEnabled", + name: "ObservabilityEnabled", env: map[string]string{ - "OTEL_GO_X_SELF_OBSERVABILITY": "True", + "OTEL_GO_X_OBSERVABILITY": "True", }, }, } { diff --git a/sdk/trace/tracer.go b/sdk/trace/tracer.go index e965c4cce..c6b77a200 100644 --- a/sdk/trace/tracer.go +++ b/sdk/trace/tracer.go @@ -20,9 +20,9 @@ type tracer struct { provider *TracerProvider instrumentationScope instrumentation.Scope - selfObservabilityEnabled bool - spanLiveMetric otelconv.SDKSpanLive - spanStartedMetric otelconv.SDKSpanStarted + observabilityEnabled bool + spanLiveMetric otelconv.SDKSpanLive + spanStartedMetric otelconv.SDKSpanStarted } var _ trace.Tracer = &tracer{} @@ -53,7 +53,7 @@ func (tr *tracer) Start( s := tr.newSpan(ctx, name, &config) newCtx := trace.ContextWithSpan(ctx, s) - if tr.selfObservabilityEnabled { + if tr.observabilityEnabled { psc := trace.SpanContextFromContext(ctx) set := spanStartedSet(psc, s) tr.spanStartedMetric.AddSet(newCtx, 1, set) @@ -168,7 +168,7 @@ func (tr *tracer) newRecordingSpan( s.SetAttributes(sr.Attributes...) s.SetAttributes(config.Attributes()...) - if tr.selfObservabilityEnabled { + if tr.observabilityEnabled { // Propagate any existing values from the context with the new span to // the measurement context. ctx = trace.ContextWithSpan(ctx, s)