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

tracing.mddocs/

Distributed Tracing API

The OpenTelemetry Go tracing API provides the interfaces and types for creating and managing distributed traces. It follows the OpenTelemetry specification and enables tracking of requests as they flow through distributed systems.

Package Import

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

Overview

The tracing API is based on three main concepts:

  1. TracerProvider: Factory for creating Tracers
  2. Tracer: Creates Spans for a specific instrumentation scope
  3. Span: Represents a unit of work in a trace

Quick Start

Basic Tracing Example

package main

import (
	"context"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
)

func main() {
	// Get a tracer
	tracer := otel.Tracer("my-service")

	// Create a context
	ctx := context.Background()

	// Start a span
	ctx, span := tracer.Start(ctx, "operation")
	defer span.End()

	// Add attributes
	span.SetAttributes(
		attribute.String("key", "value"),
		attribute.Int("count", 10),
	)

	// Record an event
	span.AddEvent("processing started")

	// Do work...

	// Record completion
	span.SetStatus(codes.Ok, "completed successfully")
}

Core Interfaces

TracerProvider

The TracerProvider is the entry point for creating Tracers.

type TracerProvider interface {
	// 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.
	//
	// The passed name needs to uniquely identify instrumentation code.
	// Therefore, it is recommended that name is the Go package name of the
	// library providing instrumentation (note: not the code being
	// instrumented). Instrumentation libraries can have multiple versions,
	// therefore, the WithInstrumentationVersion option should be used to
	// distinguish these different codebases. Additionally, instrumentation
	// libraries may sometimes use traces to communicate different domains of
	// workflow data (i.e. using spans to communicate workflow events only).
	//
	// If the same name and options are passed multiple times, the same Tracer
	// will be returned (it is up to the implementation if this will be the
	// same underlying instance of that Tracer or not). It is not necessary to
	// call this multiple times with the same name and options to get an
	// up-to-date Tracer. All implementations will ensure any TracerProvider
	// configuration changes are propagated to all provided Tracers.
	//
	// If name is empty, then an implementation defined default name will be
	// used instead.
	//
	// This method is safe to call concurrently.
	Tracer(name string, options ...TracerOption) Tracer
}

Creating a TracerProvider

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

// Set up global tracer provider
func setupTracing() {
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithBatcher(exporter),
		sdktrace.WithResource(resource),
	)
	otel.SetTracerProvider(tp)
}

// Access global tracer provider
func getTracerProvider() trace.TracerProvider {
	return otel.GetTracerProvider()
}

TracerOption

Options for configuring a Tracer.

type TracerOption interface {
	// Has unexported methods.
}

Available Options:

// WithInstrumentationVersion sets the instrumentation version.
func WithInstrumentationVersion(version string) TracerOption

// WithSchemaURL sets the schema URL for the Tracer.
func WithSchemaURL(schemaURL string) TracerOption

// WithInstrumentationAttributes sets the instrumentation attributes.
// The passed attributes will be de-duplicated.
func WithInstrumentationAttributes(attr ...attribute.KeyValue) TracerOption

Example:

tracer := provider.Tracer(
	"github.com/myorg/myapp/database",
	trace.WithInstrumentationVersion("1.0.0"),
	trace.WithSchemaURL("https://opentelemetry.io/schemas/1.20.0"),
	trace.WithInstrumentationAttributes(
		attribute.String("db.system", "postgresql"),
	),
)

Tracer

The Tracer is responsible for creating Spans.

type Tracer interface {
	// 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
	// Span will be a child of that span, otherwise it will be a root span. This behavior
	// can be overridden by providing `WithNewRoot()` as a SpanOption, causing the
	// newly-created Span to be a root span even if `ctx` contains a Span.
	//
	// When creating a Span it is recommended to provide all known span attributes using
	// the `WithAttributes()` SpanOption as samplers will only have access to the
	// attributes provided when a Span is created.
	//
	// Any Span that is created MUST also be ended. This is the responsibility of the user.
	// Implementations of this API may leak memory or other resources if Spans are not ended.
	Start(ctx context.Context, spanName string, opts ...SpanStartOption) (context.Context, Span)
}

