OpenTelemetry Trace API for Go provides distributed tracing capabilities with TracerProvider, Tracer, and Span interfaces for instrumentation.
This document covers all configuration options for customizing span behavior at creation, during execution, and at completion.
Options control span behavior at different lifecycle stages:
Options for span creation via Tracer.Start().
type SpanStartOption interface {
// contains unexported methods
}// WithNewRoot specifies that the Span should be treated as a root Span.
// Any existing parent span context will be ignored when defining the Span's
// trace identifiers.
func WithNewRoot() SpanStartOption
// WithSpanKind sets the SpanKind of a Span.
func WithSpanKind(kind SpanKind) SpanStartOption
// WithLinks adds links to a Span. The links are added to the existing Span
// links, i.e. this does not overwrite. Links with invalid span context are
// ignored.
func WithLinks(links ...Link) SpanStartOption
// WithAttributes adds the attributes related to a span life-cycle event.
// These attributes are used to describe the work a Span represents when this
// option is provided to a Span's start event. Otherwise, these attributes
// provide additional information about the event being recorded (e.g. error,
// state change, processing progress, system event).
//
// If multiple of these options are passed the attributes of each successive
// option will extend the attributes instead of overwriting. There is no
// guarantee of uniqueness in the resulting attributes.
func WithAttributes(attributes ...attribute.KeyValue) SpanStartEventOption
// WithTimestamp sets the time of a Span or Event life-cycle moment (e.g.
// started, stopped, errored).
func WithTimestamp(t time.Time) SpanEventOptionimport (
"context"
"time"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/attribute"
)
ctx, span := tracer.Start(ctx, "processOrder",
// Force new root trace (ignore parent)
trace.WithNewRoot(),
// Set span kind
trace.WithSpanKind(trace.SpanKindServer),
// Add initial attributes (visible to samplers)
trace.WithAttributes(
attribute.String("order.id", orderID),
attribute.String("customer.id", customerID),
attribute.Int("order.items", itemCount),
),
// Add links to related spans
trace.WithLinks(
trace.Link{
SpanContext: relatedSpanContext,
Attributes: []attribute.KeyValue{
attribute.String("link.type", "related-order"),
},
},
),
// Custom start time
trace.WithTimestamp(customStartTime),
)
defer span.End()Options for completing a span via Span.End().
type SpanEndOption interface {
// contains unexported methods
}// WithStackTrace sets the flag to capture the error with stack trace (e.g.
// true, false).
func WithStackTrace(b bool) SpanEndEventOption
// WithTimestamp sets the time of a Span or Event life-cycle moment (e.g.
// started, stopped, errored).
func WithTimestamp(t time.Time) SpanEventOption// End span with custom timestamp
customEndTime := time.Now().Add(-5 * time.Second)
span.End(trace.WithTimestamp(customEndTime))
// Or just defer with no options (most common)
defer span.End()Options that can be used at both span start and end.
type SpanOption interface {
SpanStartOption
SpanEndOption
}Currently, options implementing both interfaces:
WithTimestamp(t time.Time) - Can set start or end timeOptions for span events via Span.AddEvent() or Span.RecordError().
type EventOption interface {
// contains unexported methods
}// WithAttributes adds the attributes related to a span life-cycle event.
func WithAttributes(attributes ...attribute.KeyValue) SpanStartEventOption
// WithTimestamp sets the time of an Event.
func WithTimestamp(t time.Time) SpanEventOption
// WithStackTrace sets the flag to capture the error with stack trace.
func WithStackTrace(b bool) SpanEndEventOption// Add event with attributes and custom timestamp
span.AddEvent("cache-miss",
trace.WithAttributes(
attribute.String("cache.key", "user:123"),
attribute.String("cache.backend", "redis"),
),
trace.WithTimestamp(time.Now()),
)
// Record error with stack trace
if err != nil {
span.RecordError(err,
trace.WithStackTrace(true),
trace.WithAttributes(
attribute.String("error.source", "database"),
attribute.String("query", sqlQuery),
),
)
}SpanConfig holds the configuration of a span. It's typically used by SDK implementations to query span settings.
type SpanConfig struct {
// contains unexported fields
}
// NewSpanStartConfig applies all the options to a returned SpanConfig.
// No validation is performed on the returned SpanConfig (e.g. no uniqueness
// checking or bounding of data), it is left to the SDK to perform this action.
func NewSpanStartConfig(options ...SpanStartOption) SpanConfig
// NewSpanEndConfig applies all the options to a returned SpanConfig.
// No validation is performed on the returned SpanConfig (e.g. no uniqueness
// checking or bounding of data), it is left to the SDK to perform this action.
func NewSpanEndConfig(options ...SpanEndOption) SpanConfig// Timestamp is a time in a Span life-cycle.
func (cfg *SpanConfig) Timestamp() time.Time
// StackTrace reports whether stack trace capturing is enabled.
func (cfg *SpanConfig) StackTrace() bool
// Attributes describe the associated qualities of a Span.
func (cfg *SpanConfig) Attributes() []attribute.KeyValue
// Links are the associations a Span has with other Spans.
func (cfg *SpanConfig) Links() []Link
// NewRoot identifies a Span as the root Span for a new trace. This is commonly
// used when an existing trace crosses trust boundaries and the remote parent
// span context should be ignored for security.
func (cfg *SpanConfig) NewRoot() bool
// SpanKind is the role a Span has in a trace.
func (cfg *SpanConfig) SpanKind() SpanKind// SDK implementations use SpanConfig to query configuration
func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
config := trace.NewSpanStartConfig(opts...)
// Check if this should be a new root
if config.NewRoot() {
// Start new trace
}
// Get span kind
kind := config.SpanKind()
// Get initial attributes for sampling decision
attrs := config.Attributes()
// Get links
links := config.Links()
// Get custom start timestamp if provided
timestamp := config.Timestamp()
// Create span...
}EventConfig holds the configuration of an event.
type EventConfig struct {
// contains unexported fields
}
// NewEventConfig applies all the EventOptions to a returned EventConfig. If no
// timestamp option is passed, the returned EventConfig will have a Timestamp
// set to the call time, otherwise no validation is performed on the returned
// EventConfig.
func NewEventConfig(options ...EventOption) EventConfig// Timestamp is a time in an Event life-cycle.
func (cfg *EventConfig) Timestamp() time.Time
// StackTrace reports whether stack trace capturing is enabled.
func (cfg *EventConfig) StackTrace() bool
// Attributes describe the associated qualities of an Event.
func (cfg *EventConfig) Attributes() []attribute.KeyValueLinks connect spans within or across traces.
type Link struct {
// SpanContext of the linked Span.
SpanContext SpanContext
// Attributes describe the aspects of the link.
Attributes []attribute.KeyValue
}
// LinkFromContext returns a link encapsulating the SpanContext in the provided
// ctx.
func LinkFromContext(ctx context.Context, attrs ...attribute.KeyValue) Link// Create link from span context
link := trace.Link{
SpanContext: remoteSpanContext,
Attributes: []attribute.KeyValue{
attribute.String("link.type", "continuation"),
attribute.String("link.source", "batch-job"),
},
}
// Create link from context
link := trace.LinkFromContext(relatedCtx,
attribute.String("link.type", "related"),
)
// Add links at span creation (preferred for sampling)
ctx, span := tracer.Start(ctx, "batchProcess",
trace.WithLinks(
trace.LinkFromContext(jobCtx1),
trace.LinkFromContext(jobCtx2),
trace.LinkFromContext(jobCtx3),
),
)
defer span.End()
// Or add link after creation
span.AddLink(link)package main
import (
"context"
"errors"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func processBatchJob(ctx context.Context, jobID string, relatedJobs []context.Context) error {
tracer := otel.Tracer("batch-processor")
// Collect links to related jobs
links := make([]trace.Link, len(relatedJobs))
for i, jobCtx := range relatedJobs {
links[i] = trace.LinkFromContext(jobCtx,
attribute.Int("job.index", i),
)
}
// Create span with comprehensive configuration
startTime := time.Now()
ctx, span := tracer.Start(ctx, "processBatchJob",
// Span kind
trace.WithSpanKind(trace.SpanKindInternal),
// Initial attributes (available to samplers)
trace.WithAttributes(
attribute.String("job.id", jobID),
attribute.String("job.type", "batch"),
attribute.Int("job.size", len(relatedJobs)),
attribute.String("processor.version", "v2.1.0"),
),
// Links to related jobs
trace.WithLinks(links...),
// Custom start timestamp
trace.WithTimestamp(startTime),
)
defer func() {
// End with custom timestamp
endTime := time.Now()
span.End(trace.WithTimestamp(endTime))
}()
// Add progress event
span.AddEvent("job-started",
trace.WithAttributes(
attribute.String("stage", "initialization"),
),
)
// Simulate work with error
if err := performWork(ctx); err != nil {
// Record error with full context
span.RecordError(err,
trace.WithStackTrace(true),
trace.WithAttributes(
attribute.String("error.phase", "processing"),
attribute.Bool("error.recoverable", false),
),
trace.WithTimestamp(time.Now()),
)
span.SetStatus(codes.Error, "batch job failed")
return err
}
// Add completion event
span.AddEvent("job-completed",
trace.WithAttributes(
attribute.String("stage", "finalization"),
attribute.Int("processed.items", 100),
),
)
span.SetStatus(codes.Ok, "")
return nil
}
func performWork(ctx context.Context) error {
return errors.New("simulated error")
}Add attributes at creation: Samplers only see attributes provided at span start
ctx, span := tracer.Start(ctx, "operation",
trace.WithAttributes(/* sampling-relevant attributes */),
)Use links at creation: Prefer WithLinks() over AddLink() for sampling decisions
ctx, span := tracer.Start(ctx, "batch",
trace.WithLinks(links...),
)Set appropriate span kind: Helps backends classify and visualize traces
trace.WithSpanKind(trace.SpanKindServer) // or Client, Producer, Consumer, InternalAdd meaningful events: Mark significant milestones in span execution
span.AddEvent("cache-hit")
span.AddEvent("database-query-started")
span.AddEvent("validation-completed")Include event context: Add attributes to explain what happened
span.AddEvent("retry-attempted",
trace.WithAttributes(
attribute.Int("retry.attempt", attemptNum),
attribute.String("retry.reason", reason),
),
)Always include stack traces for errors: Helps debugging
span.RecordError(err, trace.WithStackTrace(true))Set status after recording error: RecordError() doesn't change status
span.RecordError(err)
span.SetStatus(codes.Error, "operation failed")Use sparingly: Let SDK capture timestamps unless you have a specific need
// Only when you need to backdate or adjust timing
trace.WithTimestamp(customTime)Consistent timing: If you use custom start time, consider custom end time
startTime := parseEventTime(event)
ctx, span := tracer.Start(ctx, "historical",
trace.WithTimestamp(startTime),
)
// ...
span.End(trace.WithTimestamp(endTime))Use for untrusted contexts: Create new trace for security
ctx, span := tracer.Start(ctx, "publicEndpoint",
trace.WithNewRoot(), // Ignore potentially malicious parent
)Link to original context: Maintain relationship while securing trace
originalLink := trace.LinkFromContext(untrustedCtx,
attribute.String("link.type", "untrusted-parent"),
)
ctx, span := tracer.Start(ctx, "publicEndpoint",
trace.WithNewRoot(),
trace.WithLinks(originalLink),
)Install with Tessl CLI
npx tessl i tessl/golang-go-opentelemetry-io--otel--trace