Files
2026-05-27 23:47:53 +01:00

3.1 KiB

@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

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

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.