Files
skills/docs/localization/i18n/i18n-backend-memory/src/observability/observability.md
2026-05-29 08:20:56 +01:00

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 fresh MeterProvider can 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 separate read_errors_total counter 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.