first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# 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.