or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

attributes.mdbridges.mdcontext-propagation.mdexporters-other.mdexporters-otlp.mdindex.mdlogging.mdmetrics.mdsdk-log.mdsdk-metric.mdsdk-resource.mdsdk-trace.mdsemantic-conventions.mdtracing.md
tile.json

sdk-trace.mddocs/

SDK Trace Implementation

The OpenTelemetry Go SDK trace implementation provides concrete implementations of the trace API interfaces, including TracerProvider, span processors, span exporters, samplers, and ID generators.

Package Import

import sdktrace "go.opentelemetry.io/otel/sdk/trace"

Overview

The SDK trace package provides:

  1. TracerProvider: Factory for creating instrumented Tracers
  2. SpanProcessor: Process spans at start and end lifecycle events
  3. SpanExporter: Export spans to external systems
  4. Sampler: Make sampling decisions for spans
  5. IDGenerator: Generate trace and span IDs
  6. SpanLimits: Configure resource limits for spans

TracerProvider

The TracerProvider is the SDK implementation of the trace.TracerProvider interface.

Type Definition

type TracerProvider struct {
	// Has unexported fields
}

Creating a TracerProvider

func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider

Default Configuration:

  • Sampler: ParentBased(AlwaysSample)
  • IDGenerator: Random number generator
  • Resource: resource.Default()
  • SpanLimits: Default span limits

Example:

package main

import (
	"context"
	"log"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)

func main() {
	ctx := context.Background()

	// Create resource
	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceName("my-service"),
			semconv.ServiceVersion("1.0.0"),
		),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Create exporter
	exporter, err := otlptracehttp.New(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Create tracer provider
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithBatcher(exporter),
		sdktrace.WithResource(res),
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
	)

	// Set as global tracer provider
	otel.SetTracerProvider(tp)

	// Cleanup
	defer func() {
		if err := tp.Shutdown(ctx); err != nil {
			log.Printf("Error shutting down tracer provider: %v", err)
		}
	}()

	// Use tracer
	tracer := otel.Tracer("my-tracer")
	ctx, span := tracer.Start(ctx, "example-operation")
	defer span.End()

	// Your application logic here
}

TracerProvider Methods

// Tracer returns a Tracer with the given name and options
func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer

// Shutdown shuts down the TracerProvider, flushing all pending telemetry
func (p *TracerProvider) Shutdown(ctx context.Context) error

// ForceFlush immediately exports all spans that have not yet been exported
func (p *TracerProvider) ForceFlush(ctx context.Context) error

// RegisterSpanProcessor adds a SpanProcessor to the TracerProvider
func (p *TracerProvider) RegisterSpanProcessor(sp SpanProcessor)

// UnregisterSpanProcessor removes a SpanProcessor from the TracerProvider
func (p *TracerProvider) UnregisterSpanProcessor(sp SpanProcessor)

Example Usage:

// Get tracer from provider
tracer := tp.Tracer("my-component")

// Force flush all pending spans
if err := tp.ForceFlush(context.Background()); err != nil {
	log.Printf("Failed to flush: %v", err)
}

// Shutdown and cleanup
if err := tp.Shutdown(context.Background()); err != nil {
	log.Printf("Failed to shutdown: %v", err)
}

TracerProviderOption

Options for configuring a TracerProvider.

type TracerProviderOption interface {
	// Has unexported methods
}

Available Options:

// WithBatcher registers an exporter with a BatchSpanProcessor
func WithBatcher(e SpanExporter, opts ...BatchSpanProcessorOption) TracerProviderOption

// WithSyncer registers an exporter with a SimpleSpanProcessor (not recommended for production)
func WithSyncer(e SpanExporter) TracerProviderOption

// WithSpanProcessor registers a SpanProcessor
func WithSpanProcessor(sp SpanProcessor) TracerProviderOption

// WithResource configures the Resource for the TracerProvider
func WithResource(r *resource.Resource) TracerProviderOption

// WithSampler configures the Sampler for the TracerProvider
func WithSampler(s Sampler) TracerProviderOption

// WithIDGenerator configures the IDGenerator for the TracerProvider
func WithIDGenerator(g IDGenerator) TracerProviderOption

// WithRawSpanLimits configures SpanLimits for the TracerProvider
func WithRawSpanLimits(limits SpanLimits) TracerProviderOption

Span Processors

Span processors are called at the start and end of a span's lifecycle.

SpanProcessor Interface

