Spans represent units of work in your application and are the building blocks of distributed tracing.
Overview
Spans track:
- Operation timing (start and end)
- Operation metadata (name, description)
- Attributes for filtering and analysis
- Parent-child relationships
- Status and outcome
Creating Spans
startSpan
Create an active span that automatically becomes the parent of nested spans.
import * as Sentry from '@sentry/node';
const result = Sentry.startSpan(
{ name: 'Operation', op: 'function' },
(span) => {
// Span is automatically finished when callback completes
return performOperation();
}
);
Configuration for the span.The span operation (e.g., 'http.client', 'db.query').
Additional span attributes.
Only create span if there’s a parent span.
Function to execute within the span context. Receives the span as argument.
Example:
import * as Sentry from '@sentry/node';
const data = await Sentry.startSpan(
{
name: 'fetchUserData',
op: 'http.client',
attributes: {
'http.method': 'GET',
'http.url': '/api/users'
}
},
async (span) => {
const response = await fetch('/api/users');
span.setAttribute('http.status_code', response.status);
return response.json();
}
);
startInactiveSpan
Create an inactive span that doesn’t automatically become the active parent.
import * as Sentry from '@sentry/node';
const span = Sentry.startInactiveSpan({
name: 'Background Task',
op: 'task'
});
try {
await performTask();
span.setStatus({ code: 1 }); // OK
} catch (error) {
span.setStatus({ code: 2 }); // ERROR
throw error;
} finally {
span.end();
}
startSpanManual
Create a span with manual control over when it finishes.
import * as Sentry from '@sentry/node';
const result = Sentry.startSpanManual(
{ name: 'Long Running Operation', op: 'task' },
(span, finish) => {
someAsyncOperation().then(() => {
finish(); // Manually finish the span
});
return result;
}
);
Span Interface
From packages/core/src/types-hoist/span.ts:
export interface Span {
spanContext(): SpanContextData;
end(endTimestamp?: SpanTimeInput): void;
setAttribute(key: string, value: SpanAttributeValue | undefined): this;
setAttributes(attributes: SpanAttributes): this;
setStatus(status: SpanStatus): this;
updateName(name: string): this;
isRecording(): boolean;
}
spanContext()
Get context data for the span.
const context = span.spanContext();
console.log(context.traceId); // Trace ID
console.log(context.spanId); // Span ID
32-character hex string representing the trace.
16-character hex string representing the span.
Trace flags (1 = sampled, 0 = not sampled).
setAttribute()
Set a single attribute on the span.
span.setAttribute('http.method', 'POST');
span.setAttribute('http.status_code', 200);
span.setAttribute('db.system', 'postgresql');
setAttributes()
Set multiple attributes at once.
span.setAttributes({
'http.method': 'GET',
'http.url': '/api/users',
'http.status_code': 200,
'user.id': '12345'
});
setStatus()
Set the span status.
import { SPAN_STATUS_OK, SPAN_STATUS_ERROR } from '@sentry/core';
// Success
span.setStatus({ code: SPAN_STATUS_OK });
// Error
span.setStatus({
code: SPAN_STATUS_ERROR,
message: 'Connection timeout'
});
Status Codes:
0 - Unset
1 - OK
2 - Error
updateName()
Update the span name.
span.updateName('GET /api/users/123');
Note: Use Sentry.updateSpanName(span, name) to ensure the name persists through instrumentation.
end()
End the span.
// End with current timestamp
span.end();
// End with custom timestamp
span.end(Date.now());
isRecording()
Check if the span is recording.
if (span.isRecording()) {
span.setAttribute('debug', 'true');
}
Span Attributes
export type SpanAttributeValue =
| string
| number
| boolean
| Array<null | undefined | string>
| Array<null | undefined | number>
| Array<null | undefined | boolean>;
export type SpanAttributes = Partial<{
'sentry.origin': string;
'sentry.op': string;
'sentry.source': TransactionSource;
'sentry.sample_rate': number;
}> & Record<string, SpanAttributeValue | undefined>;
Semantic Attributes
Use semantic conventions for consistency:
HTTP Attributes:
span.setAttributes({
'http.method': 'GET',
'http.url': 'https://api.example.com/users',
'http.status_code': 200,
'http.request.body.size': 1024,
'http.response.body.size': 4096
});
Database Attributes:
span.setAttributes({
'db.system': 'postgresql',
'db.name': 'myapp',
'db.statement': 'SELECT * FROM users WHERE id = $1',
'db.operation': 'SELECT'
});
Custom Attributes:
span.setAttributes({
'user.id': '123',
'feature.flag': 'new-ui',
'cache.hit': true,
'retry.count': 3
});
Nested Spans
Create parent-child relationships:
import * as Sentry from '@sentry/node';
await Sentry.startSpan(
{ name: 'Parent Operation', op: 'task' },
async (parentSpan) => {
// This span is automatically a child of parentSpan
await Sentry.startSpan(
{ name: 'Child Operation 1', op: 'http' },
async () => {
await fetch('/api/endpoint1');
}
);
await Sentry.startSpan(
{ name: 'Child Operation 2', op: 'db' },
async () => {
await db.query('SELECT * FROM users');
}
);
}
);
Error Handling
import * as Sentry from '@sentry/node';
await Sentry.startSpan(
{ name: 'Operation', op: 'task' },
async (span) => {
try {
await riskyOperation();
span.setStatus({ code: 1 }); // OK
} catch (error) {
span.setStatus({
code: 2,
message: error.message
});
// Error is automatically captured
throw error;
}
}
);
Async Operations
import * as Sentry from '@sentry/node';
// Sequential operations
await Sentry.startSpan(
{ name: 'Process Order', op: 'task' },
async () => {
await Sentry.startSpan({ name: 'Validate', op: 'validation' }, () =>
validateOrder(order)
);
await Sentry.startSpan({ name: 'Process Payment', op: 'payment' }, () =>
processPayment(order)
);
await Sentry.startSpan({ name: 'Send Confirmation', op: 'email' }, () =>
sendEmail(order)
);
}
);
// Parallel operations
await Sentry.startSpan(
{ name: 'Fetch Data', op: 'task' },
async () => {
await Promise.all([
Sentry.startSpan({ name: 'Fetch Users', op: 'http' }, () =>
fetchUsers()
),
Sentry.startSpan({ name: 'Fetch Products', op: 'http' }, () =>
fetchProducts()
),
Sentry.startSpan({ name: 'Fetch Orders', op: 'http' }, () =>
fetchOrders()
)
]);
}
);
Getting Active Span
import * as Sentry from '@sentry/node';
Sentry.startSpan({ name: 'Parent', op: 'task' }, () => {
const activeSpan = Sentry.getActiveSpan();
if (activeSpan) {
console.log('Active span:', activeSpan.spanContext().spanId);
activeSpan.setAttribute('custom', 'value');
}
// Nested operation
Sentry.startSpan({ name: 'Child', op: 'subtask' }, () => {
const childSpan = Sentry.getActiveSpan();
// childSpan is now the active span
});
});
Span JSON
Spans are serialized as:
export interface SpanJSON {
data: SpanAttributes;
description?: string;
op?: string;
parent_span_id?: string;
span_id: string;
start_timestamp: number;
status?: string;
timestamp?: number;
trace_id: string;
origin?: SpanOrigin;
profile_id?: string;
exclusive_time?: number;
measurements?: Measurements;
is_segment?: boolean;
segment_id?: string;
}
Best Practices
1. Use Descriptive Names
// Bad
Sentry.startSpan({ name: 'op', op: 'task' }, () => {});
// Good
Sentry.startSpan({ name: 'Process Payment', op: 'payment' }, () => {});
2. Set Appropriate Operations
// HTTP requests
{ name: 'GET /api/users', op: 'http.client' }
// Database queries
{ name: 'SELECT users', op: 'db.query' }
// Function calls
{ name: 'processOrder', op: 'function' }
// Background tasks
{ name: 'Send Emails', op: 'task' }
3. Add Relevant Attributes
Sentry.startSpan(
{
name: 'Database Query',
op: 'db.query',
attributes: {
'db.system': 'postgresql',
'db.statement': query,
'db.name': 'production'
}
},
() => executeQuery()
);
4. Handle Errors Properly
await Sentry.startSpan({ name: 'Operation' }, async (span) => {
try {
await operation();
} catch (error) {
span.setStatus({ code: 2, message: error.message });
throw error;
}
});