# otel/instruments _(no submodules)_ ## Purpose The instruments module owns the OpenTelemetry counters and histogram emitted by every code path inside `@suaveplan/api-plugin-auth`. The bundle is lazily constructed against the global `MeterProvider` so operators can wire an SDK before the plugin loads without import-time side effects, and tests can call `resetAuthInstruments()` between suites to rebind against a fresh in-memory provider. ## Features - Five named instruments — `sessionsResolvedTotal`, `sessionResolveDurationMs`, `roleCheckTotal`, `signupsTotal`, `signinsTotal` — bundled in one `AuthInstruments` value. - Lazy global-provider binding via `metrics.getMeter(...)` so the instruments resolve at first call, never at import time. - `resetAuthInstruments()` for deterministic test setup. - Stable instrument names (`auth.*`) mirrored by the spec delta so operators can scrape known metric IDs without code inspection. ## Usage: Basic ```ts import { getAuthInstruments } from "@suaveplan/api-plugin-auth/otel/instruments"; const ins = getAuthInstruments(); ins.sessionsResolvedTotal.add(1, { outcome: "authenticated" }); ins.sessionResolveDurationMs.record(durationMs, { outcome: "authenticated" }); ``` ## Usage: Advanced For testing, install an in-memory `MeterProvider`, then reset the cached instruments so the next access binds against it. The plugin's own test polyfill (`tests/setup/polyfills.ts`) does exactly this for every test file in the package. ```ts import { metrics } from "@opentelemetry/api"; import { InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader, AggregationTemporality, } from "@opentelemetry/sdk-metrics"; import { getAuthInstruments, resetAuthInstruments, } from "@suaveplan/api-plugin-auth/otel/instruments"; const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE); const reader = new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 60_000 }); const provider = new MeterProvider({ readers: [reader] }); metrics.setGlobalMeterProvider(provider); resetAuthInstruments(); const ins = getAuthInstruments(); ins.signinsTotal.add(1, { outcome: "failure" }); await provider.forceFlush(); const collected = exporter.getMetrics(); ``` ## API Reference ### `interface AuthInstruments` - `sessionsResolvedTotal: Counter` — `auth.sessions_resolved_total`. - `sessionResolveDurationMs: Histogram` — `auth.session_resolve_duration_ms` (unit `ms`). - `roleCheckTotal: Counter` — `auth.role_check_total`. - `signupsTotal: Counter` — `auth.signups_total`. - `signinsTotal: Counter` — `auth.signins_total`. ### `getAuthInstruments(): AuthInstruments` Returns the cached bundle, building it on first call. Safe to call concurrently — the cache is single-writer because the function is synchronous. ### `resetAuthInstruments(): void` Drops the cache so the next `getAuthInstruments()` builds afresh against whatever `MeterProvider` is currently registered. ## Implementation Notes - The module deliberately reads `metrics.getMeter` lazily — calling it at import time would resolve the no-op meter before tests install their provider, which is exactly the failure mode that the `resetAuthInstruments()` helper exists to prevent. - Counter / histogram attribute keys (`outcome`) are documented per call site rather than enforced at the meter — OpenTelemetry's attribute API is intentionally untyped, but the call-site values are string literals (`"authenticated"`, `"anonymous"`, `"error"`, `"allowed"`, `"denied"`, `"success"`, `"failure"`) so dashboards can rely on the cardinality being bounded. - Histogram unit is `ms` so dashboards do not need to renormalise.