type SpanProcessor interface {
	// OnStart is called when a span is started
	OnStart(parent context.Context, s ReadWriteSpan)

	// OnEnd is called when a span is finished
	OnEnd(s ReadOnlySpan)

	// Shutdown is called when the SDK shuts down
	Shutdown(ctx context.Context) error

	// ForceFlush exports all ended spans that have not yet been exported
	ForceFlush(ctx context.Context) error
}

BatchSpanProcessor

BatchSpanProcessor batches spans and exports them in batches.

func NewBatchSpanProcessor(exporter SpanExporter, options ...BatchSpanProcessorOption) SpanProcessor

Default Configuration:

  • MaxQueueSize: 2048
  • BatchTimeout: 5000 ms
  • ExportTimeout: 30000 ms
  • MaxExportBatchSize: 512

Example:

import (
	"time"
	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func setupBatchProcessor() sdktrace.SpanProcessor {
	exporter, err := stdouttrace.New()
	if err != nil {
		log.Fatal(err)
	}

	processor := sdktrace.NewBatchSpanProcessor(
		exporter,
		sdktrace.WithMaxQueueSize(4096),
		sdktrace.WithBatchTimeout(10*time.Second),
		sdktrace.WithExportTimeout(30*time.Second),
		sdktrace.WithMaxExportBatchSize(1024),
	)

	return processor
}

BatchSpanProcessorOption

Options for configuring a BatchSpanProcessor.

type BatchSpanProcessorOption func(o *BatchSpanProcessorOptions)

type BatchSpanProcessorOptions struct {
	// MaxQueueSize is the maximum queue size to buffer spans
	MaxQueueSize int

	// BatchTimeout is the maximum duration for constructing a batch
	BatchTimeout time.Duration

	// ExportTimeout specifies the maximum duration for exporting spans
	ExportTimeout time.Duration

	// MaxExportBatchSize is the maximum number of spans to process in a single batch
	MaxExportBatchSize int

	// BlockOnQueueFull blocks if the queue is full
	BlockOnQueueFull bool
}

Available Options:

// WithMaxQueueSize sets the maximum queue size
func WithMaxQueueSize(size int) BatchSpanProcessorOption

// WithBatchTimeout sets the maximum duration for constructing a batch
func WithBatchTimeout(delay time.Duration) BatchSpanProcessorOption

// WithExportTimeout sets the maximum duration for exporting spans
func WithExportTimeout(timeout time.Duration) BatchSpanProcessorOption

// WithMaxExportBatchSize sets the maximum batch size
func WithMaxExportBatchSize(size int) BatchSpanProcessorOption

// WithBlocking sets whether to block when the queue is full
func WithBlocking() BatchSpanProcessorOption

SimpleSpanProcessor

SimpleSpanProcessor exports spans synchronously when they end. Not recommended for production use.

func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor

Example:

import (
	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func setupSimpleProcessor() sdktrace.SpanProcessor {
	exporter, err := stdouttrace.New()
	if err != nil {
		log.Fatal(err)
	}

	// Only use for testing/debugging
	processor := sdktrace.NewSimpleSpanProcessor(exporter)
	return processor
}

Span Exporters

Span exporters send span data to external systems.

SpanExporter Interface

type SpanExporter interface {
	// ExportSpans exports a batch of spans
	ExportSpans(ctx context.Context, spans []ReadOnlySpan) error

	// Shutdown notifies the exporter of a pending halt to operations
	Shutdown(ctx context.Context) error
}

ReadOnlySpan Interface

ReadOnlySpan is the read-only interface to a span.

type ReadOnlySpan interface {
	Name() string
	SpanContext() trace.SpanContext
	Parent() trace.SpanContext
	SpanKind() trace.SpanKind
	StartTime() time.Time
	EndTime() time.Time
	Attributes() []attribute.KeyValue
	Links() []trace.Link
	Events() []Event
	Status() Status
	InstrumentationScope() instrumentation.Scope
	InstrumentationLibrary() instrumentation.Library
	Resource() *resource.Resource
	DroppedAttributes() int
	DroppedLinks() int
	DroppedEvents() int
	ChildSpanCount() int
	TracerProvider() trace.TracerProvider
}

Samplers

Samplers decide whether a span should be sampled and exported.

Sampler Interface

type Sampler interface {
	// ShouldSample returns a SamplingResult based on a decision
	ShouldSample(parameters SamplingParameters) SamplingResult

	// Description returns information describing the Sampler
	Description() string
}

SamplingParameters

type SamplingParameters struct {
	ParentContext context.Context
	TraceID       trace.TraceID
	Name          string
	Kind          trace.SpanKind
	Attributes    []attribute.KeyValue
	Links         []trace.Link
}

SamplingResult

type SamplingDecision uint8

const (
	// Drop will not record the span
	Drop SamplingDecision = iota

	// RecordOnly indicates the span is recorded but not sampled
	RecordOnly

	// RecordAndSample indicates the span is recorded and sampled
	RecordAndSample
)

type SamplingResult struct {
	Decision   SamplingDecision
	Attributes []attribute.KeyValue
	Tracestate trace.TraceState
}

Built-in Samplers

AlwaysSample

Samples every trace.

func AlwaysSample() Sampler

Example:

tp := sdktrace.NewTracerProvider(
	sdktrace.WithSampler(sdktrace.AlwaysSample()),
)

NeverSample

Samples no traces.

func NeverSample() Sampler

Example:

tp := sdktrace.NewTracerProvider(
	sdktrace.WithSampler(sdktrace.NeverSample()),
)

TraceIDRatioBased

Samples a given fraction of traces.

func TraceIDRatioBased(fraction float64) Sampler

Example:

// Sample 10% of traces
tp := sdktrace.NewTracerProvider(
	sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)),
)

