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.idstartTime
Unix timestamp (milliseconds) when the span was created.
const span = tracer.startSpan('op');console.log(span.startTime); // 1705123456789endTime
Unix timestamp (milliseconds) when end() was called. undefined until ended.
const span = tracer.startSpan('op');console.log(span.endTime); // undefinedspan.end();console.log(span.endTime); // 1705123456799attributes
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): voidAdds 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 laterspan.setAttributes({ 'response.length': 200,});Naming Conventions
Use dot-notation for namespacing:
| Prefix | Usage |
|---|---|
user.* | User information |
request.* | Request metadata |
response.* | Response metadata |
prompt.* | Prompt details |
error.* | Error information |
model.* | Model configuration |
recordCost()
recordCost(cost: CostEvent): voidRecords 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(): voidMarks the span as complete and queues it for export.
const span = tracer.startSpan('operation');// ... work ...span.end(); // Sets endTime and adds to pending queueSpan 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-questionError 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.