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),
)