# `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 ```ts 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 ```ts 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.