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,} from 'actor-ts';import { MetricsExtensionId, promClientRegistry, PromClientAdapterOptions,} from 'actor-ts/metrics';
// 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.
Wann nutzen
Abschnitt betitelt „Wann nutzen“Zwei Hauptgründe:
- Bestehende prom-client-Nutzung — dein Code emittiert Metriken schon via prom-client; du willst keine zwei Registries pflegen.
- 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.
Eine Registry, ein Leser
Abschnitt betitelt „Eine Registry, ein Leser“Die Bridge behält keine Kopie dessen, was sie schreibt.
prom-client hält die Werte, also ist collect() auf der
zurückgegebenen Registry leer — dauerhaft und absichtlich — und die
Registry sagt das, indem sie collectable: false deklariert.
Für den Weg, für den es diesen Adapter gibt, ist das in Ordnung: du
liest die Metriken über register.metrics(), genau wie deine
eigenen. Relevant wird es für alles im Framework, das in die
andere Richtung liest, über collect(). Davon gibt es zwei, und
beide verweigern die Antwort, statt Null zu melden:
- Die Management-Route
GET /metricsantwortet mit503, solange diese Bridge installiert ist, statt mit200und leerem Body. Ein leerer Body ist ein gültiger Prometheus-Scrape — das Target bleibtup=1, und die Serien des Frameworks hören schlicht auf zu existieren, sodass darauf geschriebene Alerts nie wieder feuern. - Die DevTools-Übersicht zeigt
unavailablefür die drei Werte, die sie aus der Registry liest: verarbeitete Nachrichten, Mailbox-Drops und Handler-Latenz. Ihr Throughput-Diagramm lässt die Liniemessages / saus demselben Grund weg und nennt sie in der Legende, statt eine flache Null zu zeichnen: Ein Diagramm überzeugt stärker als eine Kachel, und diese Linie würde genau die Behauptung aufstellen, die die Kachel daneben verweigert. Die Dead-Letter-Linie daneben wird aus dem Event-Stream gezählt und bleibt — ebenso alles andere auf dieser Seite: Es stammt aus dem Aktorenbaum und dem Event-Stream und bleibt korrekt.
Also: aktiviere enableMetricsEndpoint nicht zusammen mit dieser
Bridge — scrape stattdessen deine eigene prom-client-Route, die
dieselben Serien enthält. Und lies Framework-Metriken in den
DevTools nur auf einem System, das die eigene Registry des
Frameworks nutzt.
An den Schreibvorgängen ändert sich nichts. Jeder Counter, jede
Gauge und jedes Histogram des Frameworks liegt live auf deiner
prom-client-Route, einschließlich actor_mailbox_dropped_total —
dem Drop-Counter, den die DevTools-Kachel dir nicht zeigen kann.
Konfiguration
Abschnitt betitelt „Konfiguration“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, ...Kardinalitätsgrenze
Abschnitt betitelt „Kardinalitätsgrenze“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.
Eine Serie entfernen
Abschnitt betitelt „Eine Serie entfernen“remove
reicht an prom-clients eigenes Metric.remove(labels) weiter — ein
Tupel, dessen Gegenstand weg ist, verlässt damit beide Seiten
gleichzeitig: die Serien-Zählung der Bridge und die Exposition, die
prom-client rendert. Nur eine von beiden zu treffen wäre schlimmer als
gar nicht zu entfernen: ein freigegebener Platz, während die Serie
weiter gescrapt wird, oder umgekehrt.
Das ist die eine Stelle, an der der Adapter mehr von prom-client
braucht als die Konstruieren-und-Mutieren-Oberfläche. Der client, den
du übergibst, muss also remove auf seinen Counter / Gauge /
Histogram anbieten — jedes Release seit v11.2 tut das. Ein
selbstgebauter Ersatz, der es weglässt, ist ein Compile-Fehler statt
eines stillen Auseinanderlaufens.
Peer-Dependency
Abschnitt betitelt „Peer-Dependency“npm install prom-client# oder: bun add prom-clientprom-client ist Peer — nur benötigt, wenn du diesen Adapter
nutzt.
Wo es weitergeht
Abschnitt betitelt „Wo es weitergeht“- Observability — Überblick — das größere Bild.
- Core-Metriken — die Metrik-Primitive des Frameworks, die durch die Brücke fließen.
- Prometheus-Exporter — die framework-native Alternative, wenn du prom-client nicht brauchst.
