# `instruments` — OTel `ws.broker.*` surface _(no submodules)_ ## Purpose This module owns the OpenTelemetry instrument surface for the Redis broker. Six instruments cover every documented operational signal: publish attempts and durations, inbound receive counts, reconnect-buffer reads and writes (with hit/miss labelling), and observed connection drops. Operators wire a `MeterProvider` against the global `metrics` API and the broker emits into it without any wiring inside the broker module itself. Resolving the meter and instruments lazily (rather than at module load) lets tests install a fresh `MeterProvider` after importing the broker and still observe emissions from production paths. The `resetBrokerMetricsForTests` helper exposes the same lazy cache to test teardown. ## Features - Six instruments, each documented with attribute keys: - `ws.broker.publishes_total` — counter, labels `{ topic, outcome }`. - `ws.broker.publish_duration_ms` — histogram (ms). - `ws.broker.receives_total` — counter, labels `{ topic }`. - `ws.broker.buffer_writes_total` — counter. - `ws.broker.buffer_reads_total` — counter, labels `{ hit }`. - `ws.broker.connection_lost_total` — counter. - Lazy resolution against `metrics.getMeter("@suaveplan/api-ws-broker-redis")` so tests can install their own SDK between cases. - `resetBrokerMetricsForTests()` clears the lazy cache. - No singletons leak into the public surface — every getter returns the meter-bound instrument so tests can assert against them directly. ## Usage: Basic ```ts import { publishDurationHistogram, publishesCounter, } from "@suaveplan/api-ws-broker-redis/instruments"; publishesCounter().add(1, { topic: "orders", outcome: "ok" }); publishDurationHistogram().record(12); ``` ## Usage: Advanced ```ts import { metrics, } from "@opentelemetry/api"; import { AggregationTemporality, MeterProvider, MetricReader, } from "@opentelemetry/sdk-metrics"; import { publishesCounter, resetBrokerMetricsForTests, } from "@suaveplan/api-ws-broker-redis/instruments"; class CollectingReader extends MetricReader { constructor() { super({ aggregationTemporalitySelector: () => AggregationTemporality.CUMULATIVE, }); } protected override async onForceFlush() {} protected override async onShutdown() {} snapshot() { return this.collect(); } } const reader = new CollectingReader(); const provider = new MeterProvider({ readers: [reader] }); metrics.setGlobalMeterProvider(provider); resetBrokerMetricsForTests(); // Now the broker's emissions land in `reader.snapshot()`. ``` ## API Reference - `publishesCounter(): Counter` - `publishDurationHistogram(): Histogram` - `receivesCounter(): Counter` - `bufferWritesCounter(): Counter` - `bufferReadsCounter(): Counter` - `connectionLostCounter(): Counter` - `resetBrokerMetricsForTests(): void` ## Implementation Notes Module-local `let` cache variables (`_meter`, `_publishes`, etc.) keep the instrument identity stable across calls within a single process so OTel SDK aggregation works correctly. `resetBrokerMetricsForTests()` re-initialises those to `undefined` so tests can switch SDKs between cases without inheriting the previous run's `Meter` reference. The instrument names follow the `ws.broker.*` namespace convention — every WS-related OTel surface in Suaveplan lives under `ws.*` to keep dashboard queries simple, and `ws.broker.*` distinguishes broker-side emissions from engine-side ones (`ws.engine.*`).