ParentBased

Respects the parent span's sampling decision.

func ParentBased(root Sampler, samplers ...ParentBasedSamplerOption) Sampler

ParentBasedSamplerOption:

type ParentBasedSamplerOption interface {
	// Has unexported methods
}

// WithRemoteParentSampled sets the sampler for sampled remote parent
func WithRemoteParentSampled(s Sampler) ParentBasedSamplerOption

// WithRemoteParentNotSampled sets the sampler for remote parent which is not sampled
func WithRemoteParentNotSampled(s Sampler) ParentBasedSamplerOption

// WithLocalParentSampled sets the sampler for sampled local parent
func WithLocalParentSampled(s Sampler) ParentBasedSamplerOption

// WithLocalParentNotSampled sets the sampler for local parent which is not sampled
func WithLocalParentNotSampled(s Sampler) ParentBasedSamplerOption

Example:

// Default: ParentBased with AlwaysSample root
sampler := sdktrace.ParentBased(sdktrace.AlwaysSample())

// Custom configuration
sampler := sdktrace.ParentBased(
	sdktrace.TraceIDRatioBased(0.5),
	sdktrace.WithRemoteParentSampled(sdktrace.AlwaysSample()),
	sdktrace.WithRemoteParentNotSampled(sdktrace.NeverSample()),
	sdktrace.WithLocalParentSampled(sdktrace.AlwaysSample()),
	sdktrace.WithLocalParentNotSampled(sdktrace.NeverSample()),
)

tp := sdktrace.NewTracerProvider(
	sdktrace.WithSampler(sampler),
)

ID Generators

ID generators create trace and span IDs.

IDGenerator Interface

type IDGenerator interface {
	// NewIDs returns a new trace and span ID
	NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID)

	// NewSpanID returns a new span ID for the given trace
	NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID
}

Default ID Generator

The default ID generator uses a random number generator.

Example:

// Custom ID generator
type customIDGenerator struct{}

func (g customIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) {
	var traceID trace.TraceID
	var spanID trace.SpanID
	// Custom logic to generate IDs
	return traceID, spanID
}

