96 lines
4.1 KiB
Markdown
96 lines
4.1 KiB
Markdown
# retry-otel
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
## 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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.
|