Creating Spans

// Basic span creation
ctx, span := tracer.Start(ctx, "operation-name")
defer span.End()

// With options
ctx, span := tracer.Start(
	ctx,
	"database-query",
	trace.WithSpanKind(trace.SpanKindClient),
	trace.WithAttributes(
		attribute.String("db.system", "postgresql"),
		attribute.String("db.name", "users"),
	),
)
defer span.End()

Span

The Span interface represents a single operation within a trace.

type Span interface {
	// 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
	// method has been called.
	End(options ...SpanEndOption)

	// AddEvent adds an event with the provided name and options.
	AddEvent(name string, options ...EventOption)

	// AddLink adds a link.
	// Adding links at span creation using WithLinks is preferred to calling AddLink
	// later, for contexts that are available during span creation, because head
	// sampling decisions can only consider information present during span creation.
	AddLink(link Link)

	// IsRecording returns the recording state of the Span. It will return
	// true if the Span is active and events can be recorded.
	IsRecording() bool

	// RecordError will record err as an exception span event for this span. An
	// additional call to SetStatus is required if the Status of the Span should
	// be set to Error, as this method does not change the Span status. If this
	// span is not being recorded or err is nil then this method does nothing.
	RecordError(err error, options ...EventOption)

	// SpanContext returns the SpanContext of the Span. The returned SpanContext
	// is usable even after the End method has been called for the Span.
	SpanContext() SpanContext

	// SetStatus sets the status of the Span in the form of a code and a
	// description, provided the status hasn't already been set to a higher
	// value before (OK > Error > Unset). The description is only included in a
	// status when the code is for an error.
	SetStatus(code codes.Code, description string)

	// SetName sets the Span name.
	SetName(name string)

	// SetAttributes sets kv as attributes of the Span. If a key from kv
	// already exists for an attribute of the Span it will be overwritten with
	// the value contained in kv.
	SetAttributes(kv ...attribute.KeyValue)

	// TracerProvider returns a TracerProvider that can be used to generate
	// additional Spans on the same telemetry pipeline as the current Span.
	TracerProvider() TracerProvider
}

Span Operations

Adding Attributes
import (
	"go.opentelemetry.io/otel/attribute"
	semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)

// Set individual attributes
span.SetAttributes(
	attribute.String("user.id", "12345"),
	attribute.Int("retry.count", 3),
	attribute.Bool("cache.hit", true),
)

// Use semantic conventions
span.SetAttributes(
	semconv.HTTPMethod("GET"),
	semconv.HTTPRoute("/api/users/:id"),
	semconv.HTTPStatusCode(200),
)
Recording Events
// Simple event
span.AddEvent("cache miss")

// Event with attributes
span.AddEvent("processing",
	trace.WithAttributes(
		attribute.Int("items.processed", 100),
		attribute.String("status", "success"),
	),
)

// Event with timestamp
span.AddEvent("checkpoint",
	trace.WithTimestamp(time.Now()),
	trace.WithAttributes(
		attribute.String("checkpoint.name", "validation-complete"),
	),
)
Recording Errors
import "go.opentelemetry.io/otel/codes"

// Record error with default options
if err != nil {
	span.RecordError(err)
	span.SetStatus(codes.Error, err.Error())
}

// Record error with stack trace
if err != nil {
	span.RecordError(err,
		trace.WithStackTrace(true),
		trace.WithAttributes(
			attribute.String("error.context", "database query"),
		),
	)
	span.SetStatus(codes.Error, "database operation failed")
}
Setting Status
import "go.opentelemetry.io/otel/codes"

// Success
span.SetStatus(codes.Ok, "operation completed")

// Error
span.SetStatus(codes.Error, "validation failed")

// Unset (default)
span.SetStatus(codes.Unset, "")
Updating Span Name
// Initial name
ctx, span := tracer.Start(ctx, "initial-name")
defer span.End()

// Update after more information is known
if route := determineRoute(); route != "" {
	span.SetName(route)
}
Checking Recording State
if span.IsRecording() {
	// Only compute expensive attributes if span is being recorded
	expensiveData := computeExpensiveData()
	span.SetAttributes(attribute.String("expensive.data", expensiveData))
}