func (g customIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID {
	var spanID trace.SpanID
	// Custom logic to generate span ID
	return spanID
}

// Use custom ID generator
tp := sdktrace.NewTracerProvider(
	sdktrace.WithIDGenerator(customIDGenerator{}),
)

Span Limits

SpanLimits configure resource limits for spans.

SpanLimits Type

type SpanLimits struct {
	// AttributeValueLengthLimit is the maximum allowed attribute value length
	AttributeValueLengthLimit int

	// AttributeCountLimit is the maximum allowed span attribute count
	AttributeCountLimit int

	// EventCountLimit is the maximum allowed span event count
	EventCountLimit int

	// LinkCountLimit is the maximum allowed span link count
	LinkCountLimit int

	// AttributePerEventCountLimit is the maximum allowed attributes per event
	AttributePerEventCountLimit int

	// AttributePerLinkCountLimit is the maximum allowed attributes per link
	AttributePerLinkCountLimit int
}

Creating SpanLimits

// NewSpanLimits returns a SpanLimits with values from environment or defaults
func NewSpanLimits() SpanLimits

Default Values:

const (
	// DefaultAttributeValueLengthLimit is the default maximum allowed attribute value length
	DefaultAttributeValueLengthLimit = -1 // unlimited

	// DefaultAttributeCountLimit is the default maximum allowed span attribute count
	DefaultAttributeCountLimit = 128

	// DefaultEventCountLimit is the default maximum allowed span event count
	DefaultEventCountLimit = 128

	// DefaultLinkCountLimit is the default maximum allowed span link count
	DefaultLinkCountLimit = 128

	// DefaultAttributePerEventCountLimit is the default maximum allowed attributes per event
	DefaultAttributePerEventCountLimit = 128

	// DefaultAttributePerLinkCountLimit is the default maximum allowed attributes per link
	DefaultAttributePerLinkCountLimit = 128
)

Example:

// Use default limits
limits := sdktrace.NewSpanLimits()

// Custom limits
limits := sdktrace.SpanLimits{
	AttributeValueLengthLimit:   1000,
	AttributeCountLimit:         256,
	EventCountLimit:             256,
	LinkCountLimit:              64,
	AttributePerEventCountLimit: 64,
	AttributePerLinkCountLimit:  64,
}

tp := sdktrace.NewTracerProvider(
	sdktrace.WithRawSpanLimits(limits),
)

Complete Example

Full Setup with All Components

package main

import (
	"context"
	"log"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)

func main() {
	ctx := context.Background()

	// Step 1: Create resource
	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceName("my-service"),
			semconv.ServiceVersion("1.0.0"),
			semconv.DeploymentEnvironment("production"),
		),
		resource.WithProcess(),
		resource.WithHost(),
	)
	if err != nil {
		log.Fatalf("Failed to create resource: %v", err)
	}

	// Step 2: Create exporter
	exporter, err := otlptracehttp.New(ctx,
		otlptracehttp.WithEndpoint("localhost:4318"),
		otlptracehttp.WithInsecure(),
	)
	if err != nil {
		log.Fatalf("Failed to create exporter: %v", err)
	}

	// Step 3: Create span processor
	processor := sdktrace.NewBatchSpanProcessor(
		exporter,
		sdktrace.WithMaxQueueSize(4096),
		sdktrace.WithBatchTimeout(5*time.Second),
		sdktrace.WithExportTimeout(30*time.Second),
		sdktrace.WithMaxExportBatchSize(512),
	)

	// Step 4: Create sampler
	sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.5))

	// Step 5: Configure span limits
	limits := sdktrace.SpanLimits{
		AttributeValueLengthLimit:   -1, // unlimited
		AttributeCountLimit:         128,
		EventCountLimit:             128,
		LinkCountLimit:              128,
		AttributePerEventCountLimit: 128,
		AttributePerLinkCountLimit:  128,
	}

	// Step 6: Create tracer provider
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithSpanProcessor(processor),
		sdktrace.WithResource(res),
		sdktrace.WithSampler(sampler),
		sdktrace.WithRawSpanLimits(limits),
	)

	// Step 7: Set as global provider
	otel.SetTracerProvider(tp)

	// Step 8: Setup cleanup
	defer func() {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		// Force flush to ensure all spans are exported
		if err := tp.ForceFlush(ctx); err != nil {
			log.Printf("Failed to flush spans: %v", err)
		}

		// Shutdown provider
		if err := tp.Shutdown(ctx); err != nil {
			log.Printf("Failed to shutdown provider: %v", err)
		}
	}()

	// Step 9: Use tracer
	tracer := otel.Tracer("my-component")

	// Create root span
	ctx, rootSpan := tracer.Start(ctx, "root-operation")
	rootSpan.SetAttributes(
		attribute.String("user.id", "12345"),
		attribute.String("action", "process"),
	)

	// Simulate work
	processRequest(ctx, tracer)

	rootSpan.End()
}

func processRequest(ctx context.Context, tracer trace.Tracer) {
	ctx, span := tracer.Start(ctx, "process-request")
	defer span.End()

	span.SetAttributes(
		attribute.Int("items.count", 10),
		attribute.String("status", "processing"),
	)

	span.AddEvent("processing started")

	// Simulate work
	time.Sleep(100 * time.Millisecond)

	span.AddEvent("processing completed")
}

HTTP Server Instrumentation

package main

