first commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# ListFormatter Observability
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Per-package OpenTelemetry instruments for `@suaveplan/i18n-formatter-list`. Exposes a histogram (`i18n.format.list.duration_ms`) recording wall-clock duration of every `formatList` call (tagged with `locale`, `style`, and `type`) and a counter (`i18n.format.cache_hit`) tagged with `formatter: "list"` that increments on every cache reuse.
|
||||
|
||||
The list formatter introduces these instruments as part of the Wave 3 uplift — the monolith currently emits no OTel for list formatting. The two instruments are constructed once via `metrics.getMeter` and are lazily initialised on first record so cold-start latency stays low.
|
||||
|
||||
## Features
|
||||
|
||||
- Lazy instrument construction
|
||||
- Stable histogram name `i18n.format.list.duration_ms` with attributes `{ locale, style, type }`
|
||||
- Stable counter name `i18n.format.cache_hit` with attributes `{ formatter: "list" }`
|
||||
- In-memory sample ring (`listDurationSamples`, `cacheHitSamples`)
|
||||
- `resetListFormatMetricsState()` clears both arrays so tests stay isolated
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
recordListFormat,
|
||||
recordListCacheHit,
|
||||
getListFormatMetricsState,
|
||||
resetListFormatMetricsState,
|
||||
} from "@suaveplan/i18n-formatter-list/observability";
|
||||
|
||||
resetListFormatMetricsState();
|
||||
recordListFormat("en-GB", "long", "conjunction", 0.4);
|
||||
recordListCacheHit();
|
||||
const state = getListFormatMetricsState();
|
||||
state.listDurationSamples.length; // 1
|
||||
state.cacheHitSamples.length; // 1
|
||||
```
|
||||
|
||||
## Usage: Advanced Example — Asserting OTel emission in a test
|
||||
|
||||
```typescript
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
formatList,
|
||||
clearListFormatterCache,
|
||||
} from "@suaveplan/i18n-formatter-list";
|
||||
import {
|
||||
getListFormatMetricsState,
|
||||
resetListFormatMetricsState,
|
||||
} from "@suaveplan/i18n-formatter-list/observability";
|
||||
|
||||
describe("metrics emission", () => {
|
||||
it("emits duration histogram and cache-hit counter", { timeout: 5000 }, () => {
|
||||
clearListFormatterCache();
|
||||
resetListFormatMetricsState();
|
||||
formatList(["a", "b"], "en-GB");
|
||||
formatList(["a", "b"], "en-GB");
|
||||
const state = getListFormatMetricsState();
|
||||
expect(state.listDurationSamples).toHaveLength(2);
|
||||
expect(state.cacheHitSamples).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `recordListFormat(locale, style, type, durationMs): void`
|
||||
|
||||
Records one observation on the `i18n.format.list.duration_ms` histogram and pushes a `{ attributes: { locale, style, type } }` entry into the in-memory sample ring.
|
||||
|
||||
### `recordListCacheHit(): void`
|
||||
|
||||
Adds 1 to the `i18n.format.cache_hit` counter with `formatter: "list"` and pushes a matching entry into the in-memory sample ring.
|
||||
|
||||
### `getListFormatMetricsState(): ListFormatMetricsState`
|
||||
|
||||
Returns the in-memory sample ring (live reference — do not mutate).
|
||||
|
||||
### `resetListFormatMetricsState(): void`
|
||||
|
||||
Empties both sample arrays in place.
|
||||
|
||||
### `ListFormatMetricsState`
|
||||
|
||||
`{ listDurationSamples: FormatMetricSample[]; cacheHitSamples: FormatMetricSample[] }` where `FormatMetricSample = { attributes: Record<string, string | number> }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The instruments are built lazily so consumers who never call `formatList` do not pay the OTel meter-lookup cost. The cache check is a strict identity check against the closure-scoped `cached` slot — once built, the instruments are never rebuilt, even across `resetListFormatMetricsState` calls.
|
||||
|
||||
This module is one of two Wave 3 changes that introduce a *new* OTel surface — the monolith's `formatting/list/` module emits nothing, while `formatting/number/` and `formatting/relative-time/` already emit through the shared `observability/format-metrics.ts` module. The Wave 3 split brings every formatter package up to the same canonical observability shape so the slim core (Wave 6) can dashboard the entire family with a single regex on `i18n.format.*`.
|
||||
Reference in New Issue
Block a user