4.3 KiB
MemoryBackend Observability
(no submodules)
Purpose
This module owns the OpenTelemetry instrumentation for
@suaveplan/i18n-backend-memory. It ships per-call metric emitters
(recordRead, recordWrite) that the backend invokes around every
read / save operation, plus a small in-memory state ring so spec-
conformance tests can assert exact attribute payloads without
standing up a full SDK + InMemoryMetricExporter pipeline. Every
instrument is built lazily and is a no-op when no MeterProvider is
registered — consumers pay zero overhead unless they wire up the
SDK.
The metric naming follows the per-backend prefix convention agreed
in the Wave 5 design: i18n.backend.memory.*. Sibling backend
packages (i18n-backend-http, i18n-backend-storage, etc.) emit
under their own prefixes so consumers can group dashboard panels
per backend without shared-namespace collisions.
Features
- Five instruments — two histograms, three counters — covering both read and write paths.
- In-memory state ring for test assertions (no exporter required).
- Lazy meter construction; idempotent
getMemoryBackendMetrics(). - Test-only
resetMemoryBackendMetricsCache()so suites can rebuild the meter under a fresh provider.
Usage: Basic Example
import {
getMemoryBackendMetricsState,
resetMemoryBackendMetricsState,
} from "@suaveplan/i18n-backend-memory/observability";
import { MemoryBackend } from "@suaveplan/i18n-backend-memory";
resetMemoryBackendMetricsState();
const backend = new MemoryBackend({ seed: { en: { common: { hi: "Hi" } } } });
await backend.read("en", "common");
const state = getMemoryBackendMetricsState();
expect(state.readTotal).toEqual([{ attributes: { outcome: "hit" } }]);
Usage: Advanced Example
Mismatched reads emit BOTH an outcome counter AND an
read_errors_total counter labelled by error code. Tests can assert
the full payload to detect regressions in error classification.
import {
getMemoryBackendMetricsState,
resetMemoryBackendMetricsState,
} from "@suaveplan/i18n-backend-memory/observability";
import { MemoryBackend } from "@suaveplan/i18n-backend-memory";
resetMemoryBackendMetricsState();
const backend = new MemoryBackend();
await backend.read("en", "missing").catch(() => undefined);
const state = getMemoryBackendMetricsState();
expect(state.readTotal).toEqual([{ attributes: { outcome: "miss" } }]);
expect(state.readErrorsTotal).toEqual([
{ attributes: { code: "I18N.BACKEND_LOCALE_NOT_AVAILABLE" } },
]);
API Reference
Emitted instruments
| Instrument | Kind | Labels | Unit |
|---|---|---|---|
i18n.backend.memory.read_duration_ms |
Histogram | locale, namespace, outcome |
ms |
i18n.backend.memory.read_total |
Counter | outcome (hit | miss) |
count |
i18n.backend.memory.read_errors_total |
Counter | code (I18N error code) |
count |
i18n.backend.memory.write_duration_ms |
Histogram | locale, namespace, outcome |
ms |
i18n.backend.memory.write_total |
Counter | outcome (ok | error) |
count |
Functions
getMemoryBackendMetrics()— returns cached instruments; builds them on first call.recordRead(locale, namespace, outcome, durationMs, errorCode?)— emit a read sample.recordWrite(locale, namespace, outcome, durationMs)— emit a write sample.getMemoryBackendMetricsState()— frozen-shape snapshot of every emitted sample since last reset.resetMemoryBackendMetricsState()— clear the in-memory state ring (test fixture).resetMemoryBackendMetricsCache()— drop the cached instruments so a freshMeterProvidercan be wired (test fixture).
Implementation Notes
- The state ring is intentionally cardinality-bounded by the
caller's test cases. It exists for assertion ergonomics, not for
production telemetry — production telemetry flows through the
registered OTel
MeterProvider. - Cardinality safety: aggregate counters use only
outcome(hit / miss / ok / error); per-error-code attribution lives on a separateread_errors_totalcounter so consumers can drop the high-cardinality counter without losing aggregate visibility. - Metric names are stable: every
i18n.backend.memory.*name is part of the package's public API contract and will not change without a major bump.