4.3 KiB
RelativeTimeFormatter Observability
(no submodules)
Purpose
Per-package OpenTelemetry instruments for @suaveplan/i18n-formatter-relative-time. Exposes a histogram (i18n.format.relative_time.duration_ms) recording wall-clock duration of every formatRelativeTime call (tagged with locale, style, and the resolved unit) and a counter (i18n.format.cache_hit) tagged with formatter: "relative_time" 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 so spec-conformance tests can assert exact attribute payloads without standing up a full SDK exporter.
Features
- Lazy instrument construction
- Stable histogram name
i18n.format.relative_time.duration_mswith attributes{ locale, style, unit } - Stable counter name
i18n.format.cache_hitwith attributes{ formatter: "relative_time" } - In-memory sample ring (
relativeTimeDurationSamples,cacheHitSamples) resetRelativeTimeFormatMetricsState()clears both arrays so tests stay isolated
Usage: Basic Example
import {
recordRelativeTimeFormat,
recordRelativeTimeCacheHit,
getRelativeTimeFormatMetricsState,
resetRelativeTimeFormatMetricsState,
} from "@suaveplan/i18n-formatter-relative-time/observability";
resetRelativeTimeFormatMetricsState();
recordRelativeTimeFormat("en-GB", "long", "minutes", 1.23);
recordRelativeTimeCacheHit();
const state = getRelativeTimeFormatMetricsState();
state.relativeTimeDurationSamples.length; // 1
state.cacheHitSamples.length; // 1
Usage: Advanced Example — Asserting OTel emission in a test
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
formatRelativeTime,
clearRelativeTimeFormatterCache,
} from "@suaveplan/i18n-formatter-relative-time";
import {
getRelativeTimeFormatMetricsState,
resetRelativeTimeFormatMetricsState,
} from "@suaveplan/i18n-formatter-relative-time/observability";
describe("metrics emission", () => {
it("emits duration histogram and cache-hit counter", { timeout: 5000 }, () => {
clearRelativeTimeFormatterCache();
resetRelativeTimeFormatMetricsState();
formatRelativeTime(60_000, "en-GB");
formatRelativeTime(60_000, "en-GB");
const state = getRelativeTimeFormatMetricsState();
expect(state.relativeTimeDurationSamples).toHaveLength(2);
expect(state.cacheHitSamples).toHaveLength(1);
});
});
API Reference
recordRelativeTimeFormat(locale, style, unit, durationMs): void
Records one observation on the i18n.format.relative_time.duration_ms histogram and pushes a { attributes: { locale, style, unit } } entry into the in-memory sample ring.
recordRelativeTimeCacheHit(): void
Adds 1 to the i18n.format.cache_hit counter with formatter: "relative_time" and pushes a matching entry into the in-memory sample ring.
getRelativeTimeFormatMetricsState(): RelativeTimeFormatMetricsState
Returns the in-memory sample ring (live reference — do not mutate).
resetRelativeTimeFormatMetricsState(): void
Empties both sample arrays in place.
RelativeTimeFormatMetricsState
{ relativeTimeDurationSamples: FormatMetricSample[]; cacheHitSamples: FormatMetricSample[] } where FormatMetricSample = { attributes: Record<string, string | number> }.
Implementation Notes
The instruments are built lazily so consumers who never call formatRelativeTime (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 resetRelativeTimeFormatMetricsState calls (resetting state does not invalidate the meter).
The module deliberately keeps the in-memory sample ring unbounded. Tests should call resetRelativeTimeFormatMetricsState() between cases. Long-running production processes should not retain the ring; the slim-core wave (Wave 6) 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.