4.1 KiB
react-ui-metrics — OpenTelemetry instruments for @suaveplan/react-ui
(no submodules)
Purpose
Owns every OpenTelemetry counter, histogram, and span emitted from the
@suaveplan/react-ui chassis. The module wraps metrics.getMeter() /
trace.getTracer() from @opentelemetry/api so consumers wiring an SDK
get full observability with zero overhead when no SDK is registered (the
default behaviour). The instruments mirror the telemetry contract
documented in the design proposal —
theme switches, pack lifecycle, locale-bundle loads, themed-variants
resolution, toast notifications, and the react_ui.provider.mount span.
Features
- Lazy, cached instrument construction via
getReactUiMetrics()so a single meter is shared by every production path - Discrete recorder helpers per signal —
recordThemeSwitch,recordPackRegister,recordPackUnregister,recordPackVersionMismatch,recordLocaleBundleLoad,recordThemedVariantsResolve,recordToastShown - In-memory
statering exposed viagetReactUiMetricsState()so*.spec-conformance.test.tsfiles can assert exact attribute payloads without standing up a full SDK pipeline startProviderMountSpan/startPackRegisterSpanreturning the OTelSpanso call sites canend()once mount work completesresetReactUiMetricsState()for test isolation between cases
Usage: Basic example
import { recordThemeSwitch } from "@suaveplan/react-ui/observability";
recordThemeSwitch({ from: "light", to: "dark", durationMs: 12.5 });
Usage: Advanced example
import {
getReactUiMetrics,
getReactUiMetricsState,
resetReactUiMetricsState,
startProviderMountSpan,
} from "@suaveplan/react-ui/observability";
// On a `<ReactUiProvider>` mount path the chassis records the span
// from React's effect:
const span = startProviderMountSpan({
theme: "light",
packCount: 2,
locale: "en-GB",
});
try {
await loadLocaleBundle();
} finally {
span.end();
}
// In a test:
resetReactUiMetricsState();
const meter = getReactUiMetrics();
expect(meter).toBeDefined();
expect(getReactUiMetricsState().themeSwitchTotal).toHaveLength(0);
API Reference
getReactUiMetrics()— returns the cached{ counters, histograms, tracer }bundle. Idempotent.recordThemeSwitch({ from, to, durationMs })— incrementsreact_ui.theme.switch_total{from,to}and records the matching histogram.recordPackRegister({ packName, packVersion })— incrementsreact_ui.pack.register_total{pack.name, pack.version}.recordPackUnregister({ packName })— incrementsreact_ui.pack.unregister_total{pack.name}.recordPackVersionMismatch({ packName, packVersion, reactUiVersion })— incrementsreact_ui.pack.version_mismatch_totalwith the three attributes.recordLocaleBundleLoad({ locale, outcome, durationMs })— increments the load counter and records the duration histogram.recordThemedVariantsResolve({ component, durationMs })— records the themed-variants cache-miss histogram.recordToastShown({ kind })— incrementsreact_ui.toast.shown_total.startProviderMountSpan({ theme, packCount, locale })/startPackRegisterSpan({ packName })— open OTel spans; the caller ownsspan.end().getReactUiMetricsState()— snapshot of the in-memory ring.resetReactUiMetricsState()— clears the ring and forces the cached instruments to rebuild on next access.
Implementation notes
The instruments are constructed lazily inside getReactUiMetrics() so
no work happens at import time. The recorder helpers double-write
through the OTel API (so a wired SDK collects samples) AND into a local
state ring (so unit tests can assert payloads without SDK plumbing).
The state ring lives in a module-level const and is cleared by
resetReactUiMetricsState(); every test that exercises the recorders
should call the reset in its afterEach. The tracer is fetched once
inside getReactUiMetrics() rather than per-span so a single tracer
identity flows through every span the package opens.