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

3.2 KiB

otel/instruments

(no submodules)

Purpose

The otel/instruments module owns the OpenTelemetry instrument bundle emitted by @suaveplan/api-plugin-campaigns. It binds a meter under the "@suaveplan/api-plugin-campaigns" instrumentation-scope name against the process-global MeterProvider and exposes a lazily-cached factory so the plugin's route handlers, repositories, and lifecycle hooks share a single counter / histogram pair without re-resolving the meter on every call.

The bundle is intentionally small in Chunk A: a request-count counter and a request-duration histogram covering every route mounted by the plugin. Chunks B and C extend it (in their own modules) with per-domain instruments for campaign writes, suppression checks, and webhook ingestion outcomes.

Features

  • Lazy, cached meter binding — first call resolves the global meter; later calls return the cached bundle so handlers can record metrics in their hot path without provider lookup overhead.
  • resetCampaignsApiInstruments() hook for tests that install a fresh MeterProvider after the first cache write.
  • Counter + histogram primitives only; aggregation strategy and views are owned by the host's MeterProvider, not this package.

Usage: Basic

import { getCampaignsApiInstruments } from "@suaveplan/api-plugin-campaigns";

const { requestsTotal, requestDurationMs } = getCampaignsApiInstruments();

const start = performance.now();
try {
    await handler();
    requestsTotal.add(1, { route: "/campaigns", outcome: "ok" });
} catch (err) {
    requestsTotal.add(1, { route: "/campaigns", outcome: "error" });
    throw err;
} finally {
    requestDurationMs.record(performance.now() - start, {
        route: "/campaigns",
    });
}

Usage: Advanced

import { metrics } from "@opentelemetry/api";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import {
    getCampaignsApiInstruments,
    resetCampaignsApiInstruments,
} from "@suaveplan/api-plugin-campaigns";

// Test set-up: rebind to a freshly-installed provider.
const provider = new MeterProvider({ readers: [] });
metrics.setGlobalMeterProvider(provider);
resetCampaignsApiInstruments();

// The next resolve binds to `provider`, not the implicit global from
// before the rebind.
const bundle = getCampaignsApiInstruments();
bundle.requestsTotal.add(1, { route: "/x", outcome: "ok" });

API Reference

  • interface CampaignsApiInstruments { readonly requestsTotal: Counter; readonly requestDurationMs: Histogram }
  • getCampaignsApiInstruments(): CampaignsApiInstruments — cached factory.
  • resetCampaignsApiInstruments(): void — drops the cache.

Implementation Notes

The cache is a module-level mutable binding. That is intentional and matches the upstream pattern in @suaveplan/api-plugin-contacts: an inert no-op metric is cheap to recreate, but creating one per request is not, so caching plus an explicit reset hook for tests is the simplest honest design.

The bundle is readonly so handlers cannot accidentally mutate it; the cache itself is mutable but only the reset hook reaches it. Reset is test-only — production code never invalidates the cache because the global provider is installed once at process start.