1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-11-25 22:41:46 +02:00

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>
This commit is contained in:
Tyler Yahn
2025-09-11 01:01:19 -07:00
committed by GitHub
parent b06d2739a7
commit 7fdebbe3ed
25 changed files with 219 additions and 182 deletions

View File

@@ -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)
<!-- Released section -->

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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`:

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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)
})

View File

@@ -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
}

View File

@@ -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`:

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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)
})

View File

@@ -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)

View File

@@ -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),

View File

@@ -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()

View File

@@ -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`:

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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),
)

View File

@@ -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")

View File

@@ -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.

View File

@@ -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",
},
},
}

View File

@@ -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",
},
},
} {

View File

@@ -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)