Skip to main content
A Span represents a single operation within a trace. Spans can be nested to form a trace tree, where each trace contains a root span and zero or more child spans representing sub-operations.

Span Interface

The Span trait is defined in opentelemetry/src/trace/span.rs:50:
pub trait Span {
    fn add_event<T>(&mut self, name: T, attributes: Vec<KeyValue>)
    where T: Into<Cow<'static, str>>;

    fn add_event_with_timestamp<T>(
        &mut self,
        name: T,
        timestamp: SystemTime,
        attributes: Vec<KeyValue>,
    ) where T: Into<Cow<'static, str>>;

    fn span_context(&self) -> &SpanContext;
    fn is_recording(&self) -> bool;
    fn set_attribute(&mut self, attribute: KeyValue);
    fn set_status(&mut self, status: Status);
    fn update_name<T>(&mut self, new_name: T)
    where T: Into<Cow<'static, str>>;
    fn add_link(&mut self, span_context: SpanContext, attributes: Vec<KeyValue>);
    fn end(&mut self);
    fn end_with_timestamp(&mut self, timestamp: SystemTime);
}

Span Lifecycle

Creating Spans

Spans are created through a Tracer:
use opentelemetry::{global, trace::{Tracer, Span}};

let tracer = global::tracer("my-component");

// Create and start a span
let mut span = tracer.start("operation_name");

// Perform work...

// End the span
span.end();

Automatic Span Management

The in_span method handles the lifecycle automatically:
use opentelemetry::{global, trace::Tracer};

let tracer = global::tracer("my-component");

tracer.in_span("operation", |cx| {
    // Span is automatically started and active
    // Span ends automatically when closure returns
});

Recording State

Check if a span is recording before adding expensive data:
use opentelemetry::trace::Span;

if span.is_recording() {
    // Only compute expensive attributes if span is recording
    let expensive_value = compute_expensive_data();
    span.set_attribute(KeyValue::new("data", expensive_value));
}
From opentelemetry/src/trace/span.rs:98-108:
This flag may be true despite the entire trace being sampled out. This allows recording and processing of information about individual spans without sending it to the backend.

Span Attributes

Attributes are key-value pairs that provide additional context about the operation.

Setting Attributes

use opentelemetry::{trace::Span, KeyValue};

// Set a single attribute
span.set_attribute(KeyValue::new("http.method", "GET"));
span.set_attribute(KeyValue::new("http.status_code", 200));

// Set multiple attributes
span.set_attributes(vec![
    KeyValue::new("user.id", "12345"),
    KeyValue::new("user.role", "admin"),
]);

Attributes at Span Creation

Set attributes when creating the span:
use opentelemetry::trace::{Tracer, SpanKind};
use opentelemetry::KeyValue;

let span = tracer
    .span_builder("http_request")
    .with_attributes(vec![
        KeyValue::new("http.method", "POST"),
        KeyValue::new("http.url", "https://api.example.com/users"),
        KeyValue::new("http.target", "/users"),
    ])
    .start(&tracer);

Attribute Limits

Spans have configurable limits on the number of attributes. From opentelemetry-sdk/src/trace/span.rs:139-147:
fn set_attribute(&mut self, attribute: KeyValue) {
    let span_attribute_limit = self.span_limits.max_attributes_per_span as usize;
    self.with_data(|data| {
        if data.attributes.len() < span_attribute_limit {
            data.attributes.push(attribute);
        } else {
            data.dropped_attributes_count += 1;
        }
    });
}
Attributes beyond the limit are dropped, and the drop count is tracked. Configure limits via SpanLimits.

Span Events

Events are timestamped annotations that record something happening during the span’s lifetime.

Adding Events

use opentelemetry::{trace::Span, KeyValue};

// Simple event
span.add_event("cache_miss", vec![]);

// Event with attributes
span.add_event(
    "item_processed",
    vec![
        KeyValue::new("item.id", "12345"),
        KeyValue::new("item.type", "product"),
    ]
);

Events with Custom Timestamps

use std::time::SystemTime;

let timestamp = SystemTime::now();
span.add_event_with_timestamp(
    "database_query",
    timestamp,
    vec![KeyValue::new("query.duration_ms", 45)]
);

Recording Errors

The record_error method is a convenience for recording exceptions:
use opentelemetry::trace::Span;
use std::error::Error;

fn process_data(span: &mut impl Span) -> Result<(), Box<dyn Error>> {
    match do_something() {
        Ok(result) => Ok(result),
        Err(err) => {
            span.record_error(&*err);
            // Note: You must also set status to error
            span.set_status(Status::error(err.to_string()));
            Err(err)
        }
    }
}
From opentelemetry/src/trace/span.rs:72-77:
fn record_error(&mut self, err: &dyn Error) {
    if self.is_recording() {
        let attributes = vec![KeyValue::new("exception.message", err.to_string())];
        self.add_event("exception", attributes);
    }
}
record_error only adds an event. To mark the span as failed, you must also call set_status.

Span Status

The status indicates whether the operation succeeded or failed.

Status Values

From opentelemetry/src/trace/span.rs:281-296:
pub enum Status {
    /// The default status (operation has not been evaluated)
    Unset,

