first commit
This commit is contained in:
95
docs/foundation/flow/retry/src/otel/retry-otel.md
Normal file
95
docs/foundation/flow/retry/src/otel/retry-otel.md
Normal 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.
|
||||
Reference in New Issue
Block a user