Files
skills/docs/foundation/flow/retry/src/otel/retry-otel.md
2026-05-29 08:20:56 +01:00

4.1 KiB

retry-otel

(no submodules)

Purpose

Provides OpenTelemetry instrumentation helpers for @suaveplan/retry. This module defines the RetryMetrics interface and the createRetryMetrics factory that creates all four instruments from a caller-supplied OTel Meter. Instrument creation is lazy — no metrics are registered until createRetryMetrics is called — and the instruments are decoupled from the circuit breaker and retry engine, allowing callers to integrate metrics at the call site without modifying core retry logic.

The design follows the OTel semantic conventions for reliability metrics: counters track event totals, histograms capture distributions that matter for latency and load analysis.

Features

  • createRetryMetrics(meter) — creates all four instruments at once
  • recordAttempt — increments retry.attempt.total with outcome and strategy attributes
  • recordDelay — records to retry.attempt.delay_ms histogram with strategy attribute
  • recordCircuitStateChange — increments circuit_breaker.state_change.total with from/to attributes
  • recordCircuitCall — records to circuit_breaker.call.duration_ms histogram with outcome attribute
  • Fully tree-shakeable — each helper is a standalone function

Usage: Basic Example

import { metrics } from "@opentelemetry/api";
import { createRetryMetrics, recordAttempt } from "@suaveplan/retry";

const meter = metrics.getMeter("my-service");
const retryMetrics = createRetryMetrics(meter);

// After each attempt:
recordAttempt(retryMetrics, { outcome: "failure", strategy: "exponential" });

Usage: Advanced Example

import { metrics } from "@opentelemetry/api";
import {
  createRetryMetrics,
  recordAttempt,
  recordDelay,
  recordCircuitStateChange,
  recordCircuitCall,
  CircuitBreaker,
} from "@suaveplan/retry";

const meter = metrics.getMeter("payment-service");
const m = createRetryMetrics(meter);

const breaker = new CircuitBreaker({ failureThreshold: 5, successThreshold: 2, timeout: 10_000 });
breaker.on("stateChange", ({ from, to }) => recordCircuitStateChange(m, from, to));

// Instrument a call manually:
const start = Date.now();
try {
  const result = await breaker.call(() => chargeCard(payload));
  recordCircuitCall(m, Date.now() - start, "success");
  recordAttempt(m, { outcome: "success", strategy: "circuit-breaker" });
  return result;
} catch (err) {
  recordCircuitCall(m, Date.now() - start, "failure");
  recordAttempt(m, { outcome: "failure", strategy: "circuit-breaker" });
  throw err;
}

API Reference

createRetryMetrics(meter)

Parameter Type Description
meter Meter OTel Meter from @opentelemetry/api.

Returns: RetryMetrics — { attemptTotal, attemptDelayMs, circuitStateChangeTotal, circuitCallDurationMs }.

recordAttempt(metrics, opts)

Increments retry.attempt.total by 1. Attributes: outcome ("success" | "failure"), strategy (string, default "unknown").

recordDelay(metrics, delayMs, strategy?)

Records delayMs to retry.attempt.delay_ms. Attribute: strategy.

recordCircuitStateChange(metrics, from, to)

Increments circuit_breaker.state_change.total by 1. Attributes: from, to (both CircuitState).

recordCircuitCall(metrics, durationMs, outcome)

Records durationMs to circuit_breaker.call.duration_ms. Attribute: outcome.

Implementation Notes

All helper functions accept RetryMetrics as their first parameter rather than using module-level singletons, making them safe for multi-tenant applications that may configure multiple meters. The functions do not set up any automatic instrumentation — callers instrument their retry loops explicitly. This is intentional: wrapping retry() internally with OTel would couple the core engine to the OTel API and add mandatory overhead even when metrics are not needed. The strategy attribute defaults to "unknown" when not provided to ensure all recorded events have a value for that dimension, preventing sparse attribute cardinality issues in OTel backends.