The noop package provides production-ready implementations of the OpenTelemetry Trace API that produce no telemetry and minimize computational resources. These implementations are useful for testing, disabling telemetry, or as default behaviors for API implementers.
import "go.opentelemetry.io/otel/trace/noop"No-op implementations:
Note: The main go.opentelemetry.io/otel/trace package also provides a deprecated NewNoopTracerProvider() function. New code should use noop.NewTracerProvider() from this package instead.
type TracerProvider struct {
embedded.TracerProvider
}
// NewTracerProvider returns a TracerProvider that does not record any
// telemetry.
func NewTracerProvider() TracerProvider
// Tracer returns an OpenTelemetry Tracer that does not record any telemetry.
func (TracerProvider) Tracer(string, ...trace.TracerOption) trace.Tracerimport (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace/noop"
)
func main() {
// Disable tracing globally
otel.SetTracerProvider(noop.NewTracerProvider())
// All tracing operations will now be no-ops
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "operation")
defer span.End()
// No telemetry is produced
}package myservice
import (
"testing"
"context"
"go.opentelemetry.io/otel/trace/noop"
)
func TestServiceOperation(t *testing.T) {
// Use no-op tracer provider for tests
tp := noop.NewTracerProvider()
service := NewService(tp)
// Test service logic without producing trace data
err := service.DoWork(context.Background())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}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 (Tracer) Start(ctx context.Context, _ string, _ ...trace.SpanStartOption) (context.Context, trace.Span)The no-op Tracer:
import (
"context"
"go.opentelemetry.io/otel/trace/noop"
)
func processWithNoOp(ctx context.Context) {
tp := noop.NewTracerProvider()
tracer := tp.Tracer("no-telemetry")
// Span is created but does nothing
ctx, span := tracer.Start(ctx, "process")
defer span.End()
// All span operations are no-ops
span.SetAttributes(/* no effect */)
span.AddEvent("event") // no effect
span.SetStatus(codes.Ok, "") // no effect
// But context propagation still works
childCtx, childSpan := tracer.Start(ctx, "child")
defer childSpan.End()
}type Span struct {
embedded.Span
// contains unexported fields
}// End does nothing.
func (Span) End(...trace.SpanEndOption)
// AddEvent does nothing.
func (Span) AddEvent(string, ...trace.EventOption)
// AddLink does nothing.
func (Span) AddLink(trace.Link)
// IsRecording always returns false.
func (Span) IsRecording() bool
// RecordError does nothing.
func (Span) RecordError(error, ...trace.EventOption)
// SpanContext returns an empty span context.
func (s Span) SpanContext() trace.SpanContext
// SetStatus does nothing.
func (Span) SetStatus(codes.Code, string)
// SetName does nothing.
func (Span) SetName(string)
// SetAttributes does nothing.
func (Span) SetAttributes(...attribute.KeyValue)
// TracerProvider returns a No-Op TracerProvider.
func (Span) TracerProvider() trace.TracerProviderIsRecording() always returns false: Allows optimizations in user code
if span.IsRecording() {
// This block never executes with no-op span
expensiveData := computeMetrics()
span.SetAttributes(expensiveData)
}SpanContext() returns empty context: Still valid for propagation
sc := span.SpanContext()
// sc.IsValid() returns false for no-op spansAll mutating operations are no-ops: Safe to call, but have no effect
package main
import (
"context"
"flag"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
var enableTracing = flag.Bool("trace", false, "enable distributed tracing")
func main() {
flag.Parse()
// Configure tracer provider based on flag
var tp trace.TracerProvider
if *enableTracing {
// Use real implementation (e.g., from SDK)
tp = setupRealTracer()
} else {
// Use no-op implementation
tp = noop.NewTracerProvider()
}
otel.SetTracerProvider(tp)
// Application code remains the same
runApplication()
}
func runApplication() {
tracer := otel.Tracer("my-app")
ctx := context.Background()
ctx, span := tracer.Start(ctx, "main-operation")
defer span.End()
// All tracing calls work regardless of provider
span.SetAttributes(/* ... */)
processRequest(ctx, tracer)
}
func processRequest(ctx context.Context, tracer trace.Tracer) {
ctx, span := tracer.Start(ctx, "process-request")
defer span.End()
// Optimize expensive operations based on recording state
if span.IsRecording() {
// Only compute when actually recording
metrics := computeExpensiveMetrics()
span.SetAttributes(metrics...)
}
}
func setupRealTracer() trace.TracerProvider {
// Return actual SDK implementation
return nil // placeholder
}When implementing custom TracerProvider, embed no-op types for safe defaults:
import (
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
// CustomTracerProvider with no-op defaults
type CustomTracerProvider struct {
noop.TracerProvider // Embed for default behavior
// custom fields
}
// Override only the methods you implement
func (ctp *CustomTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
// Custom implementation
return &CustomTracer{
name: name,
}
}
// CustomTracer with no-op defaults
type CustomTracer struct {
noop.Tracer // Embed for default behavior
name string
}
// Override only the methods you implement
func (ct *CustomTracer) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
// Custom implementation
return ctx, &CustomSpan{name: spanName}
}
// CustomSpan with no-op defaults
type CustomSpan struct {
noop.Span // Embed for default behavior
name string
}
// Override specific methods as needed
func (cs *CustomSpan) End(opts ...trace.SpanEndOption) {
// Custom end logic
}| Feature | No-Op Span | Non-Recording Span |
|---|---|---|
| IsRecording() | Always false | Always false |
| Context propagation | Maintains SpanContext | Maintains SpanContext |
| Sampling decision | Not sampled | Sampled but not exported |
| Performance | Minimal overhead | Slightly higher overhead |
| Use case | Disable tracing | Respect sampling decisions |
No-op implementations are designed for minimal overhead:
// No-op operations have nearly zero cost
span.SetAttributes(attr1, attr2, attr3) // ~1-2 ns
span.AddEvent("event") // ~1-2 ns
span.End() // ~1-2 nsCompared to recording spans which involve:
Use for testing: Avoid test dependencies on trace infrastructure
func TestMyFunction(t *testing.T) {
tp := noop.NewTracerProvider()
service := NewService(tp)
// Test without trace overhead
}Conditional tracing: Enable/disable based on configuration
if config.TracingEnabled {
tp = setupSDK()
} else {
tp = noop.NewTracerProvider()
}Embed for partial implementations: Provide safe defaults
type MyTracer struct {
noop.Tracer // Unimplemented methods are no-ops
}Check IsRecording(): Optimize expensive operations
if span.IsRecording() {
// Only compute if span is actually recording
}Global default: Set no-op as default until SDK is initialized
func init() {
otel.SetTracerProvider(noop.NewTracerProvider())
}
func main() {
// Later, replace with real implementation
otel.SetTracerProvider(realProvider)
}Starting with no-op and migrating to full tracing:
// Phase 1: Add tracing instrumentation with no-op
func init() {
otel.SetTracerProvider(noop.NewTracerProvider())
}
// Phase 2: Enable tracing in dev/staging
func initTelemetry() {
if os.Getenv("ENV") != "production" {
otel.SetTracerProvider(realSDK())
} else {
otel.SetTracerProvider(noop.NewTracerProvider())
}
}
// Phase 3: Enable in production with sampling
func initTelemetry() {
tp := realSDK()
// SDK handles sampling; no need for no-op
otel.SetTracerProvider(tp)
}