first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
# `metrics.ts` — detector-chain OTel instruments
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`metrics.ts` owns the OTel surface for the entire
`@suaveplan/i18n-detector` package. It exposes lazily-built
instruments (`tracer`, `matchTotal`, `durationMs`,
`detectedTotal`, `chainExhaustedTotal`) plus an in-memory state
ring so spec-conformance tests can assert on exact attribute
payloads without standing up a full `MeterProvider` +
`InMemoryMetricExporter` pipeline.
All instruments are no-ops when no `MeterProvider` /
`TracerProvider` is registered (the default), so consumers pay
zero overhead unless they wire up the SDK. The cached singleton
is reset between tests via `resetDetectorMetricsCache()`.
## Features
- `getDetectorMetrics()` — lazily builds the tracer, counters, and
histograms; caches the result.
- `recordDetectorMatch(detector, locale)` — increments the
aggregate `i18n.detector.match_total{detector}` counter AND
the per-locale `i18n.detector.detected_total{detector,locale}`
counter, mirroring both events into the in-memory state ring.
- `recordDetectorDuration(detector, durationMs)` — records
`i18n.detector.duration_ms{detector}` and pushes the sample
into state.
- `recordChainExhausted()` — increments
`i18n.detector.chain.exhausted_total` on every sentinel return.
- `getDetectorMetricsState()` / `resetDetectorMetricsState()` —
test-only helpers for asserting + clearing the in-memory ring.
- `resetDetectorMetricsCache()` — drops the singleton so a fresh
meter can be built between tests.
## Usage: Basic Example
```ts
import { getDetectorMetrics } from "@suaveplan/i18n-detector";
const m = getDetectorMetrics();
m.matchTotal.add(1, { detector: "cookie" });
```
## Usage: Advanced Example
```ts
import {
getDetectorMetricsState,
recordDetectorMatch,
resetDetectorMetricsState,
} from "@suaveplan/i18n-detector";
beforeEach(() => resetDetectorMetricsState());
it("increments detected_total once per match", () => {
recordDetectorMatch("cookie", "fr-FR");
const state = getDetectorMetricsState();
expect(state.detectedTotal).toEqual([
{ attributes: { detector: "cookie", locale: "fr-FR" } },
]);
});
```
## API Reference
- `getDetectorMetrics()` — returns the cached `CachedInstruments`
object (`tracer`, `matchTotal`, `durationMs`,
`detectedTotal`, `chainExhaustedTotal`).
- `recordDetectorMatch(detector, locale)` — emits both aggregate
and labelled counters.
- `recordDetectorDuration(detector, ms)` — emits the duration
histogram.
- `recordChainExhausted()` — emits the chain-exhausted counter.
- `getDetectorMetricsState()` / `resetDetectorMetricsState()` /
`resetDetectorMetricsCache()` — test plumbing.
- `DetectorMetricSample` / `DetectorMetricsState` — in-memory
ring shape.
## Implementation Notes
- The cached singleton has process-wide lifetime by design —
metrics emitters are not per-chain because the chain is a thin
orchestrator and detector names are globally unique.
- `state.detectedTotal` carries `{detector, locale}` attributes;
`state.matchTotal` carries `{detector}` only. Tests that need
per-locale assertions read `detectedTotal`; tests that only
care about which detector won read `matchTotal`.
- All sample arrays are unbounded — `resetDetectorMetricsState`
is the safety valve. Production code should never call the
state-inspection helpers (they exist for spec conformance).