Zum Inhalt springen
Deutsch

prom-client-Adapter

Wenn deine App prom-client (die De-facto-Node-/Prometheus- Bibliothek) schon für ihre Nicht-Actor-Metriken nutzt, lässt promClientRegistry(...) die Framework-Metriken in derselben prom-client-Registry leben — ein /metrics-Endpunkt, alle Metriken zusammen.

import client from 'prom-client';
import {
ActorSystem,
MetricsExtensionId,
promClientRegistry,
PromClientAdapterOptions,
} from 'actor-ts';
// Die bestehende prom-client-Registry deiner App. `client.register`
// ist die globale Standard-Registry; hier nutzen wir der Klarheit
// halber eine frische Registry.
const registry = new client.Registry();
// ... registriere deine bestehenden prom-client-Metriken auf `registry` ...
const system = ActorSystem.create('my-app');
// Richte die Metrics-Extension auf eine Registry aus, die direkt in
// prom-client schreibt. Jeder Framework-Counter / jede -Gauge / jedes
// -Histogram landet ab hier in `registry` neben deinen App-Metriken.
const promAdapterOptions = PromClientAdapterOptions.create()
.withClient(client)
.withRegistry(registry)
.withNamePrefix('actor_ts_');
system.extension(MetricsExtensionId).useRegistry(
promClientRegistry(promAdapterOptions),
);
// Dein bestehender /metrics-Endpunkt liefert jetzt Framework- + App-Metriken:
get(async () => ({
status: 200,
body: await registry.metrics(),
contentType: registry.contentType,
}));

promClientRegistry(...) gibt eine MetricsRegistry zurück, die direkt durchschreibt zu prom-client — jede Mutation eines Framework-Counters, einer -Gauge oder eines -Histograms landet synchron in deiner prom-client-Registry. Es gibt keinen Hintergrund-Sync, kein Intervall und keine zweite Kopie der Werte: prom-client hält den kanonischen Zustand, und deine bestehende register.metrics()-Route liefert alles aus.

Zwei Hauptgründe:

  1. Bestehende prom-client-Nutzung — dein Code emittiert Metriken schon via prom-client; du willst keine zwei Registries pflegen.
  2. Ein Scrape-Endpunkt — deine Operatoren erwarten eine einzelne /metrics-URL, die alle deine Metriken kombiniert.

Wenn du prom-client noch nicht nutzt, bevorzuge den nativen Prometheus-Exporter des Frameworks — keine zusätzliche Dependency.

type PromClientAdapterOptionsType = {
client: PromClientLike; // der prom-client-Namespace: import client from 'prom-client'
registry: PromClientRegistryLike; // die Registry, in die publiziert wird — typischerweise client.register
namePrefix?: string; // Präfix für jeden Metriknamen — Default: ''
maxSeriesPerFamily?: number; // Kardinalitätsgrenze pro Familie — Default: 10 000, 0 schaltet ab
};

client und registry sind Pflicht — ohne sie hat die Brücke nichts, wohin sie publizieren kann. client ist der prom-client-Namespace, den du ohnehin importierst; registry ist die Registry, in die er schreibt (üblicherweise client.register, die globale Standard-Registry). Das obige Plain-Object funktioniert überall dort, wo auch der Builder funktioniert — der Builder ist der dokumentierte Standard.

namePrefix lässt dich Framework-Metriken namespacen, damit die von der Brücke stammenden Familien in der Exposition leicht zu erkennen sind:

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

Die Brücke erzwingt dieselbe Grenze pro Familie wie die In-Process-Registry (Default 10 000 Label-Tupel, 0 schaltet ab) — und braucht sie dringender: prom-client legt bei jedem .labels(...)-Aufruf eine Serie in seinem eigenen Counter / Gauge / Histogram an und lässt sie nie verfallen. Ein unbeschränktes Label lässt also den Resident-Speicher deines Prozesses wachsen, nicht nur den Scrape-Body.

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

Jenseits der Grenze schreibt die Brücke das Tupel auf die Label-Namen der Familie um, alle Werte auf __overflow__ gesetzt — prom-client sieht damit höchstens maxSeriesPerFamily + 1 verschiedene Tupel pro Familie. Die deklarierten Namen wiederzuverwenden ist nicht kosmetisch: prom-client legt die Label-Namen einer Metrik bei der Konstruktion fest und wirft bei einem .labels(...) mit irgendetwas anderem — ein synthetischer Marker-Name würde also genau den Aufruf sprengen, der den Schaden begrenzen soll.

Siehe Kardinalitätsdisziplin für bucketize, den Weg, die Grenze gar nicht erst zu erreichen.

Terminal-Fenster
npm install prom-client
# oder: bun add prom-client

prom-client ist Peer — nur benötigt, wenn du diesen Adapter nutzt.