Files
skills/docs/ui/react/react-ui/src/observability/react-ui-metrics.md
2026-05-29 08:20:56 +01:00

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 state ring exposed via getReactUiMetricsState() so *.spec-conformance.test.ts files can assert exact attribute payloads without standing up a full SDK pipeline
  • startProviderMountSpan / startPackRegisterSpan returning the OTel Span so call sites can end() once mount work completes
  • resetReactUiMetricsState() 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 }) — increments react_ui.theme.switch_total{from,to} and records the matching histogram.
  • recordPackRegister({ packName, packVersion }) — increments react_ui.pack.register_total{pack.name, pack.version}.
  • recordPackUnregister({ packName }) — increments react_ui.pack.unregister_total{pack.name}.
  • recordPackVersionMismatch({ packName, packVersion, reactUiVersion }) — increments react_ui.pack.version_mismatch_total with 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 }) — increments react_ui.toast.shown_total.
  • startProviderMountSpan({ theme, packCount, locale }) / startPackRegisterSpan({ packName }) — open OTel spans; the caller owns span.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.