first commit
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# `MemoryBackend` Observability
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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.
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user