3.8 KiB
otel — host instrument registry
(no submodules)
Purpose
createHostInstruments builds the frozen bundle of every OpenTelemetry counter and histogram the host emits on a production path. The instrument list mirrors design doc §12.3 exactly — every name is a published surface and removing one is a breaking change. The package never installs an OTel SDK; the meter-provider source is, in priority order, the explicit meterProvider override and then the OTel global provider (which falls back to a no-op when no SDK is wired). This keeps SDK initialisation an operator concern, not a library concern.
The registry also vends per-plugin meters via forPlugin(name) — each call returns a cached meter namespaced under @suaveplan/api-host/plugin/<name> with the plugin label pre-attached. Stage-4 packages plug their plugin-declared manifest.telemetry instruments onto these meters.
Features
- Twelve counters (
HOST_COUNTER_NAMES) and four histograms (HOST_HISTOGRAM_NAMES) covering the full §12.3 metric surface. - Pre-tagged host meter with
host.id+host.versionattributes. - Histogram units (
ms,By) set at construction time. - Cached per-plugin meters with
plugin.namepre-attached. tenantLabelOptInflag — defaults tofalseto bound cardinality.dispose()clears the per-plugin meter cache for graceful teardown.
Usage: Basic
import { createHostInstruments } from "@suaveplan/api-host/otel";
const instruments = createHostInstruments({
hostId: "host-1",
hostVersion: "0.1.0",
});
instruments.counters["api.requests_total"].add(1, { route: "/billing/invoices" });
instruments.histograms["api.request_duration_ms"].record(42, {
route: "/billing/invoices",
});
Usage: Advanced
Plug an explicit meter-provider (test harness or operator-supplied SDK):
import {
HOST_COUNTER_NAMES,
createHostInstruments,
} from "@suaveplan/api-host/otel";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
const provider = new MeterProvider({
/* readers / views configured by the operator */
});
const instruments = createHostInstruments({
hostId: "host-1",
hostVersion: "0.1.0",
tenantLabelOptIn: true,
meterProvider: provider,
});
// Per-plugin meter — caches per `pluginName`.
const billingMeter = instruments.forPlugin("billing").meter;
const errorsByReason = billingMeter.createCounter("billing.errors_total");
// Verify the surface is complete.
for (const name of HOST_COUNTER_NAMES) {
instruments.counters[name]; // present.
}
API Reference
createHostInstruments(config: HostInstrumentsConfig): HostInstrumentsHOST_COUNTER_NAMES—readonlytuple of every counter name.HOST_HISTOGRAM_NAMES—readonlytuple of every histogram name.HostInstruments:meter: MetertenantLabelOptIn: booleancounters: Readonly<Record<HostCounterName, Counter>>histograms: Readonly<Record<HostHistogramName, Histogram>>forPlugin(name): PluginInstrumentsdispose(): void
PluginInstruments:{ meter: Meter; pluginName: string }.
Implementation Notes
The counter / histogram lists are exported as readonly tuples so consumers can iterate and assert against them in tests — a missing instrument shows up as a test failure, not a runtime no-op. Per-plugin meters are cached by name to avoid leaking meter objects on every reload (one meter per plugin is the design invariant — disposal happens at uninstall, mirrored by dispose() here clearing the whole cache). Tenant-label opt-in is gated at the call site rather than the registry level: the registry exposes the flag and the request pipeline (Chunk B) decides whether to attach tenant to a given measurement. This keeps the cardinality story explicit at every emit site rather than hidden behind a registry policy.