Distributed Tracing
Trace requests across services with spans, W3C traceparent, and auto-tracing.
Callback-based spans (recommended)
The simplest way to trace a unit of work. The span automatically ends when the callback finishes:
const result = await logger.startSpan("fetch-user", async (span) => {
const res = await fetch("https://api.example.com/users/123", {
headers: span.getHeaders(), // propagate trace context
})
return res.json()
})If the callback throws, the span is marked as "error". If it resolves, the span is marked as "ok". Duration is measured automatically.
Manual spans
For cases where callback-based spans don't fit (e.g., event-driven code):
const span = logger.startInactiveSpan("process-webhook")
try {
await processEvent(event)
span.end({ status: "ok" })
} catch (error) {
span.end({ status: "error", metadata: { error: error.message } })
throw error
}You must call span.end() yourself. Forgetting to end a span means it never gets sent to DeepTracer.
Nested spans
Both callback-based and manual spans support nesting:
await logger.startSpan("checkout", async (parentSpan) => {
// Callback-based child
await logger.startSpan("validate-cart", async () => {
await validateItems(cart)
})
// Manual child from an inactive span
const parent = logger.startInactiveSpan("process-payment")
const child = parent.startInactiveSpan("charge-card")
await chargeCard(card)
child.end()
parent.end()
})Child spans inherit the trace_id from their parent, creating a connected trace tree on the dashboard.
Propagating trace context
Use span.getHeaders() to pass trace IDs to downstream services:
await logger.startSpan("call-api", async (span) => {
const response = await fetch("https://api.myapp.com/data", {
headers: {
...span.getHeaders(),
"Content-Type": "application/json",
},
})
return response.json()
})The returned headers include:
| Header | Format | Example |
|---|---|---|
traceparent | W3C standard | 00-{traceId}-{spanId}-01 |
x-trace-id | DeepTracer | 0af7651916cd43dd8448eb211c80319c |
x-span-id | DeepTracer | b7ad6b7169203331 |
The downstream service can pick up the trace with logger.forRequest(request).
Auto-tracing in Next.js
When you use @deeptracer/nextjs, distributed tracing is enabled by default (autoTracing: true). The SDK:
- Registers an OpenTelemetry
SpanProcessorthat captures all Next.js spans (route handlers, server components, middleware, fetch calls) - Wraps
globalThis.fetchto create outgoing HTTP spans and injecttraceparentheaders
No per-route wrapping needed. Every request is traced automatically.
Restricting trace propagation
By default, traceparent headers are sent to all outgoing fetch URLs. Use tracePropagationTargets to restrict this to your own services:
export const { register, onRequestError } = init({
tracePropagationTargets: [
"https://api.myapp.com",
/^https:\/\/.*\.internal\.myapp\.com/,
],
})The wrap helper
Wrap any function with automatic tracing and error capture:
const tracedFetch = logger.wrap("fetch-data", async (url: string) => {
const res = await fetch(url)
return res.json()
})
const data = await tracedFetch("https://api.example.com/data")
// → creates a span named "fetch-data" with duration and status