Actor-Tracing
Wenn die Tracing-Extension konfiguriert ist, erzeugt das
Framework automatisch einen Span pro Actor-Message plus einen
Infrastruktur-Span für eingehende Cluster-Wire-Envelopes. Spans
verketten sich über Tells hinweg — ein Message-Handler, der
einem anderen Actor etwas tellt, gibt den aktiven Span-Kontext
weiter, sodass der Span des Empfängers zurück zum Sender
verlinkt.
Auto-instrumentierte Spans
Abschnitt betitelt „Auto-instrumentierte Spans“| Span-Name | Wann | Bemerkenswerte Attribute |
|---|---|---|
actor.receive | Einmal pro an onReceive gelieferter Message. | actor.path, actor.message.type |
cluster.envelope.received | Eingehender Cross-Cluster-Envelope mit Parent-Trace-Kontext. | cluster.from, cluster.to.path |
Für einen typischen Request-Flow:
Jeder Span trägt die Trace-ID — dein Tracing-Backend näht sie zu einem Trace zusammen.
Kausale Verkettung
Abschnitt betitelt „Kausale Verkettung“// Innerhalb eines Actors:override async onReceive(message) { // tracer.activeSpan() gibt den actor.receive-Span zurück // tell erzeugt einen Envelope mit gesetztem traceparent this.downstream.tell({ kind: 'derived', from: message.id }); // Der actor.receive-Span des Downstream-Actors hat DIESEN Span als Parent}tell snapshottet den aktiven Span-Kontext auf den Envelope
(via tracer.injectContext()). Beim Empfangen führt das
Framework tracer.withActiveSpan(span, ...) aus, damit das
onReceive des Actors die Kette sieht.
Über Cluster-Nodes reitet derselbe traceparent auf dem Wire-
Envelope. Der empfangende Node extrahiert ihn; sein
actor.receive-Span verlinkt zurück zum Sender.
Span-Attribute
Abschnitt betitelt „Span-Attribute“// actor.receive-Attribute:{ 'actor.path': 'actor-ts://my-app/user/api/sessions/user-42', 'actor.message.type': 'LoginMessage',}// Bei einem Fehler zeichnet der Span die Exception auf und setzt// einen Error-Status (span.recordException(err) + span.setStatus('error', …)) —// es gibt kein error.message-Attribut.Für Cluster-Envelopes:
// cluster.envelope.received-Attribute:{ 'cluster.from': 'actor-ts://my-app@10.0.0.5:2552', 'cluster.to.path': '/user/api/sessions/user-42',}Diese folgen OpenTelemetry-Semantic-Conventions, wo zutreffend, sodass Standard-Dashboards (Honeycomb, Datadog, Grafana Tempo) ohne Anpassung funktionieren.
Span-Volumen steuern
Abschnitt betitelt „Span-Volumen steuern“Auto-Instrumentierung ist alles-oder-nichts — sie ist aktiv,
sobald du einen Nicht-Noop-Tracer aktivierst, und aus (null
Overhead), wenn der Tracer der Default-NoopTracer ist. Es gibt
keine Per-Kategorie-Schalter.
Um das Span-Volumen in sehr hochdurchsatzstarken Systemen zu
reduzieren, sample auf SDK-Ebene (siehe
OTel-Adapter → Sampling),
statt Kategorien abzuschalten — das Framework zeichnet mit voller
Rate auf, aber das SDK exportiert nur einen Bruchteil. Die
Auto-Instrumentierung ganz abzuschalten bedeutet, den NoopTracer
zu installieren, was auch deine manuellen Spans deaktiviert, da sie
durch denselben Tracer laufen.
Anwendungs-Level-Spans hinzufügen
Abschnitt betitelt „Anwendungs-Level-Spans hinzufügen“override async onReceive(message) { const tracer = this.context.system.extension(TracingExtensionId).get(); const span = tracer.startSpan('process-order', { attributes: { 'order.id': message.orderId, 'order.amount': message.amount, }, }); try { await tracer.withActiveSpan(span, async () => { // ... Verarbeitung ... }); span.setStatus('ok'); } catch (e) { span.recordException(e as Error); span.setStatus('error', (e as Error).message); throw e; } finally { span.end(); }}Anwendungs-Spans erscheinen als Kinder des automatisch
erzeugten actor.receive-Spans — deine Custom-Logik sitzt
natürlich innerhalb der Verarbeitung des Actors.
MDC-Integration
Abschnitt betitelt „MDC-Integration“Das Framework mergt traceId / spanId nicht automatisch
in den
LogContext — Log-Zeilen werden
nicht von Haus aus mit den IDs des aktiven Spans versehen.
Die MDC-Felder, die du selbst setzt (z. B. correlationId),
fließen wie gewohnt in jede Log-Zeile; die Trace-IDs sind nur
dann dabei, wenn du sie hinzufügst.
Um IDs zwischen Logs und Traces zu teilen, kopiere sie selbst vom aktiven Span in den MDC:
const span = tracer.activeSpan();if (span) { const { traceId, spanId } = span.context(); LogContext.with({ traceId, spanId }, () => { // hier emittierte Log-Zeilen tragen jetzt traceId + spanId });}Mit den IDs im MDC teilen deine Logs und Traces sie — du kannst von einem langsamen Trace in Honeycomb zu den passenden Log-Zeilen in Loki springen.
Performance
Abschnitt betitelt „Performance“Wenn Tracing deaktiviert ist (NoopTracer), ist die Auto- Instrumentierung Null-Overhead — die Hot-Paths des Frameworks short-circuiten, ohne Spans zu allokieren oder Async-Storage-Lookups zu machen.
Wenn aktiviert, fügt jede verarbeitete Nachricht hinzu:
- Eine Span-Allokation (kleines Objekt).
- Ein paar Attribut-Schreibvorgänge.
- Ein AsyncLocalStorage-Scope.
Gesamtkosten pro Nachricht: ~5-10 Mikrosekunden. Signifikant für Millionen-msg-pro-Sekunde-Systeme; ansonsten vernachlässigbar.
Wo es weitergeht
Abschnitt betitelt „Wo es weitergeht“- Tracer-API — das zugrundeliegende Interface.
- OTel-Adapter — zu deinem Tracing-Backend pipen.
- Recording-Tracer — für Test-Assertions.
- Logging — LogContext — der MDC, den du selbst mit Trace-IDs versehen kannst.
