# `otel/instruments` — counter + histogram per migration apply _(no submodules)_ ## Purpose Lazily builds and caches the two OpenTelemetry instruments that `@suaveplan/api-migrations` emits on every migration apply attempt: - counter `migrations.applied_total{dialect, outcome}` - histogram `migrations.apply_duration_ms{name}` Lazy construction defers `metrics.getMeter(...)` until the first apply, so the meter resolves against whatever `MeterProvider` is globally installed at that moment — including the test in-memory provider that `tests/setup/polyfills.ts` registers before any source module loads. ## Features - Single `recordApply(dialect, name, durationMs, outcome)` entry point — keeps the call-site code in `runner.ts` short and uniform regardless of success or failure path. - Emits both instruments on every call, so observability does not depend on whether a migration succeeded. - `resetMigrationInstruments()` lets the test suite discard the cached instance between `MeterProvider` swaps. - Counter attributes are deliberately low-cardinality (`dialect` × two outcomes) so the time series is stable across releases. ## Usage: Basic Example ```ts import { getMigrationInstruments } from "@suaveplan/api-migrations/otel/instruments"; const instruments = getMigrationInstruments(); instruments.recordApply("sqlite", "001_initial", 17, "success"); ``` ## Usage: Advanced Example ```ts import { getMigrationInstruments, resetMigrationInstruments, } from "@suaveplan/api-migrations/otel/instruments"; import { metrics } from "@opentelemetry/api"; import { MeterProvider } from "@opentelemetry/sdk-metrics"; // Swap providers between test phases — call reset to rebuild the cached // instruments against the new meter. metrics.setGlobalMeterProvider(new MeterProvider()); resetMigrationInstruments(); getMigrationInstruments().recordApply("postgres", "002_users", 42, "error"); ``` ## API Reference - `interface MigrationInstruments { recordApply(dialect, name, durationMs, outcome): void }`. - `getMigrationInstruments(): MigrationInstruments` — returns the cached instance, building it on first call against the currently registered `MeterProvider`. - `resetMigrationInstruments(): void` — clears the cache (test-only). ## Implementation Notes - The module-level `instruments` cache is the only mutable state and is scoped to the module — re-importing the module under a fresh ESM realm yields a fresh cache, which is the desired behaviour for isolated tests. - The counter is namespaced `migrations.applied_total`; the histogram is namespaced `migrations.apply_duration_ms` — both names match the spec delta literally so downstream dashboards do not need translation tables. - Attribute cardinality: `dialect` is bounded by `MigrationDialect` (currently four members), `outcome` is exactly two, and `name` is the user-supplied migration basename. The histogram attribute is intentionally `name`-only — adding `outcome` here would explode the bucket count.