Span Context

The SpanContext contains the trace and span identifiers, trace flags, and trace state.

SpanContext Type

type SpanContext struct {
	// Has unexported fields.
}

Creating SpanContext

type SpanContextConfig struct {
	TraceID    TraceID
	SpanID     SpanID
	TraceFlags TraceFlags
	TraceState TraceState
	Remote     bool
}

func NewSpanContext(config SpanContextConfig) SpanContext

Example:

traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7")

sc := trace.NewSpanContext(trace.SpanContextConfig{
	TraceID:    traceID,
	SpanID:     spanID,
	TraceFlags: trace.FlagsSampled,
	Remote:     true,
})

SpanContext Methods

// Validation
func (sc SpanContext) IsValid() bool
func (sc SpanContext) HasTraceID() bool
func (sc SpanContext) HasSpanID() bool

// Accessors
func (sc SpanContext) TraceID() TraceID
func (sc SpanContext) SpanID() SpanID
func (sc SpanContext) TraceFlags() TraceFlags
func (sc SpanContext) TraceState() TraceState

// Status checks
func (sc SpanContext) IsRemote() bool
func (sc SpanContext) IsSampled() bool

// Comparison
func (sc SpanContext) Equal(other SpanContext) bool

// Modification (returns new SpanContext)
func (sc SpanContext) WithTraceID(traceID TraceID) SpanContext
func (sc SpanContext) WithSpanID(spanID SpanID) SpanContext
func (sc SpanContext) WithTraceFlags(flags TraceFlags) SpanContext
func (sc SpanContext) WithTraceState(state TraceState) SpanContext
func (sc SpanContext) WithRemote(remote bool) SpanContext

// Serialization
func (sc SpanContext) MarshalJSON() ([]byte, error)

Example Usage:

// Get span context from span
sc := span.SpanContext()

// Check if valid
if sc.IsValid() {
	fmt.Printf("Trace ID: %s\n", sc.TraceID())
	fmt.Printf("Span ID: %s\n", sc.SpanID())
	fmt.Printf("Is Sampled: %v\n", sc.IsSampled())
}

// Create modified context
newSc := sc.WithTraceFlags(trace.FlagsSampled)

Extracting SpanContext from Context

// Get SpanContext from context.Context
func SpanContextFromContext(ctx context.Context) SpanContext

// Get Span from context.Context
func SpanFromContext(ctx context.Context) Span

Example:

// From context
sc := trace.SpanContextFromContext(ctx)
if sc.IsValid() {
	fmt.Println("Found valid span context")
}

// Get span and then context
span := trace.SpanFromContext(ctx)
sc = span.SpanContext()

TraceID and SpanID

TraceID

A 16-byte identifier for a trace.

type TraceID [16]byte

// Create from hex string
func TraceIDFromHex(h string) (TraceID, error)

// Methods
func (t TraceID) IsValid() bool
func (t TraceID) String() string
func (t TraceID) MarshalJSON() ([]byte, error)

Example:

// Parse from hex
traceID, err := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
if err != nil {
	log.Fatal(err)
}

// Validate
if traceID.IsValid() {
	fmt.Println("Valid trace ID:", traceID.String())
}

SpanID

An 8-byte identifier for a span.

type SpanID [8]byte

// Create from hex string
func SpanIDFromHex(h string) (SpanID, error)

// Methods
func (s SpanID) IsValid() bool
func (s SpanID) String() string
func (s SpanID) MarshalJSON() ([]byte, error)

Example:

// Parse from hex
spanID, err := trace.SpanIDFromHex("00f067aa0ba902b7")
if err != nil {
	log.Fatal(err)
}

// Validate
if spanID.IsValid() {
	fmt.Println("Valid span ID:", spanID.String())
}

TraceFlags

Flags that can be set on a SpanContext.

type TraceFlags byte

const (
	// FlagsSampled is a bitmask with the sampled bit set. A SpanContext
	// with the sampling bit set means the span is sampled.
	FlagsSampled = TraceFlags(0x01)
)

