Files
2026-05-27 23:29:53 +01:00

3.7 KiB

otel/instruments — OpenTelemetry instruments

(no submodules)

Purpose

Builds the OpenTelemetry instrument bundle emitted by the canonical ImportService so consumers can wire production observability without hand-rolling counter names. The bundle is a single cached object keyed against the global MeterProvider — the first getImportInstruments() call resolves the meter; subsequent calls return the same reference. Tests rebind by installing their own MeterProvider, calling resetImportInstruments(), and reading back via an InMemoryMetricExporter.

Features

  • Nine instruments: jobsTotal, rowsTotal, rowErrorsTotal, durationMs (histogram), storeWritesTotal, storeReadsTotal, sseConnected, sseEventsSent, sseDisconnected.
  • storeWritesTotal{outcome} and storeReadsTotal{outcome} are emitted by SurrealImportStore on every adapter call so dashboards can split backend health from upstream import success/failure. The in-memory adapter (MemoryImportStore) does not emit; durable backends do.
  • sseConnected, sseEventsSent{kind}, and sseDisconnected{reason} are emitted by GET /imports/:id/progress so dashboards can chart open SSE connections, frame throughput by event kind, and teardown causes (terminal complete / failed vs client abort).
  • Cached bundle — production code does not re-resolve the meter on every emit.
  • resetImportInstruments() drops the cache so a freshly-installed MeterProvider rebinds on the next call.
  • Asserted by instruments.test.ts against a real in-memory exporter (no mocks).

Usage: Basic Example

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

const instruments = getImportInstruments();
instruments.jobsTotal.add(1, { outcome: "success" });
instruments.rowsTotal.add(42, { outcome: "success" });
instruments.durationMs.record(123);

Usage: Advanced Example

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

const exporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
const reader = new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 60_000 });
const provider = new MeterProvider({ readers: [reader] });
metrics.setGlobalMeterProvider(provider);
resetImportInstruments();

const instruments = getImportInstruments();
instruments.jobsTotal.add(1, { outcome: "success" });
await provider.forceFlush();
const collected = exporter.getMetrics();
// collected[0].scopeMetrics[0].metrics — read back the counter datapoint

API Reference

  • interface ImportInstruments { jobsTotal: Counter; rowsTotal: Counter; rowErrorsTotal: Counter; durationMs: Histogram; storeWritesTotal: Counter; storeReadsTotal: Counter; sseConnected: Counter; sseEventsSent: Counter; sseDisconnected: Counter }
  • getImportInstruments(): ImportInstruments
  • resetImportInstruments(): void

Implementation Notes

  • The meter is named @suaveplan/api-plugin-import — matching the package name keeps cross-package metric reports readable.
  • Counters carry the outcome label so dashboards can split jobs/rows by success vs error without re-aggregating.
  • The histogram unit is set to "ms" so OTel collectors that respect unit annotation produce labelled charts; consumers MUST emit millisecond values.
  • The cache is module-scoped — every consumer of the package shares the same instrument set. This is the desired behaviour because the meter resolves against the global MeterProvider, which is a process-wide singleton.