Skip to content

Spans

A span represents a unit of work in your AI pipeline. Spans capture timing, attributes, and cost data, forming the building blocks of traces.

LadgerSpan Interface

interface LadgerSpan {
id: string;
name: string;
parentId?: string;
startTime: number;
endTime?: number;
attributes: SpanAttributes;
costEvent?: CostEvent;
setAttributes(attrs: SpanAttributes): void;
recordCost(cost: CostEvent): void;
end(): void;
}

Creating Spans

Basic Span

const span = tracer.startSpan('operation-name');
// ... perform work ...
span.end();

With Parent (Nested Spans)

const parent = tracer.startSpan('parent-operation');
const child = tracer.startSpan('child-operation', {
parent: parent
});
child.end();
parent.end();

Using trace() Helper

const result = await tracer.trace('operation', async (span) => {
// span is automatically ended
return await doWork();
});

Span Properties

id

Unique identifier for the span.

const span = tracer.startSpan('my-span');
console.log(span.id); // "lz4k2m8-abc123def"

name

Human-readable name describing the operation.

const span = tracer.startSpan('classify-intent');
console.log(span.name); // "classify-intent"

parentId

ID of the parent span (if nested).

const parent = tracer.startSpan('parent');
const child = tracer.startSpan('child', { parent });
console.log(child.parentId); // parent.id

startTime

Unix timestamp (milliseconds) when the span was created.

const span = tracer.startSpan('op');
console.log(span.startTime); // 1705123456789

endTime

Unix timestamp (milliseconds) when end() was called. undefined until ended.

const span = tracer.startSpan('op');
console.log(span.endTime); // undefined
span.end();
console.log(span.endTime); // 1705123456799

attributes

Custom key-value data attached to the span.

const span = tracer.startSpan('op');
span.setAttributes({ 'user.id': '123' });
console.log(span.attributes); // { 'user.id': '123' }

costEvent

Cost/usage data for the operation.

const span = tracer.startSpan('op');
span.recordCost({ provider: 'openai', model: 'gpt-4o', inputTokens: 100 });
console.log(span.costEvent); // { provider: 'openai', ... }

Methods

setAttributes()

setAttributes(attrs: SpanAttributes): void

Adds or updates span attributes. Attributes are merged with existing values.

SpanAttributes Type

interface SpanAttributes {
[key: string]: string | number | boolean | undefined;
}

Example

const span = tracer.startSpan('process-request');
span.setAttributes({
'user.id': 'user-123',
'request.type': 'chat',
'prompt.length': 150,
'is_premium': true,
});
// Add more attributes later
span.setAttributes({
'response.length': 200,
});

Naming Conventions

Use dot-notation for namespacing:

PrefixUsage
user.*User information
request.*Request metadata
response.*Response metadata
prompt.*Prompt details
error.*Error information
model.*Model configuration

recordCost()

recordCost(cost: CostEvent): void

Records AI usage and cost data.

See Cost Tracking for detailed documentation.

span.recordCost({
provider: 'openai',
model: 'gpt-4o',
inputTokens: 100,
outputTokens: 50,
costUsd: 0.0025,
});

end()

end(): void

Marks the span as complete and queues it for export.

const span = tracer.startSpan('operation');
// ... work ...
span.end(); // Sets endTime and adds to pending queue

Span Lifecycle

┌──────────────────────────────────────────────────────────────┐
│ Span Lifecycle │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. startSpan() 2. setAttributes() 3. recordCost()│
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌───────────┐ ┌──────────┐ │
│ │ Created │ ────────► │ Recording │ ───────► │ Recording │ │
│ │ in mem │ │ attributes│ │ cost data │ │
│ └─────────┘ └───────────┘ └──────────┘ │
│ │ │
│ 4. end() │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ Queued │ │
│ │ export │ │
│ └──────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘

Nested Spans (Trace Hierarchy)

Create parent-child relationships to visualize your AI pipeline:

async function handleRequest(message: string) {
const rootSpan = tracer.startSpan('handle-request');
try {
// Step 1: Classify
const classifySpan = tracer.startSpan('classify-intent', {
parent: rootSpan
});
const intent = await classifyIntent(message);
classifySpan.setAttributes({ 'intent.result': intent });
classifySpan.end();
// Step 2: Route based on intent
const routeSpan = tracer.startSpan('route-request', {
parent: rootSpan
});
if (intent === 'question') {
// Step 2a: Answer question
const answerSpan = tracer.startSpan('answer-question', {
parent: routeSpan
});
const answer = await answerQuestion(message);
answerSpan.end();
}
routeSpan.end();
} finally {
rootSpan.end();
}
}

This creates a trace hierarchy:

handle-request (root)
├── classify-intent
└── route-request
└── answer-question

Error Handling

The trace() helper automatically captures errors:

try {
await tracer.trace('risky-operation', async (span) => {
throw new Error('Something went wrong');
});
} catch (error) {
// Error is re-thrown after recording
}
// Span attributes include:
// { 'error.type': 'Error', 'error.message': 'Something went wrong' }

For manual spans, record errors explicitly:

const span = tracer.startSpan('operation');
try {
await riskyOperation();
} catch (error) {
span.setAttributes({
'error.type': error.name,
'error.message': error.message,
'error.stack': error.stack,
});
throw error;
} finally {
span.end();
}

Serialization

Spans are serialized for API transport:

interface SerializedSpan {
id: string;
name: string;
parentId?: string;
startTime: number;
endTime?: number;
attributes: SpanAttributes;
costEvent?: CostEvent;
}

The serialized format is sent to /v1/ingest in batches.