// Methods
func (tf TraceFlags) IsSampled() bool
func (tf TraceFlags) WithSampled(sampled bool) TraceFlags
func (tf TraceFlags) String() string
func (tf TraceFlags) MarshalJSON() ([]byte, error)

Example:

// Check if sampled
if sc.TraceFlags().IsSampled() {
	fmt.Println("Span is sampled")
}

// Set sampled flag
flags := trace.TraceFlags(0).WithSampled(true)
sc = sc.WithTraceFlags(flags)

TraceState

Vendor-specific trace identification information across distributed tracing systems.

type TraceState struct {
	// Has unexported fields.
}

// Parse from string
func ParseTraceState(ts string) (TraceState, error)

// Methods
func (ts TraceState) Get(key string) string
func (ts TraceState) Insert(key, value string) (TraceState, error)
func (ts TraceState) Delete(key string) TraceState
func (ts TraceState) Len() int
func (ts TraceState) String() string
func (ts TraceState) Walk(f func(key, value string) bool)
func (ts TraceState) MarshalJSON() ([]byte, error)

Example:

// Parse tracestate header
ts, err := trace.ParseTraceState("vendor1=value1,vendor2=value2")
if err != nil {
	log.Fatal(err)
}

// Get value
value := ts.Get("vendor1")

// Insert new value
ts, err = ts.Insert("myvendor", "myvalue")
if err != nil {
	log.Fatal(err)
}

// Delete value
ts = ts.Delete("vendor2")

// Walk all entries
ts.Walk(func(key, value string) bool {
	fmt.Printf("%s=%s\n", key, value)
	return true // continue
})

SpanKind

The role a Span plays in a trace.

type SpanKind int

const (
	// SpanKindUnspecified is an unspecified SpanKind and is not a valid
	// SpanKind. SpanKindUnspecified should be replaced with SpanKindInternal
	// if it is received.
	SpanKindUnspecified SpanKind = 0

	// SpanKindInternal is a SpanKind for a Span that represents an internal
	// operation within an application.
	SpanKindInternal SpanKind = 1

	// SpanKindServer is a SpanKind for a Span that represents the operation
	// of handling a request from a client.
	SpanKindServer SpanKind = 2

	// SpanKindClient is a SpanKind for a Span that represents the operation
	// of client making a request to a server.
	SpanKindClient SpanKind = 3

	// SpanKindProducer is a SpanKind for a Span that represents the operation
	// of a producer sending a message to a message broker. Unlike
	// SpanKindClient and SpanKindServer, there is often no direct
	// relationship between this kind of Span and a SpanKindConsumer kind. A
	// SpanKindProducer Span will end once the message is accepted by the
	// message broker which might not overlap with the processing of that
	// message.
	SpanKindProducer SpanKind = 4

	// SpanKindConsumer is a SpanKind for a Span that represents the operation
	// of a consumer receiving a message from a message broker. Like
	// SpanKindProducer Spans, there is often no direct relationship between
	// this Span and the Span that produced the message.
	SpanKindConsumer SpanKind = 5
)

func ValidateSpanKind(spanKind SpanKind) SpanKind
func (sk SpanKind) String() string

Usage:

// HTTP server
ctx, span := tracer.Start(
	ctx,
	"HTTP GET /users",
	trace.WithSpanKind(trace.SpanKindServer),
)

// HTTP client
ctx, span := tracer.Start(
	ctx,
	"GET https://api.example.com",
	trace.WithSpanKind(trace.SpanKindClient),
)

// Message producer
ctx, span := tracer.Start(
	ctx,
	"publish message",
	trace.WithSpanKind(trace.SpanKindProducer),
)

// Message consumer
ctx, span := tracer.Start(
	ctx,
	"process message",
	trace.WithSpanKind(trace.SpanKindConsumer),
)

// Internal operation
ctx, span := tracer.Start(
	ctx,
	"calculate-score",
	trace.WithSpanKind(trace.SpanKindInternal),
)

Span Options

SpanStartOption

