Initial Commit
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# `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.
|
||||
Reference in New Issue
Block a user