Skip to content

Tracer API

The framework’s tracing API is a minimal abstraction over distributed tracing. Designed so that:

  • Tracing-off is zero-cost — the default NoopTracer short-circuits every call; no allocations, no async-storage lookups.
  • No SDK dependency — actor-ts doesn’t pull @opentelemetry/sdk-*; you bring your own.
  • W3C-compatible cross-wire — spans serialize to W3C traceparent for downstream OTel services.
import { ActorSystem, TracingExtensionId, OtelTracerAdapter } from 'actor-ts';
const system = ActorSystem.create('my-app');
system.extension(TracingExtensionId).configure({
tracer: new OtelTracerAdapter({ /* OTel SDK */ }),
});
// With tracing enabled, the framework auto-spans actor message handling.
// User code can also create custom spans:
const tracer = system.extension(TracingExtensionId).tracer;
const span = tracer.startSpan('process-order');
try {
await processOrder(...);
span.setStatus('ok');
} catch (e) {
span.recordException(e as Error);
span.setStatus('error', (e as Error).message);
throw e;
} finally {
span.end();
}
interface Tracer {
startSpan(name: string, opts?: SpanOptions): Span;
withActiveSpan<T>(span: Span, fn: () => T): T;
activeSpan(): Span | null;
injectContext(): TraceCarrier | null;
extractContext(carrier: TraceCarrier | null): SpanContext | null;
}
interface Span {
context(): SpanContext;
setAttribute(key: string, value: string | number | boolean): this;
setStatus(status: 'ok' | 'error', message?: string): this;
recordException(err: Error): this;
end(endTimeMs?: number): void;
readonly ended: boolean;
}

Minimal surface — close to OpenTelemetry’s API but smaller.

const span = tracer.startSpan('http-call', {
attributes: {
'http.method': 'GET',
'http.url': 'https://example.com',
},
kind: 'client',
});

SpanOptions:

FieldWhat
parentExplicit parent context. undefined → use active span. null → root span.
attributesInitial attributes.
kindinternal / server / client / producer / consumer.
startTimeMsOverride the start time.
const span = tracer.startSpan('outer');
await tracer.withActiveSpan(span, async () => {
const child = tracer.startSpan('inner'); // parent = outer (auto)
// ... work ...
child.end();
});
span.end();

withActiveSpan(span, fn) runs fn with span as the active span — child spans created inside auto-link as children. Uses AsyncLocalStorage under the hood, so the active span propagates across awaits.

activeSpan() reads the current active span; null outside any scope.

span.setAttribute('user.id', userId);
span.setAttribute('order.amount', order.amount);
span.setStatus('ok');
// or:
span.setStatus('error', 'payment-declined');
span.recordException(error);

Attributes are key/value pairs visible in your tracing backend. Status + exception turn the span red in most UIs.

// Sender side — inject context into a carrier
const carrier = tracer.injectContext();
sendToOtherService(payload, carrier);
// Receiver side — extract + create root from parent
const parentCtx = tracer.extractContext(carrier);
const span = tracer.startSpan('handle', { parent: parentCtx });

TraceCarrier is a key/value map; the framework’s cluster transport puts these into wire envelopes automatically. Outside the actor system (HTTP servers, broker messages), inject / extract manually to bridge tracing across services.

TracerWhen
NoopTracerDefault. Tracing disabled. Zero overhead.
RecordingTracerTests. Records every span in memory for assertions.
OtelTracerAdapterProduction. Pipes through OpenTelemetry SDK.

Production: configure the OTel adapter to export to your tracing backend (Jaeger, Tempo, Honeycomb, Datadog, etc.). Tests: use the recording tracer to assert specific spans were created.

const span = tracer.startSpan('work');
// ...
span.end();
// span.ended === true after end()
// further methods (setAttribute, end again) are no-ops

Spans must be ended — otherwise the tracer leaks memory. The framework auto-ends spans for actor message handling; user code is responsible for spans it creates.

A common pattern is try/finally:

const span = tracer.startSpan('work');
try {
return await doWork();
} catch (e) {
span.recordException(e as Error);
throw e;
} finally {
span.end();
}

The Tracer API reference covers the full surface.