OpenTelemetry Integration
Distributed tracing for Node.js task execution — one span per attempt.
Distributed tracing for Node.js task execution — one span per attempt.
Peer dependency: @opentelemetry/api. Import from the @byteveda/flexiq/contrib/otel
subpath — it is not exported from the main barrel.
import { otelMiddleware } from "@byteveda/flexiq/contrib/otel";
queue.use(otelMiddleware());One span (a single traced operation, OpenTelemetry's unit of work) is created
per execution attempt, named flexiq.execute.<taskName>.
The span ends with status OK on success and ERROR (with the exception
recorded) on throw. A retry is a new attempt, so it produces a new span.
Trace context travels with the job, so a trace that crosses an enqueue stays one trace:
onEnqueue injects the current context into the job's
metadata as traceparent / tracestate, using the registered propagator.before extracts that context, and the execute span
(kind CONSUMER) becomes a child of the enqueuer's span.The Node OpenTelemetry API registers no propagator by default. Register a W3C one, or use the OpenTelemetry Node SDK, which does it for you:
import { propagation } from "@opentelemetry/api";
import { W3CTraceContextPropagator } from "@opentelemetry/core";
propagation.setGlobalPropagator(new W3CTraceContextPropagator());TaskContext.metadata and currentJob().metadata expose the job's raw
metadata string. The merge rule, coverage table and cross-SDK behaviour are on
Trace context across SDKs.
| Attribute | Value |
|---|---|
flexiq.job_id | The job's UUID |
flexiq.task_name | Registered task name |
| Option | Type | Default | Description |
|---|---|---|---|
tracerName | string | "flexiq" | Name passed to trace.getTracer() |
tracerProvider | TracerProvider | global provider | Provider to take the tracer from |
attributePrefix | string | "flexiq" | Prefix applied to span attribute keys |
spanName | (ctx) => string | — | Override the span name per execution |
extraAttributes | (ctx) => Attributes | — | Attach additional attributes to every span |
taskFilter | (taskName) => boolean | — | Return false to skip tracing for a task |
queue.use(
otelMiddleware({
tracerName: "my-service",
attributePrefix: "app",
spanName: (ctx) => `job.${ctx.taskName}`,
extraAttributes: (ctx) => ({ "app.env": process.env.NODE_ENV ?? "dev" }),
taskFilter: (name) => !name.startsWith("internal."),
}),
);otelMiddleware does not configure an exporter or a tracer provider. Set
those up with the OpenTelemetry SDK before calling queue.runWorker().
Hooks cannot wrap the handler, so the execute span is not made active for
the handler's own spans; an enqueue inside the handler still continues it.