import (
	"context"
	"net/http"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/propagation"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)

func instrumentedHandler(w http.ResponseWriter, r *http.Request) {
	// Extract context from incoming request
	ctx := otel.GetTextMapPropagator().Extract(
		r.Context(),
		propagation.HeaderCarrier(r.Header),
	)

	tracer := otel.Tracer("http-server")

	// Start server span
	ctx, span := tracer.Start(
		ctx,
		r.URL.Path,
		trace.WithSpanKind(trace.SpanKindServer),
		trace.WithAttributes(
			semconv.HTTPMethod(r.Method),
			semconv.HTTPTarget(r.URL.Path),
			semconv.NetHostName(r.Host),
			semconv.HTTPScheme(r.URL.Scheme),
		),
	)
	defer span.End()

	// Add event
	span.AddEvent("request.received")

	// Process request
	statusCode := processHTTPRequest(ctx, r)

	// Set response attributes
	span.SetAttributes(semconv.HTTPStatusCode(statusCode))

	// Set status
	if statusCode >= 400 {
		span.SetStatus(codes.Error, "HTTP error")
	} else {
		span.SetStatus(codes.Ok, "success")
	}

	w.WriteHeader(statusCode)
}

func processHTTPRequest(ctx context.Context, r *http.Request) int {
	tracer := otel.Tracer("http-server")
	ctx, span := tracer.Start(ctx, "process-http-request")
	defer span.End()

	// Simulate processing
	time.Sleep(50 * time.Millisecond)

	return http.StatusOK
}

Environment Variables

The SDK respects the following environment variables:

Span Processor Configuration

# Batch span processor maximum queue size
OTEL_BSP_MAX_QUEUE_SIZE=2048

# Batch span processor schedule delay in milliseconds
OTEL_BSP_SCHEDULE_DELAY=5000

# Batch span processor export timeout in milliseconds
OTEL_BSP_EXPORT_TIMEOUT=30000

# Batch span processor maximum export batch size
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512

Sampler Configuration

# Sampler to use (always_on, always_off, traceidratio, parentbased_always_on, etc.)
OTEL_TRACES_SAMPLER=parentbased_always_on

# Sampler argument (for traceidratio, this is the fraction)
OTEL_TRACES_SAMPLER_ARG=0.5

Span Limits Configuration

# Maximum attribute value length (-1 for unlimited)
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT=-1

# Maximum number of attributes per span
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=128

# Maximum number of events per span
OTEL_SPAN_EVENT_COUNT_LIMIT=128

# Maximum number of links per span
OTEL_SPAN_LINK_COUNT_LIMIT=128

# Maximum number of attributes per event
OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT=128

# Maximum number of attributes per link
OTEL_LINK_ATTRIBUTE_COUNT_LIMIT=128

Best Practices

1. Use BatchSpanProcessor for Production

// Good: Use batch processor
tp := sdktrace.NewTracerProvider(
	sdktrace.WithBatcher(exporter),
)

// Bad: Don't use simple processor in production
tp := sdktrace.NewTracerProvider(
	sdktrace.WithSyncer(exporter), // Synchronous, slow
)

2. Always Shutdown TracerProvider

tp := sdktrace.NewTracerProvider(/*...*/)
defer func() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := tp.Shutdown(ctx); err != nil {
		log.Printf("Error shutting down: %v", err)
	}
}()

3. Use Appropriate Sampling

// For high-traffic services, use ratio-based sampling
sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1))

// For low-traffic services, sample everything
sampler := sdktrace.ParentBased(sdktrace.AlwaysSample())

4. Configure Reasonable Limits

limits := sdktrace.SpanLimits{
	AttributeValueLengthLimit:   1000,  // Prevent huge attribute values
	AttributeCountLimit:         128,   // Reasonable number of attributes
	EventCountLimit:             128,   // Reasonable number of events
	LinkCountLimit:              128,   // Reasonable number of links
	AttributePerEventCountLimit: 64,    // Limit per event
	AttributePerLinkCountLimit:  64,    // Limit per link
}

5. Set Resource Attributes

res, err := resource.New(ctx,
	resource.WithAttributes(
		semconv.ServiceName("my-service"),
		semconv.ServiceVersion("1.0.0"),
		semconv.DeploymentEnvironment("production"),
		semconv.ServiceInstanceID(instanceID),
	),
)

Related Documentation

  • Tracing API: Core tracing API interfaces
  • SDK Resource: Resource detection and configuration
  • OTLP Exporters: OTLP protocol exporters
  • Other Exporters: Stdout, Zipkin, and other exporters
  • Semantic Conventions: Standard attribute names