Options for span creation.

type SpanStartOption interface {
	// Has unexported methods.
}

Available Start Options:

// WithSpanKind sets the SpanKind of a Span.
func WithSpanKind(kind SpanKind) SpanStartOption

// WithAttributes adds the attributes related to a span life-cycle event.
func WithAttributes(attributes ...attribute.KeyValue) SpanStartEventOption

// WithTimestamp sets the time of a Span or Event life-cycle moment.
func WithTimestamp(t time.Time) SpanEventOption

// 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

// 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

SpanEndOption

Options for ending a span.

type SpanEndOption interface {
	// Has unexported methods.
}

Available End Options:

// WithTimestamp sets the time of a Span or Event life-cycle moment.
func WithTimestamp(t time.Time) SpanEventOption

// WithStackTrace sets the flag to capture the error with stack trace.
func WithStackTrace(b bool) SpanEndEventOption

EventOption

Options for span events.

type EventOption interface {
	// Has unexported methods.
}

Available Event Options:

// WithAttributes adds the attributes related to a span life-cycle event.
func WithAttributes(attributes ...attribute.KeyValue) SpanStartEventOption

// WithTimestamp sets the time of a Span or Event life-cycle moment.
func WithTimestamp(t time.Time) SpanEventOption

// WithStackTrace sets the flag to capture the error with stack trace.
func WithStackTrace(b bool) SpanEndEventOption

Links

Links represent relationships between spans.

type Link struct {
	// SpanContext of the linked Span.
	SpanContext SpanContext

	// Attributes describe the aspects of the link.
	Attributes []attribute.KeyValue
}

// Create link from context
func LinkFromContext(ctx context.Context, attrs ...attribute.KeyValue) Link

Example:

// Create link from another span
link := trace.Link{
	SpanContext: otherSpan.SpanContext(),
	Attributes: []attribute.KeyValue{
		attribute.String("link.type", "dependency"),
	},
}

// Create span with link
ctx, span := tracer.Start(
	ctx,
	"batch-operation",
	trace.WithLinks(link),
)

// Add link during span lifetime
span.AddLink(trace.Link{
	SpanContext: anotherSpan.SpanContext(),
	Attributes: []attribute.KeyValue{
		attribute.String("link.reason", "causedBy"),
	},
})

Context Utilities

Storing Span in Context

// ContextWithSpan returns a copy of parent with span set as the current Span.
func ContextWithSpan(parent context.Context, span Span) context.Context

// ContextWithSpanContext returns a copy of parent with sc as the current Span.
// The Span implementation that wraps sc is non-recording and performs no
// operations other than to return sc as the SpanContext from the SpanContext
// method.
func ContextWithSpanContext(parent context.Context, sc SpanContext) context.Context

// ContextWithRemoteSpanContext returns a copy of parent with rsc set
// explicitly as a remote SpanContext and as the current Span. The Span
// implementation that wraps rsc is non-recording and performs no operations
// other than to return rsc as the SpanContext from the SpanContext method.
func ContextWithRemoteSpanContext(parent context.Context, rsc SpanContext) context.Context

Example:

// Store span in context
ctx = trace.ContextWithSpan(ctx, span)

// Store remote span context
remoteSpan := trace.ContextWithRemoteSpanContext(ctx, remoteSpanContext)

// Create non-recording span with context
nonRecording := trace.ContextWithSpanContext(ctx, spanContext)

Complete Usage Examples

HTTP Server Instrumentation

import (
	"net/http"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/propagation"
	semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
)

func instrumentedHandler(w http.ResponseWriter, r *http.Request) {
	tracer := otel.Tracer("http-server")

	// Extract context from incoming request
	ctx := otel.GetTextMapPropagator().Extract(
		r.Context(),
		propagation.HeaderCarrier(r.Header),
	)

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

	// Add event for request start
	span.AddEvent("request.started")

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

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

	// Set status based on response
	if statusCode >= 400 && statusCode < 500 {
		span.SetStatus(codes.Error, "client error")
	} else if statusCode >= 500 {
		span.SetStatus(codes.Error, "server error")
	} else {
		span.SetStatus(codes.Ok, "success")
	}

	w.WriteHeader(statusCode)
}

