or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

embedded.mdindex.mdnoop.mdspan-context.mdspan-options.mdspan.mdtracer-provider-tracer.md
tile.json

span.mddocs/

Span Operations

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.

Span Interface

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
}

Getting Span from Context

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

Ending Spans

Always 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.

Adding Events

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:

  • Logging significant occurrences within a span
  • Recording state changes
  • Marking processing milestones

Adding Links

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

Usage Example

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

  1. Batch processing: Relating operations from multiple traces processed together
  2. Public endpoints: Linking untrusted parent contexts to new secure traces
  3. Fan-out/fan-in: Connecting parallel operations

Note: Prefer WithLinks() option at span creation for better sampling decisions.

Recording Errors

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 event
  • It does NOT change the span status automatically
  • Always call SetStatus() if you want to mark the span as failed
  • If the span is not recording or err is nil, does nothing

Error with Stack Trace

if 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
}

Setting Status

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

Setting Attributes

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:

  • If a key already exists, it will be overwritten
  • Attributes added after span creation are not seen by samplers
  • Use meaningful, standardized attribute names (see OpenTelemetry semantic conventions)

Updating Span Name

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

Checking Recording State

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:

  • It was sampled for export
  • The SDK is configured to record it

Non-recording spans still propagate context but don't record data.

Getting SpanContext

sc := span.SpanContext()
// sc is usable even after span.End()

if sc.IsValid() {
    traceID := sc.TraceID()
    spanID := sc.SpanID()
    // Use for correlation, logging, etc.
}

Getting TracerProvider from Span

tp := span.TracerProvider()
newTracer := tp.Tracer("additional-instrumentation")

This allows creating additional tracers on the same telemetry pipeline as the current span.

SpanKind

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

Usage Example

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

Complete Example

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
}