You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-19 21:45:50 +02:00
Add support for experimental options in the metrics API (#8111)
Part of https://github.com/open-telemetry/opentelemetry-go/issues/8110 Related to https://github.com/open-telemetry/opentelemetry-go/issues/5882. I'm hoping to find a better way to support experimental Options types in our API packages. This is one approach to consider. This contains no public API changes. It introduces a type: `ExperimentalOption` in `/metric/internal/x`, which can be used by our experimental options defined outside of the module. Options that embed this interface are ignored by `New*Config` builder functions in the metrics API to prevent them from panicing when used. Only SDKs that explicitly support the experimental option in question will respect it. Alternative SDKs will ignore the experimental options. We would still need to treat ExperimentalOption as a stable artifact, since the SDK will indirectly depend on it. See https://github.com/open-telemetry/opentelemetry-go/compare/main...dashpole:opentelemetry-go:attributes_advisory for how this would be used to support the advisory attributes parameter.
This commit is contained in:
+27
-1
@@ -1135,13 +1135,39 @@ New telemetry signals (e.g., Logs before stabilization) and components (e.g. bri
|
||||
The package should have the final name it will use once stabilized (i.e. not `/x`), and is released at a v0.x.y version to indicate it is not stable.
|
||||
Most new components are hosted in [opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib).
|
||||
|
||||
#### Experimental options for API or SDK functions
|
||||
|
||||
Experimental Options functions are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
The return type of the Option function must embed the option's type (e.g. `metric.InstrumentOption`), and have an `Experimental()` method to prevent the API from panicking when the option is used.
|
||||
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
|
||||
The SDK must not depend on the experimental module.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
type myOption struct {
|
||||
// Embed the stable option type.
|
||||
metric.InstrumentOption
|
||||
value string
|
||||
}
|
||||
|
||||
// Experimental prevents the API from panicking when the option is used.
|
||||
func (o myOption) Experimental() {}
|
||||
|
||||
// The SDK can use type assertions to use this function.
|
||||
func (o myOption) Value() string { return o.value }
|
||||
|
||||
func WithMyOption(value string) metric.InstrumentOption {
|
||||
return myOption{value: value}
|
||||
}
|
||||
```
|
||||
|
||||
#### Not Supported
|
||||
|
||||
The following kinds of experimental features are **not currently supported** on stable interfaces:
|
||||
|
||||
- Experimental methods on API interfaces
|
||||
- Experimental fields for API or SDK exported structs
|
||||
- Experimental options for API or SDK functions
|
||||
|
||||
In some cases forks or long-lived branches may be used for prototyping these features.
|
||||
|
||||
|
||||
@@ -74,10 +74,17 @@ type LoggerConfig struct {
|
||||
attrs attribute.Set
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewLoggerConfig returns a new [LoggerConfig] with all the options applied.
|
||||
func NewLoggerConfig(options ...LoggerOption) LoggerConfig {
|
||||
var c LoggerConfig
|
||||
for _, opt := range options {
|
||||
if _, ok := opt.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
c = opt.applyLogger(c)
|
||||
}
|
||||
return c
|
||||
|
||||
@@ -196,3 +196,15 @@ func BenchmarkNewLoggerConfig(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testExperimentalOption struct {
|
||||
log.LoggerOption
|
||||
}
|
||||
|
||||
func (testExperimentalOption) Experimental() {}
|
||||
|
||||
func TestExperimentalOptionSafe(t *testing.T) {
|
||||
var opt testExperimentalOption
|
||||
|
||||
assert.NotPanics(t, func() { _ = log.NewLoggerConfig(opt) })
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ type Float64ObservableCounterConfig struct {
|
||||
func NewFloat64ObservableCounterConfig(opts ...Float64ObservableCounterOption) Float64ObservableCounterConfig {
|
||||
var config Float64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -111,6 +114,9 @@ func NewFloat64ObservableUpDownCounterConfig(
|
||||
) Float64ObservableUpDownCounterConfig {
|
||||
var config Float64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -168,6 +174,9 @@ type Float64ObservableGaugeConfig struct {
|
||||
func NewFloat64ObservableGaugeConfig(opts ...Float64ObservableGaugeOption) Float64ObservableGaugeConfig {
|
||||
var config Float64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -50,6 +50,9 @@ type Int64ObservableCounterConfig struct {
|
||||
func NewInt64ObservableCounterConfig(opts ...Int64ObservableCounterOption) Int64ObservableCounterConfig {
|
||||
var config Int64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -110,6 +113,9 @@ func NewInt64ObservableUpDownCounterConfig(
|
||||
) Int64ObservableUpDownCounterConfig {
|
||||
var config Int64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -167,6 +173,9 @@ type Int64ObservableGaugeConfig struct {
|
||||
func NewInt64ObservableGaugeConfig(opts ...Int64ObservableGaugeOption) Int64ObservableGaugeConfig {
|
||||
var config Int64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -42,11 +42,18 @@ type MeterOption interface {
|
||||
applyMeter(MeterConfig) MeterConfig
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewMeterConfig creates a new MeterConfig and applies
|
||||
// all the given options.
|
||||
func NewMeterConfig(opts ...MeterOption) MeterConfig {
|
||||
var config MeterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyMeter(config)
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -197,3 +197,49 @@ func BenchmarkNewMeterConfig(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testExperimentalOption struct {
|
||||
metric.MeterOption
|
||||
metric.Int64CounterOption
|
||||
metric.Int64UpDownCounterOption
|
||||
metric.Int64HistogramOption
|
||||
metric.Int64GaugeOption
|
||||
metric.Float64CounterOption
|
||||
metric.Float64UpDownCounterOption
|
||||
metric.Float64HistogramOption
|
||||
metric.Float64GaugeOption
|
||||
metric.Int64ObservableCounterOption
|
||||
metric.Int64ObservableUpDownCounterOption
|
||||
metric.Int64ObservableGaugeOption
|
||||
metric.Float64ObservableCounterOption
|
||||
metric.Float64ObservableUpDownCounterOption
|
||||
metric.Float64ObservableGaugeOption
|
||||
metric.AddOption
|
||||
metric.RecordOption
|
||||
metric.ObserveOption
|
||||
}
|
||||
|
||||
func (testExperimentalOption) Experimental() {}
|
||||
|
||||
func TestExperimentalOptionSafe(t *testing.T) {
|
||||
var opt testExperimentalOption
|
||||
|
||||
assert.NotPanics(t, func() { _ = metric.NewMeterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewInt64CounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewInt64UpDownCounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewInt64HistogramConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewInt64GaugeConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewFloat64CounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewFloat64UpDownCounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewFloat64HistogramConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewFloat64GaugeConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewInt64ObservableCounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewInt64ObservableUpDownCounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewInt64ObservableGaugeConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewFloat64ObservableCounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewFloat64ObservableUpDownCounterConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewFloat64ObservableGaugeConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewAddConfig([]metric.AddOption{opt}) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewRecordConfig([]metric.RecordOption{opt}) })
|
||||
assert.NotPanics(t, func() { _ = metric.NewObserveConfig([]metric.ObserveOption{opt}) })
|
||||
}
|
||||
|
||||
+12
-1
@@ -3,7 +3,9 @@
|
||||
|
||||
package metric // import "go.opentelemetry.io/otel/metric"
|
||||
|
||||
import "go.opentelemetry.io/otel/attribute"
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
// Observable is used as a grouping mechanism for all instruments that are
|
||||
// updated within a Callback.
|
||||
@@ -228,6 +230,9 @@ type AddConfig struct {
|
||||
func NewAddConfig(opts []AddOption) AddConfig {
|
||||
config := AddConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyAdd(config)
|
||||
}
|
||||
return config
|
||||
@@ -253,6 +258,9 @@ type RecordConfig struct {
|
||||
func NewRecordConfig(opts []RecordOption) RecordConfig {
|
||||
config := RecordConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyRecord(config)
|
||||
}
|
||||
return config
|
||||
@@ -278,6 +286,9 @@ type ObserveConfig struct {
|
||||
func NewObserveConfig(opts []ObserveOption) ObserveConfig {
|
||||
config := ObserveConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyObserve(config)
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -51,6 +51,9 @@ type Float64CounterConfig struct {
|
||||
func NewFloat64CounterConfig(opts ...Float64CounterOption) Float64CounterConfig {
|
||||
var config Float64CounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Counter(config)
|
||||
}
|
||||
return config
|
||||
@@ -116,6 +119,9 @@ type Float64UpDownCounterConfig struct {
|
||||
func NewFloat64UpDownCounterConfig(opts ...Float64UpDownCounterOption) Float64UpDownCounterConfig {
|
||||
var config Float64UpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64UpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -182,6 +188,9 @@ type Float64HistogramConfig struct {
|
||||
func NewFloat64HistogramConfig(opts ...Float64HistogramOption) Float64HistogramConfig {
|
||||
var config Float64HistogramConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Histogram(config)
|
||||
}
|
||||
return config
|
||||
@@ -251,6 +260,9 @@ type Float64GaugeConfig struct {
|
||||
func NewFloat64GaugeConfig(opts ...Float64GaugeOption) Float64GaugeConfig {
|
||||
var config Float64GaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Gauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -51,6 +51,9 @@ type Int64CounterConfig struct {
|
||||
func NewInt64CounterConfig(opts ...Int64CounterOption) Int64CounterConfig {
|
||||
var config Int64CounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Counter(config)
|
||||
}
|
||||
return config
|
||||
@@ -116,6 +119,9 @@ type Int64UpDownCounterConfig struct {
|
||||
func NewInt64UpDownCounterConfig(opts ...Int64UpDownCounterOption) Int64UpDownCounterConfig {
|
||||
var config Int64UpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64UpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -182,6 +188,9 @@ type Int64HistogramConfig struct {
|
||||
func NewInt64HistogramConfig(opts ...Int64HistogramOption) Int64HistogramConfig {
|
||||
var config Int64HistogramConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Histogram(config)
|
||||
}
|
||||
return config
|
||||
@@ -251,6 +260,9 @@ type Int64GaugeConfig struct {
|
||||
func NewInt64GaugeConfig(opts ...Int64GaugeOption) Int64GaugeConfig {
|
||||
var config Int64GaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Gauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
@@ -34,9 +34,16 @@ type providerConfig struct {
|
||||
allowDupKeys setting[bool]
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
func newProviderConfig(opts []LoggerProviderOption) providerConfig {
|
||||
var c providerConfig
|
||||
for _, opt := range opts {
|
||||
if _, ok := opt.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
c = opt.apply(c)
|
||||
}
|
||||
|
||||
|
||||
@@ -419,3 +419,15 @@ func BenchmarkLoggerProviderLogger(b *testing.B) {
|
||||
b.StopTimer()
|
||||
loggers[0].Enabled(b.Context(), log.EnabledParameters{})
|
||||
}
|
||||
|
||||
type testExperimentalOption struct {
|
||||
LoggerProviderOption
|
||||
}
|
||||
|
||||
func (testExperimentalOption) Experimental() {}
|
||||
|
||||
func TestExperimentalOptionSafe(t *testing.T) {
|
||||
var opt testExperimentalOption
|
||||
|
||||
assert.NotPanics(t, func() { _ = NewLoggerProvider(opt) })
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ func unifyShutdown(funcs []func(context.Context) error) func(context.Context) er
|
||||
}
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// newConfig returns a config configured with options.
|
||||
func newConfig(options []Option) config {
|
||||
conf := config{
|
||||
@@ -81,6 +85,9 @@ func newConfig(options []Option) config {
|
||||
conf = o.apply(conf)
|
||||
}
|
||||
for _, o := range options {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
conf = o.apply(conf)
|
||||
}
|
||||
return conf
|
||||
|
||||
@@ -369,3 +369,15 @@ func sample(parent context.Context) context.Context {
|
||||
})
|
||||
return trace.ContextWithSpanContext(parent, sc)
|
||||
}
|
||||
|
||||
type testExperimentalOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (testExperimentalOption) Experimental() {}
|
||||
|
||||
func TestExperimentalOptionSafe(t *testing.T) {
|
||||
var opt testExperimentalOption
|
||||
|
||||
assert.NotPanics(t, func() { _ = newConfig([]Option{opt}) })
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ type TracerProvider struct {
|
||||
|
||||
var _ trace.TracerProvider = &TracerProvider{}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewTracerProvider returns a new and configured TracerProvider.
|
||||
//
|
||||
// By default the returned TracerProvider is configured with:
|
||||
@@ -99,6 +103,9 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
|
||||
o = applyTracerProviderEnvConfigs(o)
|
||||
|
||||
for _, opt := range opts {
|
||||
if _, ok := opt.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
o = opt.apply(o)
|
||||
}
|
||||
|
||||
|
||||
@@ -518,3 +518,15 @@ func (m *errMeter) Int64Counter(string, ...metric.Int64CounterOption) (metric.In
|
||||
func (m *errMeter) Int64UpDownCounter(string, ...metric.Int64UpDownCounterOption) (metric.Int64UpDownCounter, error) {
|
||||
return nil, m.err
|
||||
}
|
||||
|
||||
type testExperimentalOption struct {
|
||||
TracerProviderOption
|
||||
}
|
||||
|
||||
func (testExperimentalOption) Experimental() {}
|
||||
|
||||
func TestExperimentalOptionSafe(t *testing.T) {
|
||||
var opt testExperimentalOption
|
||||
|
||||
assert.NotPanics(t, func() { _ = NewTracerProvider(opt) })
|
||||
}
|
||||
|
||||
@@ -34,10 +34,17 @@ func (t *TracerConfig) SchemaURL() string {
|
||||
return t.schemaURL
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewTracerConfig applies all the options to a returned TracerConfig.
|
||||
func NewTracerConfig(options ...TracerOption) TracerConfig {
|
||||
var config TracerConfig
|
||||
for _, option := range options {
|
||||
if _, ok := option.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = option.apply(config)
|
||||
}
|
||||
return config
|
||||
@@ -103,6 +110,9 @@ func (cfg *SpanConfig) SpanKind() SpanKind {
|
||||
func NewSpanStartConfig(options ...SpanStartOption) SpanConfig {
|
||||
var c SpanConfig
|
||||
for _, option := range options {
|
||||
if _, ok := option.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
c = option.applySpanStart(c)
|
||||
}
|
||||
return c
|
||||
@@ -115,6 +125,9 @@ func NewSpanStartConfig(options ...SpanStartOption) SpanConfig {
|
||||
func NewSpanEndConfig(options ...SpanEndOption) SpanConfig {
|
||||
var c SpanConfig
|
||||
for _, option := range options {
|
||||
if _, ok := option.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
c = option.applySpanEnd(c)
|
||||
}
|
||||
return c
|
||||
@@ -167,6 +180,9 @@ func (cfg *EventConfig) StackTrace() bool {
|
||||
func NewEventConfig(options ...EventOption) EventConfig {
|
||||
var c EventConfig
|
||||
for _, option := range options {
|
||||
if _, ok := option.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
c = option.applyEvent(c)
|
||||
}
|
||||
if c.timestamp.IsZero() {
|
||||
|
||||
@@ -544,3 +544,21 @@ func TestWithInstrumentationAttributesMerge(t *testing.T) {
|
||||
"Attributes and attribute sets should be merged together.")
|
||||
})
|
||||
}
|
||||
|
||||
type testExperimentalOption struct {
|
||||
TracerOption
|
||||
SpanStartOption
|
||||
SpanEndOption
|
||||
EventOption
|
||||
}
|
||||
|
||||
func (testExperimentalOption) Experimental() {}
|
||||
|
||||
func TestExperimentalOptionSafe(t *testing.T) {
|
||||
var opt testExperimentalOption
|
||||
|
||||
assert.NotPanics(t, func() { _ = NewTracerConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = NewSpanStartConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = NewSpanEndConfig(opt) })
|
||||
assert.NotPanics(t, func() { _ = NewEventConfig(opt) })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user