The embedded package provides interfaces that help implementation authors safely handle the OpenTelemetry Trace API's non-standard evolution policy. This package is crucial for anyone implementing custom TracerProvider, Tracer, or Span types.
import "go.opentelemetry.io/otel/trace/embedded"The OpenTelemetry Trace API does not conform to standard Go versioning:
Without embedded types, adding a method to an interface would silently break existing implementations:
// OpenTelemetry adds a new method in v1.20.0
type Tracer interface {
Start(ctx context.Context, name string, opts ...SpanStartOption) (context.Context, Span)
// NEW in v1.20.0
NewMethod() string
}
// User's implementation from v1.19.0
type MyTracer struct {}
func (t *MyTracer) Start(...) {...}
// Missing NewMethod() - compiles but will panic at runtime!The embedded package provides interfaces that force implementation authors to make an explicit choice about handling API evolution.
Embed the corresponding embedded interface to get compilation errors when the API evolves:
import "go.opentelemetry.io/otel/trace/embedded"
type MyTracerProvider struct {
embedded.TracerProvider // Embed this
// your fields
}
func (tp *MyTracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
// your implementation
}Benefits:
When OpenTelemetry adds a method: Your code won't compile, signaling you need to update.
Embed the API interface directly to panic on unimplemented methods:
import "go.opentelemetry.io/otel/trace"
type MyTracerProvider struct {
trace.TracerProvider // Embed API interface directly
// your fields
}
func (tp *MyTracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
// your implementation
}Problems:
Not recommended - leads to runtime failures.
Embed a no-op implementation to silently drop calls to unimplemented methods:
import "go.opentelemetry.io/otel/trace/noop"
type MyTracerProvider struct {
noop.TracerProvider // Embed no-op implementation
// your fields
}
func (tp *MyTracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
// your implementation
}Use case:
Caution: Only embed noop types from go.opentelemetry.io/otel/trace/noop as they're guaranteed to implement all future API changes.
type TracerProvider interface {
// contains unexported methods
}Embed this in your TracerProvider implementations:
type CustomTracerProvider struct {
embedded.TracerProvider
config *Config
}
func (ctp *CustomTracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
// Implementation
return &CustomTracer{name: name}
}type Tracer interface {
// contains unexported methods
}Embed this in your Tracer implementations:
type CustomTracer struct {
embedded.Tracer
name string
provider *CustomTracerProvider
}
func (ct *CustomTracer) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
// Implementation
span := &CustomSpan{name: spanName}
return trace.ContextWithSpan(ctx, span), span
}type Span interface {
// contains unexported methods
}Embed this in your Span implementations:
type CustomSpan struct {
embedded.Span
name string
spanContext trace.SpanContext
}
func (cs *CustomSpan) End(options ...trace.SpanEndOption) {
// Implementation
}
func (cs *CustomSpan) SetAttributes(kv ...attribute.KeyValue) {
// Implementation
}
// ... implement other Span methodspackage customtracer
import (
"context"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
// CustomTracerProvider implements trace.TracerProvider
type CustomTracerProvider struct {
embedded.TracerProvider // Compilation failure on API changes
mu sync.Mutex
tracers map[string]*CustomTracer
}
func NewCustomTracerProvider() *CustomTracerProvider {
return &CustomTracerProvider{
tracers: make(map[string]*CustomTracer),
}
}
func (ctp *CustomTracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
ctp.mu.Lock()
defer ctp.mu.Unlock()
if tracer, ok := ctp.tracers[name]; ok {
return tracer
}
tracer := &CustomTracer{
name: name,
provider: ctp,
}
ctp.tracers[name] = tracer
return tracer
}
// CustomTracer implements trace.Tracer
type CustomTracer struct {
embedded.Tracer // Compilation failure on API changes
name string
provider *CustomTracerProvider
}
func (ct *CustomTracer) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
// Parse options
config := trace.NewSpanStartConfig(opts...)
// Create span
span := &CustomSpan{
name: spanName,
spanContext: trace.SpanContext{}, // Would normally generate IDs
attributes: config.Attributes(),
}
// Return context with span
ctx = trace.ContextWithSpan(ctx, span)
return ctx, span
}
// CustomSpan implements trace.Span
type CustomSpan struct {
embedded.Span // Compilation failure on API changes
name string
spanContext trace.SpanContext
attributes []attribute.KeyValue
mu sync.Mutex
}
func (cs *CustomSpan) End(options ...trace.SpanEndOption) {
cs.mu.Lock()
defer cs.mu.Unlock()
// End implementation
}
func (cs *CustomSpan) AddEvent(name string, options ...trace.EventOption) {
cs.mu.Lock()
defer cs.mu.Unlock()
// AddEvent implementation
}
func (cs *CustomSpan) AddLink(link trace.Link) {
cs.mu.Lock()
defer cs.mu.Unlock()
// AddLink implementation
}
func (cs *CustomSpan) IsRecording() bool {
return true
}
func (cs *CustomSpan) RecordError(err error, options ...trace.EventOption) {
cs.mu.Lock()
defer cs.mu.Unlock()
// RecordError implementation
}
func (cs *CustomSpan) SpanContext() trace.SpanContext {
return cs.spanContext
}
func (cs *CustomSpan) SetStatus(code codes.Code, description string) {
cs.mu.Lock()
defer cs.mu.Unlock()
// SetStatus implementation
}
func (cs *CustomSpan) SetName(name string) {
cs.mu.Lock()
defer cs.mu.Unlock()
cs.name = name
}
func (cs *CustomSpan) SetAttributes(kv ...attribute.KeyValue) {
cs.mu.Lock()
defer cs.mu.Unlock()
cs.attributes = append(cs.attributes, kv...)
}
func (cs *CustomSpan) TracerProvider() trace.TracerProvider {
// Return the provider (would need to store reference)
return nil
}When OpenTelemetry adds a new method to an interface:
$ go build
./tracer.go:15:6: cannot use &CustomTracerProvider{...} (type *CustomTracerProvider) as type trace.TracerProvider in return argument:
*CustomTracerProvider does not implement trace.TracerProvider (missing NewMethod method)## v1.20.0
### Breaking Changes for Implementers
- Added `NewMethod()` to `TracerProvider` interface
- Implementations must provide this method or update embedded typefunc (ctp *CustomTracerProvider) NewMethod() string {
// Implement new functionality
return "implemented"
}$ go build
Build successfulAlways embed embedded types in production implementations
type MyTracer struct {
embedded.Tracer // Required
// ...
}Monitor OpenTelemetry releases for API changes
Document your choice in implementation comments
// MyTracerProvider implements trace.TracerProvider.
// It embeds embedded.TracerProvider to ensure compilation failures
// when the OpenTelemetry API is extended, preventing runtime panics.
type MyTracerProvider struct {
embedded.TracerProvider
// ...
}Only use noop embedding from official package
import "go.opentelemetry.io/otel/trace/noop"
type PartialTracer struct {
noop.Tracer // Safe - maintained by OpenTelemetry
// ...
}Implement all interface methods - don't rely on embedded defaults
// Bad: relies on embedded methods
type MySpan struct {
embedded.Span
}
// Good: implements all methods explicitly
type MySpan struct {
embedded.Span
}
func (s *MySpan) End(...) { /* implementation */ }
func (s *MySpan) AddEvent(...) { /* implementation */ }
// ... all other methods// WRONG: Will panic when API evolves
type MyTracer struct {
// no embedded type
}// WRONG: Will panic on unimplemented methods
type MyTracer struct {
trace.Tracer // Embeds API interface directly
}// WRONG: Custom embedded types don't track API evolution
type myEmbeddedTracer interface {
// unexported methods
}
type MyTracer struct {
myEmbeddedTracer
}// CORRECT: Uses official embedded type
import "go.opentelemetry.io/otel/trace/embedded"
type MyTracer struct {
embedded.Tracer
}SDK Implementations (libraries providing TracerProvider):
embedded typesApplication Code (using TracerProvider):
// SDK code - must embed
type SDKTracerProvider struct {
embedded.TracerProvider // Required
}
// Application code - just uses interfaces
func main() {
tp := sdk.NewTracerProvider() // No embedding needed
tracer := tp.Tracer("app")
// ...
}go doc go.opentelemetry.io/otel/trace/embedded