Files
skills/docs/api-stack/api/api-plugin-events/src/otel/instruments.md
2026-05-29 08:20:56 +01:00

4.4 KiB

instruments

(no submodules)

Purpose

Bundles the seven OpenTelemetry counters emitted by @suaveplan/api-plugin-events. Every counter is lazily built on the first getEventsInstruments() call and cached against the global MeterProvider in effect at that point. Tests that install their own in-memory MeterProvider call resetEventsInstruments() before re-resolving the bundle so the counters bind to the freshly-installed provider.

The seven instruments form the canonical observability surface for the plugin: they count successful event lifecycle events (create / update / delete), RSVP writes partitioned by status, invitations sent, ICS exports served partitioned by with variant, and ICS export failures partitioned by reason. Histograms (e.g. route duration) are intentionally not part of the 0.1 surface; the canonical histogram for HTTP routes lives in the host @suaveplan/api middleware layer.

Features

  • Seven Counters emitted from the route handlers: events.created_total, events.updated_total, events.deleted_total, events.rsvps_written_total{status}, events.invitations_sent_total, events.ics.served_total{with}, events.ics.failed_total{reason}.
  • Lazily built and cached — no instrument is constructed before the first getEventsInstruments() call.
  • resetEventsInstruments() clears the cache so tests can re-bind against a fresh MeterProvider.
  • Stable name shape (events.*_total) for OTel collector scraping.

Usage: Basic Example

import { getEventsInstruments } from "@suaveplan/api-plugin-events";

const inst = getEventsInstruments();
inst.eventsCreatedTotal.add(1);
inst.rsvpsWrittenTotal.add(1, { status: "going" });

Usage: Advanced Example

import { metrics } from "@opentelemetry/api";
import {
    MeterProvider,
    PeriodicExportingMetricReader,
    InMemoryMetricExporter,
    AggregationTemporality,
} from "@opentelemetry/sdk-metrics";
import {
    getEventsInstruments,
    resetEventsInstruments,
} from "@suaveplan/api-plugin-events";

// Install a fresh in-memory exporter for assertions
const exporter = new InMemoryMetricExporter(
    AggregationTemporality.CUMULATIVE
);
const provider = new MeterProvider({
    readers: [new PeriodicExportingMetricReader({ exporter })],
});
metrics.setGlobalMeterProvider(provider);
resetEventsInstruments();

// Drive a counter and flush
getEventsInstruments().invitationsSentTotal.add(3);
await provider.forceFlush();
const snapshot = exporter.getMetrics();
// snapshot now contains `events.invitations_sent_total` with value 3

API Reference

interface EventsInstruments

  • eventsCreatedTotal: Counter — incremented on every successful POST /.
  • eventsUpdatedTotal: Counter — incremented on every successful PUT /:id.
  • eventsDeletedTotal: Counter — incremented on every successful DELETE /:id.
  • rsvpsWrittenTotal: Counter — incremented on every successful POST /:id/rsvp, labelled by the new status (going / maybe / invited / not_going).
  • invitationsSentTotal: Counter — incremented by the count of newly-created invitations on every successful POST /:id/invite.
  • icsServedTotal: Counter — incremented on every successful GET /:id/ics response. Labelled by with ("none" or "rsvps").
  • icsFailedTotal: Counter — incremented on every error response served by the ICS route. Labelled by reason ("not_found", "forbidden", "unauthenticated", or "invalid_query").

getEventsInstruments(): EventsInstruments

Lazily build and cache the instrument bundle. Repeated calls return the same object reference.

resetEventsInstruments(): void

Drop the cached bundle. The next getEventsInstruments() call rebinds against whatever MeterProvider is in effect.

Implementation Notes

The bundle uses singleton caching because OTel Counter objects are themselves bound to the meter that created them — calling meter.createCounter("events.created_total") twice does not produce a new metric, but it does produce a new JavaScript object that is slightly more expensive than the cached reference. The lazy construction also lets the polyfill in tests/setup/polyfills.ts install a MeterProvider before any source module that touches metrics imports is loaded, which is necessary because OTel binds the meter to the active provider at counter-creation time.