HTTP Client Instrumentation

func makeHTTPRequest(ctx context.Context, url string) (*http.Response, error) {
	tracer := otel.Tracer("http-client")

	// Start client span
	ctx, span := tracer.Start(
		ctx,
		"HTTP GET",
		trace.WithSpanKind(trace.SpanKindClient),
		trace.WithAttributes(
			semconv.HTTPMethod("GET"),
			semconv.HTTPURL(url),
		),
	)
	defer span.End()

	// Create request with context
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, "failed to create request")
		return nil, err
	}

	// Inject context into request headers
	otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))

	// Make request
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, "request failed")
		return nil, err
	}

	// Record response
	span.SetAttributes(semconv.HTTPStatusCode(resp.StatusCode))

	if resp.StatusCode >= 400 {
		span.SetStatus(codes.Error, "HTTP error")
	} else {
		span.SetStatus(codes.Ok, "success")
	}

	return resp, nil
}

Database Operation Tracing

func queryDatabase(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	tracer := otel.Tracer("database")

	// Start database span
	ctx, span := tracer.Start(
		ctx,
		"db.query",
		trace.WithSpanKind(trace.SpanKindClient),
		trace.WithAttributes(
			semconv.DBSystem("postgresql"),
			semconv.DBStatement(query),
			semconv.DBName("mydb"),
			semconv.NetPeerName("db.example.com"),
			semconv.NetPeerPort(5432),
		),
	)
	defer span.End()

	// Execute query
	rows, err := db.QueryContext(ctx, query, args...)
	if err != nil {
		span.RecordError(err, trace.WithAttributes(
			attribute.String("error.type", "query_failed"),
		))
		span.SetStatus(codes.Error, "database query failed")
		return nil, err
	}

	span.SetStatus(codes.Ok, "query successful")
	return rows, nil
}

Async Operation with Links

func processBatch(ctx context.Context, items []Item) {
	tracer := otel.Tracer("batch-processor")

	// Create parent span
	ctx, parentSpan := tracer.Start(ctx, "batch-processing")
	defer parentSpan.End()

	// Create links to all item spans
	var links []trace.Link
	for _, item := range items {
		// Each item has its own span context
		links = append(links, trace.Link{
			SpanContext: item.SpanContext,
			Attributes: []attribute.KeyValue{
				attribute.String("item.id", item.ID),
			},
		})
	}

	// Create batch span with links
	_, batchSpan := tracer.Start(
		ctx,
		"process-batch",
		trace.WithLinks(links...),
		trace.WithAttributes(
			attribute.Int("batch.size", len(items)),
		),
	)
	defer batchSpan.End()

	// Process items
	for _, item := range items {
		processItem(ctx, item)
	}

	batchSpan.SetStatus(codes.Ok, "batch completed")
}

Custom Root Span

func processUntrustedRequest(ctx context.Context) {
	tracer := otel.Tracer("security")

	// Create new root span, ignoring any parent context
	// This is useful for security boundaries
	ctx, span := tracer.Start(
		ctx,
		"untrusted-request",
		trace.WithNewRoot(),
		trace.WithAttributes(
			attribute.String("security.boundary", "external"),
		),
	)
	defer span.End()

	// Process request in new trace
	// ...
}

Configuration Types

The tracing API includes configuration types that group options for various operations. These are typically used internally by the SDK but are exposed in the public API for advanced use cases.

EventConfig

Groups options for span events.

type EventConfig struct {
	// Has unexported fields.
}

// NewEventConfig creates an EventConfig from EventOptions
func NewEventConfig(options ...EventOption) EventConfig

// Accessor methods
func (cfg *EventConfig) Attributes() []attribute.KeyValue
func (cfg *EventConfig) StackTrace() bool
func (cfg *EventConfig) Timestamp() time.Time

Note: Most users don't need to create EventConfig directly. It's used internally when calling span.AddEvent() or span.RecordError().

SpanConfig

Groups options for span creation and completion.

