Zum Inhalt springen
Deutsch

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.

Span-NameWannBemerkenswerte Attribute
actor.receiveEinmal pro an onReceive gelieferter Message.actor.path, actor.message.type
cluster.envelope.receivedEingehender Cross-Cluster-Envelope mit Parent-Trace-Kontext.cluster.from, cluster.to.path

Für einen typischen Request-Flow:

HTTP-Request

Root-Span

actor.receive — api-actor

tell db-actor

Cross-Wire: traceparent injiziert, kein Span

cluster.envelope.received auf db-Node

Peer-Seite

actor.receive — db-actor

Jeder Span trägt die Trace-ID — dein Tracing-Backend näht sie zu einem Trace zusammen.

// 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.

// 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.

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.

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.

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.

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.