# CurrencyFormatter Observability _(no submodules)_ ## Purpose Per-package OpenTelemetry instruments for `@suaveplan/i18n-formatter-currency`. Exposes a histogram (`i18n.format.currency.duration_ms`) recording wall-clock duration of every `formatCurrency` call (tagged with `locale`, `currency`, and `display`) and a counter (`i18n.format.cache_hit`) tagged with `formatter: "currency"` that increments on every cache reuse. The currency formatter introduces these instruments as part of the Wave 3 uplift — the monolith currently emits no OTel for currency 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.currency.duration_ms` with attributes `{ locale, currency, display }` - Stable counter name `i18n.format.cache_hit` with attributes `{ formatter: "currency" }` - In-memory sample ring (`currencyDurationSamples`, `cacheHitSamples`) - `resetCurrencyFormatMetricsState()` clears both arrays so tests stay isolated ## Usage: Basic Example ```typescript import { recordCurrencyFormat, recordCurrencyCacheHit, getCurrencyFormatMetricsState, resetCurrencyFormatMetricsState, } from "@suaveplan/i18n-formatter-currency/observability"; resetCurrencyFormatMetricsState(); recordCurrencyFormat("en-US", "USD", "symbol", 0.4); recordCurrencyCacheHit(); const state = getCurrencyFormatMetricsState(); state.currencyDurationSamples.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 { formatCurrency, clearCurrencyFormatterCache, } from "@suaveplan/i18n-formatter-currency"; import { getCurrencyFormatMetricsState, resetCurrencyFormatMetricsState, } from "@suaveplan/i18n-formatter-currency/observability"; describe("metrics emission", () => { it("emits duration histogram and cache-hit counter", { timeout: 5000 }, () => { clearCurrencyFormatterCache(); resetCurrencyFormatMetricsState(); formatCurrency(42, "USD", "en-US"); formatCurrency(42, "USD", "en-US"); const state = getCurrencyFormatMetricsState(); expect(state.currencyDurationSamples).toHaveLength(2); expect(state.cacheHitSamples).toHaveLength(1); }); }); ``` ## API Reference ### `recordCurrencyFormat(locale, currency, display, durationMs): void` Records one observation on the `i18n.format.currency.duration_ms` histogram and pushes a `{ attributes: { locale, currency, display } }` entry into the in-memory sample ring. ### `recordCurrencyCacheHit(): void` Adds 1 to the `i18n.format.cache_hit` counter with `formatter: "currency"` and pushes a matching entry into the in-memory sample ring. ### `getCurrencyFormatMetricsState(): CurrencyFormatMetricsState` Returns the in-memory sample ring (live reference — do not mutate). ### `resetCurrencyFormatMetricsState(): void` Empties both sample arrays in place. ### `CurrencyFormatMetricsState` `{ currencyDurationSamples: FormatMetricSample[]; cacheHitSamples: FormatMetricSample[] }` where `FormatMetricSample = { attributes: Record }`. ## Implementation Notes The instruments are built lazily so consumers who never call `formatCurrency` 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 `resetCurrencyFormatMetricsState` calls. The `currency` attribute is the ISO 4217 code (`"USD"`, `"EUR"`, `"JPY"`); the `display` attribute is the resolved display mode (`"symbol"`, `"code"`, `"name"`, `"narrowSymbol"`). Both are included in the histogram tags so consumers can dashboard the most-formatted currencies and the most-used display modes per locale.