    /// The operation contains an error
    Error { description: Cow<'static, str> },

    /// The operation completed successfully
    Ok,
}

Setting Status

use opentelemetry::trace::{Span, Status};

// Mark as successful
span.set_status(Status::Ok);

// Mark as error with description
span.set_status(Status::error("Database connection failed"));

// Or with String
span.set_status(Status::error(format!("Invalid user: {}", user_id)));

Status Precedence

Statuses form a total order: Ok > Error > Unset
// Once set to Ok, status cannot be changed
span.set_status(Status::Ok);
span.set_status(Status::error("too late")); // Ignored

// Error overrides Unset
span.set_status(Status::error("failed"));
span.set_status(Status::Unset); // Ignored
Instrumentation libraries should generally not set Status::Ok. Only application code should mark operations as explicitly successful.

Span Context

The SpanContext contains immutable information about a span that can be propagated.

SpanContext Structure

From opentelemetry/src/trace/span_context.rs:294-300:
pub struct SpanContext {
    trace_id: TraceId,
    span_id: SpanId,
    trace_flags: TraceFlags,
    is_remote: bool,
    trace_state: TraceState,
}

Accessing SpanContext

use opentelemetry::trace::Span;

let span_context = span.span_context();

let trace_id = span_context.trace_id();
let span_id = span_context.span_id();
let is_sampled = span_context.is_sampled();
let is_valid = span_context.is_valid();
Links connect spans across different traces or within the same trace in a non-parent-child relationship.
use opentelemetry::trace::{Span, SpanContext};
use opentelemetry::KeyValue;

// Link to another span
span.add_link(
    other_span.span_context().clone(),
    vec![KeyValue::new("link.type", "related")]
);
Links are typically added when creating the span:
use opentelemetry::trace::{Tracer, Link};

let span = tracer
    .span_builder("batch_process")
    .with_links(vec![
        Link::new(
            span_context_1.clone(),
            vec![KeyValue::new("batch.index", 0)]
        ),
        Link::new(
            span_context_2.clone(),
            vec![KeyValue::new("batch.index", 1)]
        ),
    ])
    .start(&tracer);
Links added via add_link after span creation are not accessible to samplers. Add links at creation time when possible.

Span Kind

SpanKind describes the relationship between spans in a trace. From opentelemetry/src/trace/span.rs:226-255:
pub enum SpanKind {
    /// Client span - a request to a remote service
    Client,

    /// Server span - handling of a synchronous RPC or request
    Server,

    /// Producer span - initiator of an asynchronous request
    Producer,

    /// Consumer span - handler of an asynchronous request
    Consumer,

    /// Internal span - operation within the application
    Internal,
}

Setting Span Kind

use opentelemetry::trace::{Tracer, SpanKind};

// HTTP client request
let span = tracer
    .span_builder("GET /users")
    .with_kind(SpanKind::Client)
    .start(&tracer);

// HTTP server handler
let span = tracer
    .span_builder("POST /users")
    .with_kind(SpanKind::Server)
    .start(&tracer);

// Message producer
let span = tracer
    .span_builder("send_message")
    .with_kind(SpanKind::Producer)
    .start(&tracer);

Updating Span Name

Span names can be updated after creation:
use opentelemetry::trace::Span;

let mut span = tracer.start("unknown_route");

// Later, when the route is known:
span.update_name("/api/v1/users");
Sampling decisions based on the name depend on implementation and may not be re-evaluated after name changes.

Complete Example

Here’s a realistic example combining multiple span features:
use opentelemetry::{global, trace::{Tracer, Span, SpanKind, Status}, KeyValue};
use std::time::SystemTime;

fn process_order(order_id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let tracer = global::tracer("order-service");

    let mut span = tracer
        .span_builder("process_order")
        .with_kind(SpanKind::Internal)
        .with_attributes(vec![
            KeyValue::new("order.id", order_id.to_string()),
            KeyValue::new("service.name", "order-processor"),
        ])
        .start(&tracer);

    span.add_event("validation_started", vec![]);

    match validate_order(order_id) {
        Ok(_) => {
            span.add_event(
                "validation_succeeded",
                vec![KeyValue::new("validation.duration_ms", 42)]
            );
        }
        Err(e) => {
            span.record_error(&*e);
            span.set_status(Status::error(e.to_string()));
            span.end();
            return Err(e);
        }
    }

    // Process the order...
    span.add_event("order_processed", vec![
        KeyValue::new("items.count", 3),
        KeyValue::new("total.amount", 99.99),
    ]);

    span.set_status(Status::Ok);
    span.end();
    Ok(())
}

Best Practices

Choose span names that represent a general operation, not specific instances. Use get_user instead of get_user_12345. Put the specific ID in an attribute.
Before computing expensive attribute values, check if the span is recording to avoid unnecessary work.
Always set span status to error when an operation fails. Use record_error for the exception details and set_status to mark the span as failed.
Record events for important moments in the span’s lifecycle, like cache hits/misses, retries, or state changes.

Next Steps

Context

Learn about context propagation

Span Processors

Configure how spans are processed and exported

Build docs developers (and LLMs) love