# `otel/metrics` _(no submodules)_ ## Purpose `otel/metrics` is the OpenTelemetry instrumentation layer for `@suaveplan/sms`. It owns four meter instruments — three counters and a histogram — that `SmsService` populates on every send, retry, and failure. Wiring observability at the package level keeps consumers free from re-defining instrument names and units, and gives downstream dashboards a stable contract to plot against. The module reads the global `MeterProvider` from `@opentelemetry/api`. Tests that wire an in-memory exporter call `refreshSmsMeters()` after installing the exporter so subsequent `record*` calls land on the test provider rather than the load-time meter. ## Features - `sms.sent_total{channel, from_me}` counter — successful sends. - `sms.failed_total{code}` counter — failed sends, tagged by error code. - `sms.retry_total{attempt}` counter — retry attempts, tagged by zero-based attempt index. - `sms.send_duration_ms{outcome}` histogram — pipeline duration in milliseconds, outcome ∈ {`ok`, `error`, `aborted`}. - `refreshSmsMeters()` re-resolves every instrument against the current `MeterProvider` for test wiring. ## Usage: Basic ```ts import { recordSmsSent, recordSmsFailed, recordSmsRetry, recordSmsSendDuration, } from "@suaveplan/sms/otel"; const start = Date.now(); try { // ... send ... recordSmsSent("marketing", false); recordSmsSendDuration(Date.now() - start, "ok"); } catch (err) { recordSmsFailed("SMS.SEND_FAILED"); recordSmsSendDuration(Date.now() - start, "error"); } ``` ## Usage: Advanced ```ts import { MeterProvider, PeriodicExportingMetricReader, ConsoleMetricExporter, } from "@opentelemetry/sdk-metrics"; import { metrics } from "@opentelemetry/api"; import { refreshSmsMeters } from "@suaveplan/sms/otel"; const provider = new MeterProvider({ readers: [ new PeriodicExportingMetricReader({ exporter: new ConsoleMetricExporter(), }), ], }); metrics.setGlobalMeterProvider(provider); refreshSmsMeters(); // rebind every instrument against the new provider ``` ## API Reference - `function recordSmsSent(channel: string, fromMe: boolean): void` - `function recordSmsFailed(code: string): void` - `function recordSmsRetry(attempt: number): void` - `function recordSmsSendDuration(durationMs: number, outcome: "ok" | "error" | "aborted"): void` - `function refreshSmsMeters(): void` ## Implementation Notes Instruments are stored at module scope so the hot send path performs zero allocation per record call. The default no-op meter provided by `@opentelemetry/api` accepts every call silently, so packages that never install a `MeterProvider` pay nothing. Calling `refreshSmsMeters()` re-creates each instrument against the current global — necessary because instrument identity is bound to the meter instance, not the API surface. The attribute keys (`channel`, `from_me`, `code`, `attempt`, `outcome`) are part of the public observability contract; renames require a major version bump.