Trace propagation enables distributed tracing by passing trace context between services, processes, and systems.
Overview
Propagation involves:
- Extracting trace context from incoming requests
- Injecting trace context into outgoing requests
- Maintaining parent-child relationships across services
- Preserving sampling decisions
sentry-trace
The primary header for Sentry tracing:
Format: {trace_id}-{span_id}-{sampled}
sentry-trace: 1234567890abcdef1234567890abcdef-1234567890abcdef-1
trace_id: 32 hex characters (128 bits)
span_id: 16 hex characters (64 bits)
sampled: 1 (sampled) or 0 (not sampled)
baggage
Contains dynamic sampling context:
Format: Key-value pairs separated by commas
baggage: sentry-trace_id=1234...,sentry-public_key=abc...,sentry-release=1.0.0
traceparent (W3C)
Optional W3C Trace Context standard:
Format: {version}-{trace_id}-{parent_id}-{flags}
traceparent: 00-1234567890abcdef1234567890abcdef-1234567890abcdef-01
Automatic Propagation
Sentry automatically propagates traces for HTTP requests:
import * as Sentry from '@sentry/node';
import { httpIntegration } from '@sentry/node';
Sentry.init({
dsn: 'your-dsn',
integrations: [
httpIntegration({
tracing: true // Enables automatic propagation
})
],
// Control which requests get tracing headers
tracePropagationTargets: [
'localhost',
/^https:\/\/api\.myapp\.com/
]
});
// Headers automatically added to outgoing requests
await fetch('https://api.myapp.com/users');
// Includes: sentry-trace, baggage headers
tracePropagationTargets
Control which outgoing requests receive tracing headers:
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'your-dsn',
tracePropagationTargets: [
// String matching (partial match)
'api.myapp.com',
'internal-service',
// RegExp matching
/^https:\/\/api\./,
/\.myapp\.com$/,
// Relative URLs (browser only)
/^\/api\//
]
});
Default Behavior:
- Node.js: All outgoing requests
- Browser: Same-origin requests only
Manual Propagation
Outgoing Requests
import * as Sentry from '@sentry/node';
import { spanToTraceHeader, spanToBaggageHeader } from '@sentry/core';
const span = Sentry.getActiveSpan();
if (span) {
const headers = {
'sentry-trace': spanToTraceHeader(span),
'baggage': spanToBaggageHeader(span)
};
await fetch('https://api.example.com/data', { headers });
}
Incoming Requests
import * as Sentry from '@sentry/node';
import type { Request, Response, NextFunction } from 'express';
app.use((req: Request, res: Response, next: NextFunction) => {
Sentry.continueTrace(
{
sentryTrace: req.headers['sentry-trace'] as string,
baggage: req.headers['baggage'] as string
},
() => {
// Request handling continues the trace
next();
}
);
});
continueTrace
Continue a trace from incoming headers:
export function continueTrace<V>(
{
sentryTrace,
baggage,
}: {
sentryTrace?: string;
baggage?: string | string[];
},
callback: () => V,
): V
The sentry-trace header value.
The baggage header value(s).
Function to execute with the continued trace.
Example:
import * as Sentry from '@sentry/node';
const result = Sentry.continueTrace(
{
sentryTrace: request.headers['sentry-trace'],
baggage: request.headers['baggage']
},
() => {
// Process request with continued trace
return Sentry.startSpan(
{ name: 'Process Request', op: 'http.server' },
() => processRequest()
);
}
);
Cross-Service Tracing
Service A (Origin)
import * as Sentry from '@sentry/node';
import { spanToTraceHeader, spanToBaggageHeader } from '@sentry/core';
await Sentry.startSpan(
{ name: 'Call Service B', op: 'http.client' },
async (span) => {
const response = await fetch('https://service-b.example.com/api', {
headers: {
'sentry-trace': spanToTraceHeader(span),
'baggage': spanToBaggageHeader(span)
}
});
return response.json();
}
);
Service B (Receiver)
import * as Sentry from '@sentry/node';
app.post('/api', (req, res) => {
Sentry.continueTrace(
{
sentryTrace: req.headers['sentry-trace'],
baggage: req.headers['baggage']
},
() => {
Sentry.startSpan(
{ name: 'Handle Request', op: 'http.server' },
async () => {
const result = await handleRequest(req);
res.json(result);
}
);
}
);
});
W3C Trace Context
Enable W3C traceparent header:
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'your-dsn',
propagateTraceparent: true,
tracePropagationTargets: ['example.com']
});
// Outgoing requests now include:
// - sentry-trace
// - baggage
// - traceparent (W3C)
CORS Considerations:
When enabling traceparent, ensure your server allows it:
// Server CORS config
res.setHeader(
'Access-Control-Allow-Headers',
'sentry-trace, baggage, traceparent'
);
Browser Propagation
Server-Side Rendering
Pass trace context from server to browser:
// Server (e.g., Next.js, Remix)
import * as Sentry from '@sentry/node';
import { spanToTraceHeader, getDynamicSamplingContextFromSpan } from '@sentry/core';
const span = Sentry.getActiveSpan();
if (span) {
const sentryTrace = spanToTraceHeader(span);
const dsc = getDynamicSamplingContextFromSpan(span);
const baggage = dynamicSamplingContextToSentryBaggageHeader(dsc);
// Inject into HTML
const html = `
<html>
<head>
<meta name="sentry-trace" content="${sentryTrace}" />
<meta name="baggage" content="${baggage}" />
</head>
<body>...</body>
</html>
`;
}
Client-Side
// Browser
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'your-dsn'
});
// Continue trace from meta tags
const sentryTrace = document
.querySelector('meta[name="sentry-trace"]')
?.getAttribute('content');
const baggage = document
.querySelector('meta[name="baggage"]')
?.getAttribute('content');
if (sentryTrace) {
Sentry.continueTrace({ sentryTrace, baggage }, () => {
// Browser continues server trace
});
}
Message Queues
Propagate traces through async message queues:
Producer
import * as Sentry from '@sentry/node';
import { spanToTraceHeader, spanToBaggageHeader } from '@sentry/core';
const span = Sentry.getActiveSpan();
if (span) {
const message = {
data: payload,
_sentry: {
trace: spanToTraceHeader(span),
baggage: spanToBaggageHeader(span)
}
};
await queue.publish(message);
}
Consumer
import * as Sentry from '@sentry/node';
queue.subscribe(async (message) => {
Sentry.continueTrace(
{
sentryTrace: message._sentry.trace,
baggage: message._sentry.baggage
},
() => {
Sentry.startSpan(
{ name: 'Process Message', op: 'queue.process' },
() => processMessage(message)
);
}
);
});
Database Connections
Propagate context through database operations:
import * as Sentry from '@sentry/node';
import { spanToTraceHeader } from '@sentry/core';
const span = Sentry.getActiveSpan();
if (span) {
// Add trace context as SQL comment
const traceComment = `/* sentry:${spanToTraceHeader(span)} */`;
const query = `${traceComment} SELECT * FROM users`;
await db.query(query);
}
Organization-Based Trace Continuation
Limit trace continuation to same organization:
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'your-dsn',
// Only continue traces from same org
traceContinuationOnlyIfSameOrg: true,
// Optional: explicitly set org ID
orgId: 'your-org-id'
});
// Traces from other orgs start new trace
Custom Propagation
Implement custom propagation logic:
import * as Sentry from '@sentry/node';
import type { Span } from '@sentry/types';
function propagateTraceToCustomProtocol(span: Span): Record<string, string> {
const context = span.spanContext();
return {
'X-Trace-ID': context.traceId,
'X-Span-ID': context.spanId,
'X-Sampled': context.traceFlags === 1 ? 'true' : 'false'
};
}
// Use with custom protocol
const span = Sentry.getActiveSpan();
if (span) {
const headers = propagateTraceToCustomProtocol(span);
await customProtocol.send({ headers, data });
}
Best Practices
1. Always Propagate Sampled Traces
const span = Sentry.getActiveSpan();
if (span && span.isRecording()) {
// Only add headers if span is sampled
headers['sentry-trace'] = spanToTraceHeader(span);
}
2. Handle Missing Headers Gracefully
app.use((req, res, next) => {
const sentryTrace = req.headers['sentry-trace'];
if (sentryTrace) {
Sentry.continueTrace({ sentryTrace }, () => next());
} else {
// Start new trace
next();
}
});
3. Be Careful with CORS
// Only propagate to controlled origins
Sentry.init({
tracePropagationTargets: [
/^https:\/\/api\.myapp\.com/,
// Don't add third-party APIs
]
});
function isValidSentryTrace(trace: string): boolean {
// Format: traceId-spanId-sampled
const pattern = /^[a-f0-9]{32}-[a-f0-9]{16}-[01]$/;
return pattern.test(trace);
}
if (sentryTrace && isValidSentryTrace(sentryTrace)) {
Sentry.continueTrace({ sentryTrace }, callback);
}