Skip to content
English

prom-client adapter

If your app already uses prom-client (the de-facto Node / Prometheus library) for its non-actor metrics, promClientRegistry(...) lets the framework’s metrics live in the same prom-client registry — one /metrics endpoint, all metrics together.

import client from 'prom-client';
import {
ActorSystem,
MetricsExtensionId,
promClientRegistry,
PromClientAdapterOptions,
} from 'actor-ts';
// Your app's existing prom-client registry. `client.register` is
// the default global one; here we use a fresh Registry for clarity.
const registry = new client.Registry();
// ... register your existing prom-client metrics on `registry` ...
const system = ActorSystem.create('my-app');
// Point the metrics extension at a registry that writes straight
// into prom-client. Every framework counter / gauge / histogram
// from here on lands in `registry` alongside your app metrics.
const promAdapterOptions = PromClientAdapterOptions.create()
.withClient(client)
.withRegistry(registry)
.withNamePrefix('actor_ts_');
system.extension(MetricsExtensionId).useRegistry(
promClientRegistry(promAdapterOptions),
);
// Your existing /metrics endpoint now emits framework + app metrics:
get(async () => ({
status: 200,
body: await registry.metrics(),
contentType: registry.contentType,
}));

promClientRegistry(...) returns a MetricsRegistry that writes straight through to prom-client — every framework counter, gauge, and histogram mutation lands in your prom-client registry synchronously. There’s no background sync, no interval, and no second copy of the values: prom-client holds the canonical state, and your existing register.metrics() route emits everything.

Two main reasons:

  1. Existing prom-client usage — your code has been emitting metrics via prom-client; you don’t want to maintain two registries.
  2. One scrape endpoint — your operators expect a single /metrics URL combining all your metrics.

If you don’t already use prom-client, prefer the framework’s native Prometheus exporter — no extra dependency.

type PromClientAdapterOptionsType = {
client: PromClientLike; // the prom-client namespace: import client from 'prom-client'
registry: PromClientRegistryLike; // the Registry to publish into — typically client.register
namePrefix?: string; // prefix applied to every metric name — default: ''
maxSeriesPerFamily?: number; // per-family cardinality cap — default: 10 000, 0 disables
};

client and registry are mandatory — the bridge has nothing to publish into without them. client is the prom-client namespace you already import; registry is the Registry it writes into (usually client.register, the default global registry). The plain object above works anywhere the builder does — the builder is the documented default.

namePrefix lets you namespace framework metrics so the bridge-sourced families are easy to spot in the exposition:

const prefixedOptions = PromClientAdapterOptions.create()
.withClient(client)
.withRegistry(registry)
.withNamePrefix('actorts_');
promClientRegistry(prefixedOptions);
// → actorts_messages_delivered_total, actorts_members_up, ...

The bridge enforces the same per-family cap as the in-process registry (default 10 000 label tuples, 0 disables), and it needs to more urgently: prom-client mints a series inside its own Counter / Gauge / Histogram on every .labels(...) call and never expires one, so an unbounded label grows your process’s resident memory, not just the scrape body.

const cappedOptions = PromClientAdapterOptions.create()
.withClient(client)
.withRegistry(registry)
.withMaxSeriesPerFamily(50_000);
promClientRegistry(cappedOptions);

Past the cap the bridge rewrites the tuple to the family’s own label names with every value set to __overflow__, so prom-client sees at most maxSeriesPerFamily + 1 distinct tuples per family. Reusing the declared names is not cosmetic: prom-client fixes a metric’s label names at construction and throws on a .labels(...) carrying anything else, so a synthetic marker name would fail the very call meant to contain the damage.

See cardinality discipline for bucketize, the way to not reach the cap in the first place.

Terminal window
npm install prom-client
# or: bun add prom-client

prom-client is a peer — only required if you use this adapter.