type SpanConfig struct {
	// Has unexported fields.
}

// NewSpanStartConfig creates a SpanConfig from SpanStartOptions
func NewSpanStartConfig(options ...SpanStartOption) SpanConfig

// NewSpanEndConfig creates a SpanConfig from SpanEndOptions
func NewSpanEndConfig(options ...SpanEndOption) SpanConfig

// Accessor methods
func (cfg *SpanConfig) Attributes() []attribute.KeyValue
func (cfg *SpanConfig) Links() []Link
func (cfg *SpanConfig) NewRoot() bool
func (cfg *SpanConfig) SpanKind() SpanKind
func (cfg *SpanConfig) StackTrace() bool
func (cfg *SpanConfig) Timestamp() time.Time

Note: Most users don't need to create SpanConfig directly. It's used internally when calling tracer.Start() and span.End().

TracerConfig

Groups options for tracer configuration.

type TracerConfig struct {
	// Has unexported fields.
}

// NewTracerConfig creates a TracerConfig from TracerOptions
func NewTracerConfig(options ...TracerOption) TracerConfig

// Accessor methods
func (t *TracerConfig) InstrumentationAttributes() attribute.Set
func (t *TracerConfig) InstrumentationVersion() string
func (t *TracerConfig) SchemaURL() string

Note: Most users don't need to create TracerConfig directly. It's used internally when calling TracerProvider.Tracer().

Best Practices

1. Always End Spans

ctx, span := tracer.Start(ctx, "operation")
defer span.End()

// Work here

2. Check if Recording Before Expensive Operations

if span.IsRecording() {
	expensiveAttr := computeExpensiveAttribute()
	span.SetAttributes(attribute.String("expensive", expensiveAttr))
}

3. Use Semantic Conventions

import semconv "go.opentelemetry.io/otel/semconv/v1.37.0"

span.SetAttributes(
	semconv.HTTPMethod("GET"),
	semconv.HTTPRoute("/api/users/:id"),
	semconv.HTTPStatusCode(200),
)

4. Record Errors Properly

if err != nil {
	span.RecordError(err)
	span.SetStatus(codes.Error, err.Error())
	return err
}

5. Pass Context Through Call Chain

func handler(ctx context.Context) {
	ctx, span := tracer.Start(ctx, "handler")
	defer span.End()

	// Pass context to downstream calls
	result := downstream(ctx)
	// ...
}

func downstream(ctx context.Context) Result {
	ctx, span := tracer.Start(ctx, "downstream")
	defer span.End()

	// Work here
	return result
}

6. Set Span Kind Appropriately

// Server handling requests
trace.WithSpanKind(trace.SpanKindServer)

// Client making requests
trace.WithSpanKind(trace.SpanKindClient)

// Producer sending messages
trace.WithSpanKind(trace.SpanKindProducer)

// Consumer receiving messages
trace.WithSpanKind(trace.SpanKindConsumer)

// Internal operations
trace.WithSpanKind(trace.SpanKindInternal)

7. Use Descriptive Span Names

// Good: Describes the operation
tracer.Start(ctx, "GET /api/users/:id")
tracer.Start(ctx, "database.query")
tracer.Start(ctx, "cache.get")

// Bad: Too generic
tracer.Start(ctx, "operation")
tracer.Start(ctx, "span")

8. Add Events for Significant Milestones

span.AddEvent("validation.started")
// validation logic
span.AddEvent("validation.completed")

span.AddEvent("cache.miss")
// fetch from database
span.AddEvent("data.fetched")

NoOp Implementation

For testing or when telemetry is disabled:

import "go.opentelemetry.io/otel/trace/noop"

// Create no-op tracer provider
tp := noop.NewTracerProvider()

// Use as normal - all operations are no-ops
tracer := tp.Tracer("test")
ctx, span := tracer.Start(ctx, "test")
defer span.End()

Related Documentation

  • SDK Trace: SDK implementation with sampling, batching, and exporters
  • Context Propagation: Propagating trace context across services
  • Attributes: Working with span attributes
  • Semantic Conventions: Standard attribute names
  • Exporters: Sending trace data to backends