Skip to main content
Version: Current

instrument

@rotorsoft/act-root


@rotorsoft/act-root / act-otel / instrument

Function: instrument()

instrument(app, options?): Disposer

Defined in: libs/act-otel/src/index.ts:201

Subscribe to a built Act's lifecycle events and maintain the canonical Prometheus metric set:

MetricTypeLabelsSource event
act_events_committed_totalcounternamecommitted
act_reactions_acked_totalcounterlaneacked
act_reactions_blocked_totalcounterlaneblocked
act_settled_totalcounterโ€”settled
act_streams_closed_totalcounterโ€”closed
act_events_forgotten_totalcounterโ€”forgotten
act_notifications_totalcounterโ€”notified
act_errors_totalcountercircuiterror
act_streams_blockedgaugeโ€”blocked_streams() per scrape

Label cardinality is bounded by design: event names come from the registry, lanes are declared at build, circuit states are three. There is deliberately no per-stream label โ€” stream ids are unbounded and would blow up a Prometheus instance.

The alerting split from the observability guide applies: a non-zero act_streams_blocked and act_errors_total{circuit="open"} growth are page-worthy; the rest are dashboard material.

Scrape resilience. The act_streams_blocked gauge reads blocked_streams() inside its per-scrape collect(). A rejection there (a degraded store) is swallowed and logged via the Logger port โ€” the gauge keeps its last value and every other metric still scrapes. prom-client would otherwise reject the whole registry.metrics() if any collect() rejected, blinding the dashboard exactly when the store is in trouble.

Idempotent registration. Calling instrument twice against the same registry is safe: each metric is registered idempotently (an existing metric of that name is reused rather than re-created), so a second bridge shares the counters instead of throwing prom-client's "already been registered". The two bridges then feed the same metrics. Disposing either removes the shared metrics from the registry โ€” call each returned disposer when tearing the app down, and treat one registry as backing one logical bridge even if you constructed it in two calls.

Returns a Disposer that unsubscribes the listeners and removes the metrics from the registry โ€” hand it to Act's dispose(...) registry so Ctrl-C tears the bridge down with everything else:

Parametersโ€‹

appโ€‹

ActSurface

A built Act orchestrator

options?โ€‹

InstrumentOptions = {}

See InstrumentOptions

Returnsโ€‹

Disposer

Disposer that detaches listeners and unregisters the metrics

Exampleโ€‹

import { act, dispose } from "@rotorsoft/act";
import { instrument } from "@rotorsoft/act-otel";
import { register } from "prom-client";

const app = act().withState(Task).build();
dispose(instrument(app));

// serve the scrape endpoint on your HTTP surface
root.get("/metrics", async (c) => c.text(await register.metrics()));

Throwsโ€‹

ZodError when options are out of range (misconfiguration surfaces at startup, not on the first scrape)