79 lines
3.0 KiB
Markdown
79 lines
3.0 KiB
Markdown
# `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.
|