instrument
@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:
| Metric | Type | Labels | Source event |
|---|---|---|---|
act_events_committed_total | counter | name | committed |
act_reactions_acked_total | counter | lane | acked |
act_reactions_blocked_total | counter | lane | blocked |
act_settled_total | counter | โ | settled |
act_streams_closed_total | counter | โ | closed |
act_events_forgotten_total | counter | โ | forgotten |
act_notifications_total | counter | โ | notified |
act_errors_total | counter | circuit | error |
act_streams_blocked | gauge | โ | 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 = {}
Returnsโ
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)