# WebAuthn observability — metrics _(no submodules)_ ## Purpose Defines every OTel instrument emitted by `@suaveplan/webauthn`. The registry is a single bag of pre-created `Histogram` / `Counter` instances cached at module-load time so production paths can record without per-call allocation. The cache resets via `resetMetricsForTesting()` so a fresh in-memory `MeterProvider` registered in `beforeEach` produces deterministic assertions. ## Features - Five instruments cover every production path: `webauthn.register_duration_ms`, `webauthn.authenticate_duration_ms`, `webauthn.outcome_total`, `webauthn.attestation_format_total`, `webauthn.errors_total`. - Bounded label cardinality: `outcome` ∈ `{ ok | error | aborted }`, `op` ∈ `{ register | authenticate }`, `fmt` ∈ the seven IANA-registered formats plus `unknown`. - `recordRegister` / `recordAuthenticate` write both the duration histogram and the outcome counter atomically so dashboards never disagree. - `resetMetricsForTesting` / `getInstrumentsForTesting` keep test hooks out of the runtime module surface but available to spec tests. ## Usage: Basic Example ```ts import { nowMs, recordRegister, } from "@suaveplan/webauthn/observability/metrics"; const start = nowMs(); try { await doRegister(); recordRegister("ok", nowMs() - start); } catch (err) { recordRegister("error", nowMs() - start); throw err; } ``` ## Usage: Advanced Example ```ts import { MeterProvider, InMemoryMetricExporter } from "@opentelemetry/sdk-metrics"; import { metrics } from "@opentelemetry/api"; import { resetMetricsForTesting, recordAttestationFormat, } from "@suaveplan/webauthn/observability/metrics"; const exporter = new InMemoryMetricExporter(); const provider = new MeterProvider({ readers: [/* periodic reader */] }); metrics.setGlobalMeterProvider(provider); resetMetricsForTesting(); recordAttestationFormat("packed"); const points = await exporter.collect(); ``` ## API Reference - `WebAuthnOutcome` — `"ok" | "error" | "aborted"`. - `WebAuthnOp` — `"register" | "authenticate"`. - `WebAuthnAttestationFmt` — `"packed" | "fido-u2f" | "tpm" | "android-key" | "android-safetynet" | "apple" | "none" | "unknown"`. - `recordRegister(outcome, durationMs)` — record both histogram + outcome counter. - `recordAuthenticate(outcome, durationMs)` — same for the assertion path. - `recordAttestationFormat(fmt)` — increment dispatch counter. - `recordError(code)` — increment `webauthn.errors_total{code}`. - `nowMs()` — wall-clock reader. - `resetMetricsForTesting()` / `getInstrumentsForTesting()` — test hooks. ## Implementation Notes - The instrument bag is lazily created on first call so the meter resolves against whatever `MeterProvider` the host application has installed. - The reset hook is mandatory because OTel meter caching makes pre-registered instruments outlive a test's MeterProvider swap; without it a second test reads metrics from the first test's exporter. - `nowMs()` is exposed as a function rather than `Date.now` directly so future tests can stub it without monkey-patching `globalThis.Date`.