Files
suaveplan/packages/api-stack/api/api-migrations/src/otel/instruments.md
2026-05-27 23:29:53 +01:00

79 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `otel/instruments` — counter + histogram per migration apply
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.