A Span represents an individual component of a trace - a single named and timed operation of a workflow. Spans are created by Tracers and must be ended by the user.
type Span interface {
// Users of the interface can ignore this. This embedded type is only used
// by implementations of this interface.
embedded.Span
// 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
}// SpanFromContext returns the current Span from ctx.
// If no Span is currently set in ctx an implementation of a Span that performs
// no operations is returned.
func SpanFromContext(ctx context.Context) SpanAlways call End() to complete a span. The recommended pattern is to defer it immediately:
ctx, span := tracer.Start(ctx, "operation")
defer span.End()You can also pass end options:
defer span.End(trace.WithTimestamp(customTime))After End() is called, updates to the span are not allowed, but SpanContext() remains usable.
Events are timestamped annotations on a span:
span.AddEvent("cache-hit",
trace.WithAttributes(
attribute.String("cache.key", "user:123"),
attribute.Int("cache.size", 1024),
),
)Common event use cases:
Links connect spans across traces or within the same trace:
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// Linking to a span from another trace
link := trace.Link{
SpanContext: remoteSpanContext,
Attributes: []attribute.KeyValue{
attribute.String("link.type", "batch-item"),
},
}
span.AddLink(link)
// Or create link from context
link := trace.LinkFromContext(otherCtx,
attribute.String("link.type", "related-operation"),
)
span.AddLink(link)Links are useful for:
Note: Prefer WithLinks() option at span creation for better sampling decisions.
if err := operation(); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "operation failed")
return err
}Key points:
RecordError() records the error as an exception eventSetStatus() if you want to mark the span as failedif err := operation(); err != nil {
span.RecordError(err,
trace.WithStackTrace(true),
trace.WithAttributes(
attribute.String("error.source", "database"),
),
)
span.SetStatus(codes.Error, "database operation failed")
return err
}// Status codes are from go.opentelemetry.io/otel/codes
const (
Unset codes.Code = 0 // Default status
Ok codes.Code = 1 // Operation completed successfully
Error codes.Code = 2 // Operation failed
)Status has a priority: Ok > Error > Unset. Once set to a higher value, it cannot be downgraded.
// Success case
span.SetStatus(codes.Ok, "")
// Error case
span.SetStatus(codes.Error, "validation failed: missing required field")The description is only meaningful for Error status.
Attributes add context to spans:
import "go.opentelemetry.io/otel/attribute"
span.SetAttributes(
attribute.String("http.method", "GET"),
attribute.String("http.url", "/api/users"),
attribute.Int("http.status_code", 200),
attribute.Bool("http.cached", true),
attribute.StringSlice("request.headers", []string{"Content-Type", "Authorization"}),
)Key points:
The span name can be updated after creation:
ctx, span := tracer.Start(ctx, "http-request")
defer span.End()
// Determine more specific operation name later
method := request.Method
path := request.URL.Path
span.SetName(fmt.Sprintf("%s %s", method, path))if span.IsRecording() {
// Only compute expensive attributes if span is being recorded
expensiveData := computeExpensiveMetrics()
span.SetAttributes(
attribute.String("detailed.metrics", expensiveData),
)
}A span is recording if:
Non-recording spans still propagate context but don't record data.
sc := span.SpanContext()
// sc is usable even after span.End()
if sc.IsValid() {
traceID := sc.TraceID()
spanID := sc.SpanID()
// Use for correlation, logging, etc.
}tp := span.TracerProvider()
newTracer := tp.Tracer("additional-instrumentation")This allows creating additional tracers on the same telemetry pipeline as the current span.
SpanKind indicates the role a span plays in a trace. Set at creation time via WithSpanKind() option:
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.
SpanKindProducer SpanKind = 4
// SpanKindConsumer is a SpanKind for a Span that represents the operation
// of a consumer receiving a message from a message broker.
SpanKindConsumer SpanKind = 5
)
// ValidateSpanKind returns a valid span kind value. This will coerce invalid
// values into the default value, SpanKindInternal.
func ValidateSpanKind(spanKind SpanKind) SpanKind
// String returns the specified name of the SpanKind in lower-case.
func (sk SpanKind) String() string// HTTP server handler
ctx, span := tracer.Start(ctx, "handleRequest",
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()
// Making HTTP client request
ctx, span := tracer.Start(ctx, "fetchUser",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
// Publishing to message queue
ctx, span := tracer.Start(ctx, "publishMessage",
trace.WithSpanKind(trace.SpanKindProducer),
)
defer span.End()
// Consuming from message queue
ctx, span := tracer.Start(ctx, "processMessage",
trace.WithSpanKind(trace.SpanKindConsumer),
)
defer span.End()
// Internal operation
ctx, span := tracer.Start(ctx, "processData",
trace.WithSpanKind(trace.SpanKindInternal),
)
defer span.End()package main
import (
"context"
"errors"
"fmt"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func processOrder(ctx context.Context, orderID string) error {
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "processOrder",
trace.WithAttributes(
attribute.String("order.id", orderID),
attribute.String("service.name", "order-processor"),
),
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()
// Add event
span.AddEvent("order-validation-started")
// Validate order
if err := validateOrder(ctx, orderID); err != nil {
span.RecordError(err,
trace.WithAttributes(
attribute.String("validation.step", "format-check"),
),
)
span.SetStatus(codes.Error, "order validation failed")
return fmt.Errorf("validation failed: %w", err)
}
span.AddEvent("order-validated")
// Process payment
if err := processPayment(ctx, orderID); err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.SetStatus(codes.Error, "payment processing failed")
return fmt.Errorf("payment failed: %w", err)
}
// Update span with final details
span.SetAttributes(
attribute.String("order.status", "completed"),
attribute.Int("order.items", 5),
)
span.SetStatus(codes.Ok, "")
return nil
}
func validateOrder(ctx context.Context, orderID string) error {
span := trace.SpanFromContext(ctx)
if span.IsRecording() {
// Only compute expensive validation metrics if recording
span.SetAttributes(
attribute.String("validation.type", "full"),
)
}
// Validation logic...
return nil
}
func processPayment(ctx context.Context, orderID string) error {
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "processPayment",
trace.WithSpanKind(trace.SpanKindClient),
)
defer span.End()
// Payment processing...
return nil
}