OpenTelemetry
instrumentOpenTelemetry() exports the telemetry of every pipeline in the process to OpenTelemetry: traces with a span per execution and per attempt, and four metrics. One call at startup; no change to your pipelines.
npm i resilience-pipeline @opentelemetry/api # @opentelemetry/api ^1.4 (optional peer dependency), plus the SDK of your choiceimport { NodeSDK } from '@opentelemetry/sdk-node';import { instrumentOpenTelemetry } from 'resilience-pipeline/otel';
const sdk = new NodeSDK({ /* exporters, resource… */ });sdk.start();
// After the SDK has registered its global providers.const stop = instrumentOpenTelemetry();
process.on('SIGTERM', async () => { stop(); await sdk.shutdown();});Traces
Section titled “Traces”Each execution gets a span, and each call of your callback gets a child span:
resilience.pipeline payments pipeline.name=payments (ERROR if the final outcome failed)├─ resilience.attempt attempt.number=0 (ERROR: 503)│ event: resilience.OnRetry├─ resilience.attempt attempt.number=1│ └─ HTTP POST ← your own spans nest under the attempt- Execution span:
resilience.pipeline <name>, where name is the execution’s operation name (execute(fn, { name })) or the pipeline name; plainresilience.pipelinewhen neither is set. It covers retries, delays and fallbacks. - Attempt span:
resilience.attempt, one per call of the callback (the first call, each retry, each hedged attempt), made active while the callback runs. Spans you start inside, such as an instrumentedfetchor database call, become its children. - Failures set the span status to
ERROR, record the exception and seterror.type. - Strategy events (retry, timeout, circuit opened, rate limiter rejected…) are added as span events named
resilience.<Event>to the span active when they fire, withstrategy.nameandevent.name.
Nesting needs a context manager, which every OpenTelemetry Node SDK registers.
Span attributes
Section titled “Span attributes”| Attribute | When |
|---|---|
pipeline.name |
always ('' for an unnamed pipeline) |
operation.name |
when the execution has a name |
operation.key |
when the execution has an operationKey |
the pipeline’s tags |
new ResiliencePipelineBuilder({ name, tags: { team: 'billing' } }) |
the execution’s attributes |
execute(fn, { attributes: { tenant: 'acme' } }) |
your attributes() result |
see options |
attempt.number |
attempt spans |
error.type |
failed spans |
Metrics
Section titled “Metrics”| Name | Instrument | Unit | Attributes | What |
|---|---|---|---|---|
resilience.pipeline.duration |
histogram | s |
base + outcome, error.type |
One sample per execution, retries and delays included. |
resilience.attempt.duration |
histogram | s |
base + attempt.number, outcome, error.type |
One sample per call of the callback. Its count is the number of attempts. |
resilience.strategy.events |
counter | base + strategy.name, event.name |
Retries, timeouts, circuit transitions, rejections, fallbacks, cache hits… | |
resilience.circuit.state |
observable gauge | pipeline.name, strategy.name |
0 closed, 1 half-open, 2 open, 3 isolated. |
- Base attributes:
pipeline.name,operation.key(''when not set),operation.namewhen set, the pipeline’stags, the execution’sattributes, and the result of yourattributes()option. outcomeissuccessorfailure.error.typeis set on failures: the error’scodeif it has one (TIMEOUT,CIRCUIT_OPEN,ECONNRESET…), else its class name, else itstypeof. The same function is exported aserrorType(error).- Durations are recorded in seconds, as the semantic conventions ask, with bucket advice
0.005 … 30s (OpenTelemetry’s default buckets are meant for milliseconds). - The counter skips the per-call events (
OnPipelineExecuting,OnStrategyExecuted,OnPipelineExecuted,OnExecutionAttempt); those feed the histograms. Event names are listed in the events catalog. attempt.numberon the histogram is capped at10(it means the 10th retry or later), so a largemaxRetryAttemptscan’t create unbounded series. Attempt spans keep the exact number.- The circuit gauge reports a breaker from its first state change (opened, half-opened, closed, or synced from a shared store) on. It keeps the 1000 most recently changed circuits; older ones (e.g. per-host pipelines that
createResilientFetchdropped) stop being reported.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
meter |
Meter |
metrics.getMeter('resilience-pipeline') |
Meter to create the instruments on. |
tracer |
Tracer |
trace.getTracer('resilience-pipeline') |
Tracer for the spans. |
metrics |
boolean |
true |
Record metrics. |
traces |
boolean |
true |
Create execution and attempt spans. |
spanEvents |
boolean |
true |
Add strategy events to the active span. Works with traces: false too, on your own spans. |
attributes |
(input) => Attributes | undefined |
none | Extra attributes for every span and data point. input is a telemetry event or the execution info; both have context and source. |
instrumentOpenTelemetry({ attributes: ({ context }) => ({ tenant: String(context.properties.get('tenant') ?? 'none') }),});instrumentOpenTelemetry() returns a function that stops it (removes the span hooks, the event listener and the gauge callback).
Related
Section titled “Related”- Telemetry model: events,
onTelemetry,instrument,diagnostics_channel. - Prometheus, createStats
- Events catalog