1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-07-17 01:12:45 +02:00

Add embedded package to trace API (#4620)

* Add trace/embedded

* Update trace impl to use trace/embedded

* Add noop pkg to replace no-op impl in trace pkg

* Use trace/embedded in global impl

* Use trace/embedded in SDK impl

* Update opencensus bridge

* Update opentracing bridge

* Add changes to changelog

* Update trace/doc.go

Co-authored-by: David Ashpole <dashpole@google.com>

---------

Co-authored-by: David Ashpole <dashpole@google.com>
This commit is contained in:
Tyler Yahn
2023-10-19 10:16:24 -07:00
committed by GitHub
parent da343ab9c5
commit 1e1cc901a5
21 changed files with 480 additions and 35 deletions

View File

@ -12,16 +12,32 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `go.opentelemetry.io/otel/bridge/opencensus.InstallTraceBridge`, which installs the OpenCensus trace bridge, and replaces `opencensus.NewTracer`. (#4567)
- Add scope version to trace and metric bridges in `go.opentelemetry.io/otel/bridge/opencensus`. (#4584)
- Add the `go.opentelemetry.io/otel/trace/embedded` package to be embedded in the exported trace API interfaces. (#4620)
- Add the `go.opentelemetry.io/otel/trace/noop` package as a default no-op implementation of the trace API. (#4620)
- Add context propagation in `go.opentelemetry.io/otel/example/dice`. (#4644)
### Deprecated
- Deprecate `go.opentelemetry.io/otel/bridge/opencensus.NewTracer` in favor of `opencensus.InstallTraceBridge`. (#4567)
- Deprecate `go.opentelemetry.io/otel/example/fib` package is in favor of `go.opentelemetry.io/otel/example/dice`. (#4618)
- Deprecate `go.opentelemetry.io/otel/trace.NewNoopTracerProvider`.
Use the added `NewTracerProvider` function in `go.opentelemetry.io/otel/trace/noop` instead. (#4620)
### Changed
- `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` returns a `*MetricProducer` struct instead of the metric.Producer interface. (#4583)
- The `TracerProvider` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.TracerProvider` type.
This extends the `TracerProvider` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more informatoin about how to accomplish this. (#4620)
- The `Tracer` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Tracer` type.
This extends the `Tracer` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more informatoin about how to accomplish this. (#4620)
- The `Span` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Span` type.
This extends the `Span` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more informatoin about how to accomplish this. (#4620)
## [1.19.0/0.42.0/0.0.7] 2023-09-28

View File

@ -20,12 +20,12 @@ import (
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
func TestNewTraceConfig(t *testing.T) {
globalTP := trace.NewNoopTracerProvider()
customTP := trace.NewNoopTracerProvider()
globalTP := noop.NewTracerProvider()
customTP := noop.NewTracerProvider()
otel.SetTracerProvider(globalTP)
for _, tc := range []struct {
desc string

View File

@ -24,6 +24,8 @@ import (
"go.opentelemetry.io/otel/bridge/opencensus/internal/oc2otel"
"go.opentelemetry.io/otel/bridge/opencensus/internal/otel2oc"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/noop"
)
type handler struct{ err error }
@ -38,6 +40,8 @@ func withHandler() (*handler, func()) {
}
type tracer struct {
embedded.Tracer
ctx context.Context
name string
opts []trace.SpanStartOption
@ -45,8 +49,8 @@ type tracer struct {
func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
t.ctx, t.name, t.opts = ctx, name, opts
noop := trace.NewNoopTracerProvider().Tracer("testing")
return noop.Start(ctx, name, opts...)
sub := noop.NewTracerProvider().Tracer("testing")
return sub.Start(ctx, name, opts...)
}
type ctxKey string
@ -110,11 +114,11 @@ func TestTracerFromContext(t *testing.T) {
})
ctx := trace.ContextWithSpanContext(context.Background(), sc)
noop := trace.NewNoopTracerProvider().Tracer("TestTracerFromContext")
tracer := noop.NewTracerProvider().Tracer("TestTracerFromContext")
// Test using the fact that the No-Op span will propagate a span context .
ctx, _ = noop.Start(ctx, "test")
ctx, _ = tracer.Start(ctx, "test")
got := internal.NewTracer(noop).FromContext(ctx).SpanContext()
got := internal.NewTracer(tracer).FromContext(ctx).SpanContext()
// Do not test the convedsion, only that the propagtion.
want := otel2oc.SpanContext(sc)
if got != want {
@ -129,11 +133,11 @@ func TestTracerNewContext(t *testing.T) {
})
ctx := trace.ContextWithSpanContext(context.Background(), sc)
noop := trace.NewNoopTracerProvider().Tracer("TestTracerNewContext")
tracer := noop.NewTracerProvider().Tracer("TestTracerNewContext")
// Test using the fact that the No-Op span will propagate a span context .
_, s := noop.Start(ctx, "test")
_, s := tracer.Start(ctx, "test")
ocTracer := internal.NewTracer(noop)
ocTracer := internal.NewTracer(tracer)
ctx = ocTracer.NewContext(context.Background(), internal.NewSpan(s))
got := trace.SpanContextFromContext(ctx)

View File

@ -33,10 +33,11 @@ import (
iBaggage "go.opentelemetry.io/otel/internal/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
var (
noopTracer = trace.NewNoopTracerProvider().Tracer("")
noopTracer = noop.NewTracerProvider().Tracer("")
noopSpan = func() trace.Span {
_, s := noopTracer.Start(context.Background(), "")
return s

View File

@ -26,6 +26,8 @@ import (
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/noop"
)
//nolint:revive // ignoring missing comments for unexported global variables in an internal package.
@ -44,6 +46,8 @@ type MockContextKeyValue struct {
}
type MockTracer struct {
embedded.Tracer
FinishedSpans []*MockSpan
SpareTraceIDs []trace.TraceID
SpareSpanIDs []trace.SpanID
@ -184,6 +188,8 @@ type MockEvent struct {
}
type MockSpan struct {
embedded.Span
mockTracer *MockTracer
officialTracer trace.Tracer
spanContext trace.SpanContext
@ -295,4 +301,4 @@ func (s *MockSpan) OverrideTracer(tracer trace.Tracer) {
s.officialTracer = tracer
}
func (s *MockSpan) TracerProvider() trace.TracerProvider { return trace.NewNoopTracerProvider() }
func (s *MockSpan) TracerProvider() trace.TracerProvider { return noop.NewTracerProvider() }

View File

@ -18,11 +18,14 @@ import (
"sync"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
// TracerProvider is an OpenTelemetry TracerProvider that wraps an OpenTracing
// Tracer.
type TracerProvider struct {
embedded.TracerProvider
bridge *BridgeTracer
provider trace.TracerProvider

View File

@ -19,6 +19,7 @@ import (
"go.opentelemetry.io/otel/bridge/opentracing/internal"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
type namedMockTracer struct {
@ -26,7 +27,7 @@ type namedMockTracer struct {
*internal.MockTracer
}
type namedMockTracerProvider struct{}
type namedMockTracerProvider struct{ embedded.TracerProvider }
var _ trace.TracerProvider = (*namedMockTracerProvider)(nil)

View File

@ -19,6 +19,7 @@ import (
"go.opentelemetry.io/otel/bridge/opentracing/migration"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
// WrapperTracerProvider is an OpenTelemetry TracerProvider that wraps an
@ -26,6 +27,8 @@ import (
//
// Deprecated: Use the TracerProvider from NewTracerProvider(...) instead.
type WrapperTracerProvider struct {
embedded.TracerProvider
wTracer *WrapperTracer
}
@ -56,6 +59,8 @@ func NewWrappedTracerProvider(bridge *BridgeTracer, tracer trace.Tracer) *Wrappe
// aware how to operate in environment where OpenTracing API is also
// used.
type WrapperTracer struct {
embedded.Tracer
bridge *BridgeTracer
tracer trace.Tracer
}

View File

@ -20,9 +20,10 @@ import (
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
metricnoop "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
tracenoop "go.opentelemetry.io/otel/trace/noop"
)
type nonComparableTracerProvider struct {
@ -55,7 +56,7 @@ func TestSetTracerProvider(t *testing.T) {
t.Run("First Set() should replace the delegate", func(t *testing.T) {
ResetForTest(t)
SetTracerProvider(trace.NewNoopTracerProvider())
SetTracerProvider(tracenoop.NewTracerProvider())
_, ok := TracerProvider().(*tracerProvider)
if ok {
@ -67,7 +68,7 @@ func TestSetTracerProvider(t *testing.T) {
ResetForTest(t)
tp := TracerProvider()
SetTracerProvider(trace.NewNoopTracerProvider())
SetTracerProvider(tracenoop.NewTracerProvider())
ntp := tp.(*tracerProvider)
@ -153,7 +154,7 @@ func TestSetMeterProvider(t *testing.T) {
t.Run("First Set() should replace the delegate", func(t *testing.T) {
ResetForTest(t)
SetMeterProvider(noop.NewMeterProvider())
SetMeterProvider(metricnoop.NewMeterProvider())
_, ok := MeterProvider().(*meterProvider)
if ok {
@ -166,7 +167,7 @@ func TestSetMeterProvider(t *testing.T) {
mp := MeterProvider()
SetMeterProvider(noop.NewMeterProvider())
SetMeterProvider(metricnoop.NewMeterProvider())
dmp := mp.(*meterProvider)

View File

@ -39,6 +39,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
// tracerProvider is a placeholder for a configured SDK TracerProvider.
@ -46,6 +47,8 @@ import (
// All TracerProvider functionality is forwarded to a delegate once
// configured.
type tracerProvider struct {
embedded.TracerProvider
mtx sync.Mutex
tracers map[il]*tracer
delegate trace.TracerProvider
@ -119,6 +122,8 @@ type il struct {
// All Tracer functionality is forwarded to a delegate once configured.
// Otherwise, all functionality is forwarded to a NoopTracer.
type tracer struct {
embedded.Tracer
name string
opts []trace.TracerOption
provider *tracerProvider
@ -156,6 +161,8 @@ func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStart
// SpanContext. It performs no operations other than to return the wrapped
// SpanContext.
type nonRecordingSpan struct {
embedded.Span
sc trace.SpanContext
tracer *tracer
}

View File

@ -23,9 +23,13 @@ import (
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/noop"
)
type fnTracerProvider struct {
embedded.TracerProvider
tracer func(string, ...trace.TracerOption) trace.Tracer
}
@ -34,6 +38,8 @@ func (fn fnTracerProvider) Tracer(instrumentationName string, opts ...trace.Trac
}
type fnTracer struct {
embedded.Tracer
start func(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
}
@ -72,7 +78,7 @@ func TestTraceProviderDelegation(t *testing.T) {
assert.Equal(t, want, spanName)
}
}
return trace.NewNoopTracerProvider().Tracer(name).Start(ctx, spanName)
return noop.NewTracerProvider().Tracer(name).Start(ctx, spanName)
},
}
},
@ -107,7 +113,7 @@ func TestTraceProviderDelegates(t *testing.T) {
tracer: func(name string, opts ...trace.TracerOption) trace.Tracer {
called = true
assert.Equal(t, "abc", name)
return trace.NewNoopTracerProvider().Tracer("")
return noop.NewTracerProvider().Tracer("")
},
})
@ -148,7 +154,7 @@ func TestTraceProviderDelegatesConcurrentSafe(t *testing.T) {
// Signal the goroutine to finish.
close(quit)
}
return trace.NewNoopTracerProvider().Tracer("")
return noop.NewTracerProvider().Tracer("")
},
})
@ -195,7 +201,7 @@ func TestTracerDelegatesConcurrentSafe(t *testing.T) {
// Signal the goroutine to finish.
close(quit)
}
return trace.NewNoopTracerProvider().Tracer("").Start(ctx, spanName)
return noop.NewTracerProvider().Tracer("").Start(ctx, spanName)
},
}
},
@ -218,7 +224,7 @@ func TestTraceProviderDelegatesSameInstance(t *testing.T) {
SetTracerProvider(fnTracerProvider{
tracer: func(name string, opts ...trace.TracerOption) trace.Tracer {
return trace.NewNoopTracerProvider().Tracer("")
return noop.NewTracerProvider().Tracer("")
},
})

View File

@ -25,6 +25,8 @@ import (
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/noop"
)
const (
@ -73,6 +75,8 @@ func (cfg tracerProviderConfig) MarshalLog() interface{} {
// TracerProvider is an OpenTelemetry TracerProvider. It provides Tracers to
// instrumentation so it can trace operational flow through a system.
type TracerProvider struct {
embedded.TracerProvider
mu sync.Mutex
namedTracer map[instrumentation.Scope]*tracer
spanProcessors atomic.Pointer[spanProcessorStates]
@ -139,7 +143,7 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
// This check happens before the mutex is acquired to avoid deadlocking if Tracer() is called from within Shutdown().
if p.isShutdown.Load() {
return trace.NewNoopTracerProvider().Tracer(name, opts...)
return noop.NewTracerProvider().Tracer(name, opts...)
}
c := trace.NewTracerConfig(opts...)
if name == "" {
@ -157,7 +161,7 @@ func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T
// Must check the flag after acquiring the mutex to avoid returning a valid tracer if Shutdown() ran
// after the first check above but before we acquired the mutex.
if p.isShutdown.Load() {
return trace.NewNoopTracerProvider().Tracer(name, opts...), true
return noop.NewTracerProvider().Tracer(name, opts...), true
}
t, ok := p.namedTracer[is]
if !ok {

View File

@ -32,6 +32,7 @@ import (
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
// ReadOnlySpan allows reading information from the data structure underlying a
@ -108,6 +109,8 @@ type ReadWriteSpan interface {
// recordingSpan is an implementation of the OpenTelemetry Span API
// representing the individual component of a trace that is sampled.
type recordingSpan struct {
embedded.Span
// mu protects the contents of this span.
mu sync.Mutex
@ -774,6 +777,8 @@ func (s *recordingSpan) runtimeTrace(ctx context.Context) context.Context {
// that wraps a SpanContext. It performs no operations other than to return
// the wrapped SpanContext or TracerProvider that created it.
type nonRecordingSpan struct {
embedded.Span
// tracer is the SDK tracer that created this span.
tracer *tracer
sc trace.SpanContext

View File

@ -20,9 +20,12 @@ import (
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
type tracer struct {
embedded.Tracer
provider *TracerProvider
instrumentationScope instrumentation.Scope
}

View File

@ -62,5 +62,69 @@ a default.
defer span.End()
// ...
}
# API Implementations
This package does not conform to the standard Go versioning policy; all of its
interfaces may have methods added to them without a package major version bump.
This non-standard API evolution could surprise an uninformed implementation
author. They could unknowingly build their implementation in a way that would
result in a runtime panic for their users that update to the new API.
The API is designed to help inform an instrumentation author about this
non-standard API evolution. It requires them to choose a default behavior for
unimplemented interface methods. There are three behavior choices they can
make:
- Compilation failure
- Panic
- Default to another implementation
All interfaces in this API embed a corresponding interface from
[go.opentelemetry.io/otel/trace/embedded]. If an author wants the default
behavior of their implementations to be a compilation failure, signaling to
their users they need to update to the latest version of that implementation,
they need to embed the corresponding interface from
[go.opentelemetry.io/otel/trace/embedded] in their implementation. For
example,
import "go.opentelemetry.io/otel/trace/embedded"
type TracerProvider struct {
embedded.TracerProvider
// ...
}
If an author wants the default behavior of their implementations to panic, they
can embed the API interface directly.
import "go.opentelemetry.io/otel/trace"
type TracerProvider struct {
trace.TracerProvider
// ...
}
This option is not recommended. It will lead to publishing packages that
contain runtime panics when users update to newer versions of
[go.opentelemetry.io/otel/trace], which may be done with a trasitive
dependency.
Finally, an author can embed another implementation in theirs. The embedded
implementation will be used for methods not defined by the author. For example,
an author who wants to default to silently dropping the call can use
[go.opentelemetry.io/otel/trace/noop]:
import "go.opentelemetry.io/otel/trace/noop"
type TracerProvider struct {
noop.TracerProvider
// ...
}
It is strongly recommended that authors only embed
[go.opentelemetry.io/otel/trace/noop] if they choose this default behavior.
That implementation is the only one OpenTelemetry authors can guarantee will
fully implement all the API interfaces when a user updates their API.
*/
package trace // import "go.opentelemetry.io/otel/trace"

View File

@ -0,0 +1,56 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package embedded provides interfaces embedded within the [OpenTelemetry
// trace API].
//
// Implementers of the [OpenTelemetry trace API] can embed the relevant type
// from this package into their implementation directly. Doing so will result
// in a compilation error for users when the [OpenTelemetry trace API] is
// extended (which is something that can happen without a major version bump of
// the API package).
//
// [OpenTelemetry trace API]: https://pkg.go.dev/go.opentelemetry.io/otel/trace
package embedded // import "go.opentelemetry.io/otel/trace/embedded"
// TracerProvider is embedded in
// [go.opentelemetry.io/otel/trace.TracerProvider].
//
// Embed this interface in your implementation of the
// [go.opentelemetry.io/otel/trace.TracerProvider] if you want users to
// experience a compilation error, signaling they need to update to your latest
// implementation, when the [go.opentelemetry.io/otel/trace.TracerProvider]
// interface is extended (which is something that can happen without a major
// version bump of the API package).
type TracerProvider interface{ tracerProvider() }
// Tracer is embedded in [go.opentelemetry.io/otel/trace.Tracer].
//
// Embed this interface in your implementation of the
// [go.opentelemetry.io/otel/trace.Tracer] if you want users to experience a
// compilation error, signaling they need to update to your latest
// implementation, when the [go.opentelemetry.io/otel/trace.Tracer] interface
// is extended (which is something that can happen without a major version bump
// of the API package).
type Tracer interface{ tracer() }
// Span is embedded in [go.opentelemetry.io/otel/trace.Span].
//
// Embed this interface in your implementation of the
// [go.opentelemetry.io/otel/trace.Span] if you want users to experience a
// compilation error, signaling they need to update to your latest
// implementation, when the [go.opentelemetry.io/otel/trace.Span] interface is
// extended (which is something that can happen without a major version bump of
// the API package).
type Span interface{ span() }

View File

@ -19,16 +19,20 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace/embedded"
)
// NewNoopTracerProvider returns an implementation of TracerProvider that
// performs no operations. The Tracer and Spans created from the returned
// TracerProvider also perform no operations.
//
// Deprecated: Use [go.opentelemetry.io/otel/trace/noop.NewTracerProvider]
// instead.
func NewNoopTracerProvider() TracerProvider {
return noopTracerProvider{}
}
type noopTracerProvider struct{}
type noopTracerProvider struct{ embedded.TracerProvider }
var _ TracerProvider = noopTracerProvider{}
@ -38,7 +42,7 @@ func (p noopTracerProvider) Tracer(string, ...TracerOption) Tracer {
}
// noopTracer is an implementation of Tracer that performs no operations.
type noopTracer struct{}
type noopTracer struct{ embedded.Tracer }
var _ Tracer = noopTracer{}
@ -54,7 +58,7 @@ func (t noopTracer) Start(ctx context.Context, name string, _ ...SpanStartOption
}
// noopSpan is an implementation of Span that performs no operations.
type noopSpan struct{}
type noopSpan struct{ embedded.Span }
var _ Span = noopSpan{}

118
trace/noop/noop.go Normal file
View File

@ -0,0 +1,118 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package noop provides an implementation of the OpenTelemetry trace API that
// produces no telemetry and minimizes used computation resources.
//
// Using this package to implement the OpenTelemetry trace API will effectively
// disable OpenTelemetry.
//
// This implementation can be embedded in other implementations of the
// OpenTelemetry trace API. Doing so will mean the implementation defaults to
// no operation for methods it does not implement.
package noop // import "go.opentelemetry.io/otel/trace/noop"
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
var (
// Compile-time check this implements the OpenTelemetry API.
_ trace.TracerProvider = TracerProvider{}
_ trace.Tracer = Tracer{}
_ trace.Span = Span{}
)
// TracerProvider is an OpenTelemetry No-Op TracerProvider.
type TracerProvider struct{ embedded.TracerProvider }
// NewTracerProvider returns a TracerProvider that does not record any telemetry.
func NewTracerProvider() TracerProvider {
return TracerProvider{}
}
// Tracer returns an OpenTelemetry Tracer that does not record any telemetry.
func (TracerProvider) Tracer(string, ...trace.TracerOption) trace.Tracer {
return Tracer{}
}
// Tracer is an OpenTelemetry No-Op Tracer.
type Tracer struct{ embedded.Tracer }
// Start creates a span. The created span will be set in a child context of ctx
// and returned with the span.
//
// If ctx contains a span context, the returned span will also contain that
// span context. If the span context in ctx is for a non-recording span, that
// span instance will be returned directly.
func (t Tracer) Start(ctx context.Context, _ string, _ ...trace.SpanStartOption) (context.Context, trace.Span) {
span := trace.SpanFromContext(ctx)
// If the parent context contains a non-zero span context, that span
// context needs to be returned as a non-recording span
// (https://github.com/open-telemetry/opentelemetry-specification/blob/3a1dde966a4ce87cce5adf464359fe369741bbea/specification/trace/api.md#behavior-of-the-api-in-the-absence-of-an-installed-sdk).
var zeroSC trace.SpanContext
if sc := span.SpanContext(); !sc.Equal(zeroSC) {
if !span.IsRecording() {
// If the span is not recording return it directly.
return ctx, span
}
// Otherwise, return the span context needs in a non-recording span.
span = Span{sc: sc}
} else {
// No parent, return a No-Op span with an empty span context.
span = Span{}
}
return trace.ContextWithSpan(ctx, span), span
}
// Span is an OpenTelemetry No-Op Span.
type Span struct {
embedded.Span
sc trace.SpanContext
}
// SpanContext returns an empty span context.
func (s Span) SpanContext() trace.SpanContext { return s.sc }
// IsRecording always returns false.
func (Span) IsRecording() bool { return false }
// SetStatus does nothing.
func (Span) SetStatus(codes.Code, string) {}
// SetAttributes does nothing.
func (Span) SetAttributes(...attribute.KeyValue) {}
// End does nothing.
func (Span) End(...trace.SpanEndOption) {}
// RecordError does nothing.
func (Span) RecordError(error, ...trace.EventOption) {}
// AddEvent does nothing.
func (Span) AddEvent(string, ...trace.EventOption) {}
// SetName does nothing.
func (Span) SetName(string) {}
// TracerProvider returns a No-Op TracerProvider.
func (Span) TracerProvider() trace.TracerProvider { return TracerProvider{} }

117
trace/noop/noop_test.go Normal file
View File

@ -0,0 +1,117 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package noop // import "go.opentelemetry.io/otel/trace/noop"
import (
"context"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/trace"
)
func TestImplementationNoPanics(t *testing.T) {
// Check that if type has an embedded interface and that interface has
// methods added to it than the No-Op implementation implements them.
t.Run("TracerProvider", assertAllExportedMethodNoPanic(
reflect.ValueOf(TracerProvider{}),
reflect.TypeOf((*trace.TracerProvider)(nil)).Elem(),
))
t.Run("Meter", assertAllExportedMethodNoPanic(
reflect.ValueOf(Tracer{}),
reflect.TypeOf((*trace.Tracer)(nil)).Elem(),
))
t.Run("Span", assertAllExportedMethodNoPanic(
reflect.ValueOf(Span{}),
reflect.TypeOf((*trace.Span)(nil)).Elem(),
))
}
func assertAllExportedMethodNoPanic(rVal reflect.Value, rType reflect.Type) func(*testing.T) {
return func(t *testing.T) {
for n := 0; n < rType.NumMethod(); n++ {
mType := rType.Method(n)
if !mType.IsExported() {
t.Logf("ignoring unexported %s", mType.Name)
continue
}
m := rVal.MethodByName(mType.Name)
if !m.IsValid() {
t.Errorf("unknown method for %s: %s", rVal.Type().Name(), mType.Name)
}
numIn := mType.Type.NumIn()
if mType.Type.IsVariadic() {
numIn--
}
args := make([]reflect.Value, numIn)
ctx := context.Background()
for i := range args {
aType := mType.Type.In(i)
if aType.Name() == "Context" {
// Do not panic on a nil context.
args[i] = reflect.ValueOf(ctx)
} else {
args[i] = reflect.New(aType).Elem()
}
}
assert.NotPanicsf(t, func() {
_ = m.Call(args)
}, "%s.%s", rVal.Type().Name(), mType.Name)
}
}
}
func TestNewTracerProvider(t *testing.T) {
tp := NewTracerProvider()
assert.Equal(t, tp, TracerProvider{})
tracer := tp.Tracer("")
assert.Equal(t, tracer, Tracer{})
}
func TestTracerStartPropagatesSpanContext(t *testing.T) {
tracer := NewTracerProvider().Tracer("")
spanCtx := trace.SpanContext{}
ctx := trace.ContextWithSpanContext(context.Background(), spanCtx)
ctx, span := tracer.Start(ctx, "test_span")
assert.Equal(t, spanCtx, trace.SpanContextFromContext(ctx), "empty span context not set in context")
assert.IsType(t, Span{}, span, "non-noop span returned")
assert.Equal(t, spanCtx, span.SpanContext(), "empty span context not returned from span")
assert.False(t, span.IsRecording(), "empty span context returned recording span")
spanCtx = spanCtx.WithTraceID(trace.TraceID([16]byte{1}))
spanCtx = spanCtx.WithSpanID(trace.SpanID([8]byte{1}))
ctx = trace.ContextWithSpanContext(context.Background(), spanCtx)
ctx, span = tracer.Start(ctx, "test_span")
assert.Equal(t, spanCtx, trace.SpanContextFromContext(ctx), "non-empty span context not set in context")
assert.Equal(t, spanCtx, span.SpanContext(), "non-empty span context not returned from span")
assert.False(t, span.IsRecording(), "non-empty span context returned recording span")
rSpan := recordingSpan{Span: Span{sc: spanCtx}}
ctx = trace.ContextWithSpan(context.Background(), rSpan)
ctx, span = tracer.Start(ctx, "test_span")
assert.Equal(t, spanCtx, trace.SpanContextFromContext(ctx), "recording span's span context not set in context")
assert.IsType(t, Span{}, span, "non-noop span returned")
assert.Equal(t, spanCtx, span.SpanContext(), "recording span's span context not returned from span")
assert.False(t, span.IsRecording(), "recording span returned")
}
type recordingSpan struct{ Span }
func (recordingSpan) IsRecording() bool { return true }

View File

@ -22,6 +22,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace/embedded"
)
const (
@ -342,8 +343,15 @@ func (sc SpanContext) MarshalJSON() ([]byte, error) {
// create a Span and it is then up to the operation the Span represents to
// properly end the Span when the operation itself ends.
//
// Warning: methods may be added to this interface in minor releases.
// Warning: Methods may be added to this interface in minor releases. See
// package documentation on API implementation for information on how to set
// default behavior for unimplemented methods.
type Span interface {
// Users of the interface can ignore this. This embedded type is only used
// by implementations of this interface. See the "API Implementations"
// section of the package documentation for more information.
embedded.Span
// End completes the Span. The Span is considered complete and ready to be
// delivered through the rest of the telemetry pipeline after this method
// is called. Therefore, updates to the Span are not allowed after this
@ -490,8 +498,15 @@ func (sk SpanKind) String() string {
// Tracer is the creator of Spans.
//
// Warning: methods may be added to this interface in minor releases.
// Warning: Methods may be added to this interface in minor releases. See
// package documentation on API implementation for information on how to set
// default behavior for unimplemented methods.
type Tracer interface {
// Users of the interface can ignore this. This embedded type is only used
// by implementations of this interface. See the "API Implementations"
// section of the package documentation for more information.
embedded.Tracer
// Start creates a span and a context.Context containing the newly-created span.
//
// If the context.Context provided in `ctx` contains a Span then the newly-created
@ -522,8 +537,15 @@ type Tracer interface {
// at runtime from its users or it can simply use the globally registered one
// (see https://pkg.go.dev/go.opentelemetry.io/otel#GetTracerProvider).
//
// Warning: methods may be added to this interface in minor releases.
// Warning: Methods may be added to this interface in minor releases. See
// package documentation on API implementation for information on how to set
// default behavior for unimplemented methods.
type TracerProvider interface {
// Users of the interface can ignore this. This embedded type is only used
// by implementations of this interface. See the "API Implementations"
// section of the package documentation for more information.
embedded.TracerProvider
// Tracer returns a unique Tracer scoped to be used by instrumentation code
// to trace computational workflows. The scope and identity of that
// instrumentation code is uniquely defined by the name and options passed.

View File

@ -20,19 +20,21 @@ import (
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/noop"
)
type testTracerProvider struct{}
type testTracerProvider struct{ embedded.TracerProvider }
var _ trace.TracerProvider = &testTracerProvider{}
func (*testTracerProvider) Tracer(_ string, _ ...trace.TracerOption) trace.Tracer {
return trace.NewNoopTracerProvider().Tracer("")
return noop.NewTracerProvider().Tracer("")
}
func TestMultipleGlobalTracerProvider(t *testing.T) {
p1 := testTracerProvider{}
p2 := trace.NewNoopTracerProvider()
p2 := noop.NewTracerProvider()
SetTracerProvider(&p1)
SetTracerProvider(p2)