# `otel` — OpenTelemetry instrumentation for OPFS storage _(no submodules)_ ## Purpose Centralises every metric, histogram, and span emitted by `OpfsStorage` behind a single lazy instrument factory. Lazy construction lets consumers install a custom global `MeterProvider` / `TracerProvider` before the storage path executes, and lets tests pin a real in-memory `MeterProvider` against which the recorded attribute set is asserted. The module exposes a small `recordOp({op, outcome, durationMs, bytes?, direction?})` helper that every operation in the storage class calls once. ## Features - Six instruments: 4 counters (`reads_total`, `writes_total`, `deletes_total`, `errors_total`) and 2 histograms (`op_duration_ms`, `io_bytes`). - Stable label space: `{op, outcome}` on every counter / duration, `{op, direction}` on `io_bytes`. - Aborted operations count toward `errors_total` with `outcome: "aborted"` so SLO dashboards can split them out. - `setStorageBrowserOpfsMeterProvider(provider)` for runtime provider injection (used by `@suaveplan/opentelemetry`). - `resetInstruments()` rebuilds the cache between test blocks. ## Usage: Basic Example ```ts import { recordOp } from "@suaveplan/storage-browser-opfs/otel"; recordOp({ op: "set", outcome: "success", durationMs: 12, bytes: 100, direction: "write" }); ``` ## Usage: Advanced Example ```ts import { metrics } from "@opentelemetry/api"; import { getInstruments, setStorageBrowserOpfsMeterProvider, resetInstruments, } from "@suaveplan/storage-browser-opfs/otel"; // In a Bun/Node test: class CapturingMeterProvider { /* real in-memory MeterProvider */ } const provider = new CapturingMeterProvider(); setStorageBrowserOpfsMeterProvider(provider); resetInstruments(); const { writesTotal } = getInstruments(true); writesTotal.add(1, { op: "set", outcome: "success" }); ``` ## API Reference - `setStorageBrowserOpfsMeterProvider(provider | undefined): void`. - `getInstruments(force?: boolean): StorageBrowserOpfsInstruments` — cached unless `force` is `true`. - `recordOp(input: RecordOpInput): void`. - `resetInstruments(): void`. - Types: `StorageBrowserOpfsOp` (14 op names), `StorageBrowserOpfsOpOutcome` (`success | error | aborted`), `RecordOpInput`. ## Implementation Notes The dispatch in `recordOp` is intentionally a flat `if/else` chain rather than a lookup table: TypeScript can statically verify exhaustiveness over `StorageBrowserOpfsOp`, and the cost of a 14-arm chain is negligible compared to the underlying counter add. `aborted` outcomes are routed to `errors_total` (not a separate counter) on purpose — operationally they're failures from the caller's perspective even though they're not exceptions on the server side.