Files
suaveplan/packages/api-stack/api/api-plugin-tenant/src/otel/instruments.md
2026-05-27 23:29:53 +01:00

3.3 KiB

instruments

(no submodules)

Purpose

Lazily resolves the OpenTelemetry instruments (Counter, Histogram) the plugin emits from its production paths. The bundle is built on first call and cached; tests call resetTenantPluginInstruments after installing a fresh MeterProvider so subsequent calls rebind to the test exporter. This pattern lets the package work seamlessly in production (where the global provider is the OTel SDK) and under test (where the global provider is an InMemoryMetricExporter).

Features

  • Four instruments: resolutionsTotal (counter), resolutionDurationMs (histogram), cacheTotal (counter), managementOpsTotal (counter).
  • Lazy initialisation — the meter is resolved on first call so test polyfills installed before the package loads bind correctly.
  • resetTenantPluginInstruments clears the cache so a re-bind is forced; primarily useful in test setup hooks.

Usage: Basic Example

import { getTenantPluginInstruments } from "@suaveplan/api-plugin-tenant";

const instruments = getTenantPluginInstruments();
instruments.resolutionsTotal.add(1, { outcome: "resolved", strategy: "header" });
instruments.resolutionDurationMs.record(12.4, { strategy: "header" });

Usage: Advanced Example

import {
    getTenantPluginInstruments,
    resetTenantPluginInstruments,
} from "@suaveplan/api-plugin-tenant";
import { metrics } from "@opentelemetry/api";
import { InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader, AggregationTemporality } from "@opentelemetry/sdk-metrics";

// In test setup before the package's other modules load:
const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
const reader = new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 60_000 });
const provider = new MeterProvider({ readers: [reader] });
metrics.setGlobalMeterProvider(provider);
resetTenantPluginInstruments();

// Later — assert the counters that were emitted:
await provider.forceFlush();
const collected = exporter.getMetrics();
const resolutions = collected[0]?.scopeMetrics[0]?.metrics.find(
    (m) => m.descriptor.name === "tenant_plugin.resolutions_total"
);
console.assert(resolutions !== undefined);

API Reference

  • interface TenantPluginInstruments — { resolutionsTotal: Counter, resolutionDurationMs: Histogram, cacheTotal: Counter, managementOpsTotal: Counter }.
  • function getTenantPluginInstruments(): TenantPluginInstruments — returns the cached bundle, building it on first call.
  • function resetTenantPluginInstruments(): void — drops the cache.

Implementation Notes

The lazy pattern is essential — calling metrics.getMeter("...") at module top-level binds to whichever MeterProvider was installed at import time. Because Vitest's setupFiles run before src/ modules import, installing the in-memory provider there guarantees the test bundle binds correctly. Production callers that install their OTel SDK late (e.g. behind a feature flag) call resetTenantPluginInstruments once the SDK is up so the plugin rebinds. The instrument names use tenant_plugin. rather than tenant. so dashboards can cleanly distinguish the plugin's emissions from the underlying @suaveplan/api-tenant library's instruments (if any are added later).