Tracing
Titan ships a minimal tracing layer that carries a W3C traceparent-
compatible context across async scopes via AsyncLocalStorage.
No OpenTelemetry SDK is required.
This is the foundation. For full observability (spans exported to a
collector, exemplars, sampling), layer the
titan-telemetry-relay or your own
OTel integration on top.
Public surface
import {
// From '@omnitron-dev/titan' main entry (re-exports a subset):
createSpanId,
createTraceId,
currentTrace,
extractTraceparent,
formatTraceparent,
parseTraceparent,
startSpan,
withTrace,
type TraceContext,
} from '@omnitron-dev/titan';
// From '@omnitron-dev/titan/tracing' (the full surface):
import {
INVALID_SPAN_ID,
INVALID_TRACE_ID,
TRACE_FLAGS,
makeTraceContext,
} from '@omnitron-dev/titan/tracing';
TraceContext
interface TraceContext {
readonly traceId: string; // 32 hex chars (16 bytes)
readonly spanId: string; // 16 hex chars (8 bytes)
readonly parentSpanId?: string; // undefined for a root span
readonly flags: number; // W3C trace-flags byte; bit 0 = sampled
}
The field is flags (not traceFlags), and there is no
traceState field — the type ships only the identifier portion of
W3C trace context. It is immutable; produce children with
startSpan() rather than mutating it.
Constants:
INVALID_TRACE_ID // '00000000000000000000000000000000'
INVALID_SPAN_ID // '0000000000000000'
TRACE_FLAGS.SAMPLED // 0x01
TRACE_FLAGS.NONE // 0x00 (there is no TRACE_FLAGS.NOT_SAMPLED)
Reading the active context
const trace = currentTrace(); // TraceContext | undefined
Returns undefined if no trace is active in the current async
scope.
Establishing a context
import { makeTraceContext, withTrace, TRACE_FLAGS } from '@omnitron-dev/titan/tracing';
await withTrace(
makeTraceContext({ traceId, spanId, flags: TRACE_FLAGS.SAMPLED }),
async () => {
await this.doWork(); // currentTrace() returns the bound context inside this scope
}
);
withTrace(ctx, fn) installs ctx for the duration of fn (sync or
async) via AsyncLocalStorage, then restores the previous context.
makeTraceContext is a convenience for building a context literal
with flags defaulted to SAMPLED.
Starting a span
import { startSpan, withTrace } from '@omnitron-dev/titan/tracing';
// startSpan(parent?) returns a NEW TraceContext (a child of the
// current trace, or of `parent` if given). It does not take a name
// or attributes, and does not return an `end()` callback.
const span = startSpan();
await withTrace(span, async () => {
await this.db.query(...);
});
startSpan(parent?) returns a fresh TraceContext: same traceId,
a new spanId, and parentSpanId set to the inherited span. If no
parent (and no current trace) exists, it mints a new root trace.
There is no span lifecycle, attributes, or end() here — this
module is propagation-only. Span timing/attributes/export belong to
a telemetry layer (the relay module or an OTel sink) on top.
W3C traceparent header
// Parse incoming header
const trace = extractTraceparent('00-T-S-01'); // returns TraceContext
// or:
const trace = parseTraceparent('00-T-S-01');
// Serialise for outgoing
const header = formatTraceparent(trace); // '00-T-S-01'
Netron uses these helpers internally to propagate trace context across RPC calls.
Manual propagation
AsyncLocalStorage does not propagate across worker threads,
queue boundaries, or some Promise pool implementations. Capture
and restore manually:
const captured = currentTrace();
worker.postMessage({ work, trace: captured });
// On the worker side:
worker.on('message', async ({ work, trace }) => {
await withTrace(trace, async () => {
await processWork(work);
});
});
Anti-patterns
- Reading the trace context manually inside business logic. Defeats the point. Trust the framework's propagation; let the trace context attach to logs and downstream calls automatically.
- Trace IDs in error payloads. Errors carry their own
structure. Trace correlation happens in the log aggregator by
traceId, not by passing the ID in the error. - Skipping
withTraceacross async boundaries. A trace context that doesn't propagate across a worker thread leaves the downstream work uncorrelated. Capture and restore.
→ Back to Titan Overview.