# `@suaveplan/websocket-gateway` OTel surface _(no submodules)_ ## Purpose Centralises every OpenTelemetry instrument the WebSocket gateway emits — five span wrappers (`connect`, `message`, `broadcast`, `start`, `stop`), three counters, one up-down gauge for active rooms, and two histograms for `start()` and per-message dispatch latency. Splitting the OTel module out from the gateway file keeps the daemon under the 300-line cap and lets tests assert observability separately from behaviour. ## Features - Lazy meter/tracer acquisition via `@opentelemetry/api` — no provider coupling. - Span helpers `withStartSpan` / `withStopSpan` / `withConnectSpan` / `withMessageSpan` / `withBroadcastSpan` always end the span via `finally` so an exception in the wrapped body cannot leak an open span. - Counter `ws_gateway.actions_total{action,outcome}` records every authoritative decision (`allowed` / `denied` / `error`) so dashboards can chart auth/authz/rate-limit denial rates. - Counter `ws_gateway.presence_updates_total{kind}` matches the proposal's required surface. - Gauge `ws_gateway.rooms_active` follows the proposal's required surface and is monotone-by-delta (+1 on room create, -1 on delete). - Histograms `ws_gateway.message_dispatch_ms{action}` and `ws_gateway.start_duration_ms` for SLO tracking. ## Usage: Basic Example ```ts import { withMessageSpan, recordDispatchDuration } from "@suaveplan/websocket-gateway"; await withMessageSpan(async () => { const t0 = Date.now(); await dispatch(message); recordDispatchDuration("room.broadcast", Date.now() - t0); }); ``` ## Usage: Advanced Example ```ts import { adjustRoomsActive, recordAction, recordPresenceUpdate, withStartSpan, } from "@suaveplan/websocket-gateway"; await withStartSpan(async () => { await bind(); adjustRoomsActive(0); }); // On every authoritative decision recordAction("room.broadcast", "denied"); // On every presence transition recordPresenceUpdate("join"); ``` ## API Reference - `withStartSpan` / `withStopSpan` / `withConnectSpan` / `withMessageSpan` / `withBroadcastSpan` — wrap an async body in a named span. - `adjustRoomsActive(delta)` — bump the rooms-active gauge. - `recordPresenceUpdate(kind)` — increment the presence counter; `kind` is `"join" | "leave" | "update"`. - `recordAction(action, outcome)` — increment the actions counter; `outcome` is `"allowed" | "denied" | "error"`. - `recordDispatchDuration(action, ms)` — push one observation to the dispatch histogram. - `recordStartDuration(ms)` — push one observation to the start-duration histogram. ## Implementation Notes Every counter is multi-dimensional with a low-cardinality attribute set so the surface stays Prometheus/OTLP-friendly. Spans use `tracer.startActiveSpan` to participate in W3C context propagation; consumers reading inbound traceparent headers from clients will automatically nest the gateway's spans under the request span. The histograms clamp at zero to defend against clock skew producing negative deltas. The gauge is implemented as `UpDownCounter` because the gateway tracks per-event deltas, not snapshot reads.