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

3.0 KiB
Raw Blame History

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

import { getMigrationInstruments } from "@suaveplan/api-migrations/otel/instruments";

const instruments = getMigrationInstruments();
instruments.recordApply("sqlite", "001_initial", 17, "success");

Usage: Advanced Example

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.