OpenTelemetry
One trace per run, one span per step. Bridge the engine's lifecycle events to OpenTelemetry and see workflows in Jaeger, Grafana or Datadog.
@dudousxd/nestjs-durable-otel turns workflow runs into OpenTelemetry traces — a root span per
run, a child span per step — so they show up in the tracing stack you already run.
pnpm add @dudousxd/nestjs-durable-otel @opentelemetry/apiimport { attachDurableOtel } from '@dudousxd/nestjs-durable-otel';
// after the module boots, with the engine in hand:
const detach = attachDurableOtel(engine); // uses the global tracer providerEach step span carries durable.run_id, durable.step.seq and durable.step.kind; a failed run
marks its span with an error status. Step spans are timed from the engine's durationMs, so
latencies are accurate.
It subscribes to the same engine lifecycle events the dashboard and Telescope watcher use — three views of one event log. OTel is for debugging in production (latency, correlation, alerts); the dashboard is for operating the workflow.
Distributed tracing across workers
A remote step runs in another process — often another language (the Python SDK).
Left alone, that worker would start a fresh, detached trace, so the work it does wouldn't show up
under the run's root span. To keep one trace across the boundary, the engine stamps a W3C
traceparent on each dispatched RemoteTask, taken from an optional traceparent provider. The
worker reads it and continues the trace instead of starting a new one — the step's span lands as
a child of the workflow's span, even across processes and languages.
Core stays OTel-free (it never imports @opentelemetry/api), so you wire the provider in. Use
otelTraceparent from @dudousxd/nestjs-durable-otel, which reads the current active span via the
globally-registered W3C propagator:
import { WorkflowEngine } from '@dudousxd/nestjs-durable-core';
import { otelTraceparent } from '@dudousxd/nestjs-durable-otel';
const engine = new WorkflowEngine({
store,
transport,
traceparent: () => otelTraceparent(),
});With the NestJS module, pass it as the traceparent option:
import { otelTraceparent } from '@dudousxd/nestjs-durable-otel';
DurableModule.forRoot({
store,
transport,
traceparent: () => otelTraceparent(),
});The provider is just () => string | undefined, so you can supply your own context reader instead of
the OTel one. Omit it and no traceparent is sent (the worker starts its own trace as before). The
stamped value travels in the documented RemoteTask.traceparent field, so any language SDK that
honours W3C trace context picks it up automatically.