first commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# NumberFormatter Observability
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Per-package OpenTelemetry instruments for `@suaveplan/i18n-formatter-number`. Exposes a histogram (`i18n.format.number.duration_ms`) recording wall-clock duration of every `formatNumber` call and a counter (`i18n.format.cache_hit`) tagged with `formatter: "number"` that increments on every cache reuse. The two instruments are constructed once via `metrics.getMeter` and are lazily initialised on first record so cold-start latency stays low.
|
||||
|
||||
The module also retains an in-memory sample ring (`getNumberFormatMetricsState()` and `resetNumberFormatMetricsState()`) so spec-conformance tests can assert the exact attribute payloads produced by `recordNumberFormat` and `recordNumberCacheHit` without needing to wire a full SDK exporter inside every test file.
|
||||
|
||||
## Features
|
||||
|
||||
- Lazy instrument construction: the first call to `recordNumberFormat` or `recordNumberCacheHit` builds both instruments via the OTel global meter
|
||||
- Stable histogram name `i18n.format.number.duration_ms` with attributes `{ locale, style }`
|
||||
- Stable counter name `i18n.format.cache_hit` with attributes `{ formatter: "number" }`
|
||||
- In-memory sample ring (`numberDurationSamples`, `cacheHitSamples`) for spec-conformance assertions
|
||||
- `resetNumberFormatMetricsState()` clears both arrays so tests stay isolated
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
recordNumberFormat,
|
||||
recordNumberCacheHit,
|
||||
getNumberFormatMetricsState,
|
||||
resetNumberFormatMetricsState,
|
||||
} from "@suaveplan/i18n-formatter-number/observability";
|
||||
|
||||
resetNumberFormatMetricsState();
|
||||
recordNumberFormat("en-GB", "decimal", 1.23);
|
||||
recordNumberCacheHit();
|
||||
const state = getNumberFormatMetricsState();
|
||||
state.numberDurationSamples; // [{ attributes: { locale: "en-GB", style: "decimal" } }]
|
||||
state.cacheHitSamples; // [{ attributes: { formatter: "number" } }]
|
||||
```
|
||||
|
||||
## Usage: Advanced Example — Asserting OTel emission in a test
|
||||
|
||||
```typescript
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { formatNumber, clearNumberFormatterCache } from "@suaveplan/i18n-formatter-number";
|
||||
import {
|
||||
getNumberFormatMetricsState,
|
||||
resetNumberFormatMetricsState,
|
||||
} from "@suaveplan/i18n-formatter-number/observability";
|
||||
|
||||
describe("metrics emission", () => {
|
||||
it("emits duration histogram and cache-hit counter", { timeout: 5000 }, () => {
|
||||
clearNumberFormatterCache();
|
||||
resetNumberFormatMetricsState();
|
||||
formatNumber(1, "en-GB", { style: "decimal" });
|
||||
formatNumber(1, "en-GB", { style: "decimal" });
|
||||
const state = getNumberFormatMetricsState();
|
||||
expect(state.numberDurationSamples).toHaveLength(2);
|
||||
expect(state.cacheHitSamples).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `recordNumberFormat(locale: string, style: string, durationMs: number): void`
|
||||
|
||||
Records one observation on the `i18n.format.number.duration_ms` histogram and pushes a `{ attributes: { locale, style } }` entry into the in-memory sample ring.
|
||||
|
||||
### `recordNumberCacheHit(): void`
|
||||
|
||||
Adds 1 to the `i18n.format.cache_hit` counter with `formatter: "number"` and pushes a matching entry into the in-memory sample ring.
|
||||
|
||||
### `getNumberFormatMetricsState(): NumberFormatMetricsState`
|
||||
|
||||
Returns the in-memory sample ring (live reference — do not mutate). Use `resetNumberFormatMetricsState` between tests to keep state isolated.
|
||||
|
||||
### `resetNumberFormatMetricsState(): void`
|
||||
|
||||
Empties both sample arrays in place.
|
||||
|
||||
### `NumberFormatMetricsState`
|
||||
|
||||
`{ numberDurationSamples: FormatMetricSample[]; cacheHitSamples: FormatMetricSample[] }` where `FormatMetricSample = { attributes: Record<string, string | number> }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The instruments are built lazily so that consumers who never call `formatNumber` (e.g. tree-shaken builds that import only types) 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 `resetNumberFormatMetricsState` calls (resetting state does not invalidate the meter).
|
||||
|
||||
The module deliberately keeps the in-memory sample ring unbounded. Tests should call `resetNumberFormatMetricsState()` between cases. Long-running production processes do not retain the ring because the slim-core wave swaps the in-package observability module for the unified one in `@suaveplan/i18n` core; for now, every leaf package owns its own ring per the design doc's per-package shape.
|
||||
Reference in New Issue
Block a user