Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->
Expand Down
32 changes: 16 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -684,17 +684,17 @@ 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:

```go
import "go.opentelemetry.io/otel/*/internal/x"

if x.SelfObservability.Enabled() {
// Initialize self-observability metrics
if x.Observability.Enabled() {
// Initialize observability metrics
}
```

Expand Down Expand Up @@ -769,7 +769,7 @@ type instrumentation struct {
}

func newInstrumentation() (*instrumentation, error) {
if !x.SelfObservability.Enabled() {
if !x.Observability.Enabled() {
return nil, nil
}

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

Expand Down Expand Up @@ -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()
Expand All @@ -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
}

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

Expand Down Expand Up @@ -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).

Expand Down Expand Up @@ -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) })
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand All @@ -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) })
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions exporters/stdout/stdouttrace/internal/x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down
45 changes: 27 additions & 18 deletions exporters/stdout/stdouttrace/internal/x/x.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
21 changes: 12 additions & 9 deletions exporters/stdout/stdouttrace/internal/x/x_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

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)))
func TestObservability(t *testing.T) {
const key = "OTEL_GO_X_OBSERVABILITY"
require.Contains(t, Observability.Keys(), key)

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) {
Expand Down
8 changes: 4 additions & 4 deletions exporters/stdout/stdouttrace/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})

Expand Down
2 changes: 1 addition & 1 deletion sdk/log/instrumentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
8 changes: 4 additions & 4 deletions sdk/log/internal/x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down
Loading
Loading