Skip to content

OpenTelemetry forwarder: the edge's round-trip record is the SERVER span - #36

Merged
acn-ericlaw merged 1 commit into
mainfrom
fix/otel-span-kind-edge
Sep 22, 2026
Merged

acn-ericlaw merged 1 commit into
mainfrom
fix/otel-span-kind-edge

Conversation

@acn-ericlaw

Copy link
Copy Markdown
Collaborator

What

  • http.request, the edge's round-trip record (HttpRouter, AsyncContextHolder): REST automation mints a span at receipt (TraceInfo.newSpanId()), the first function — and the authentication service — parent onto it instead of the inbound traceparent span, and closeContext emits the record when the response completes: the single-shot writer, the stream renderer's terminal, the error writer (SimpleHttpUtility.sendResponse now passes the status it is about to write) and the housekeeper's timeout. start is the receipt time, exec_time the round trip to 3 decimals, status the HTTP status sent — except that a stream failing in-band after its head reports the failure's own status and message (AsyncContextHolder.markError). parent_span_id is the inbound traceparent span when present.

  • The stream relay's client leg is parented (EventEmitter.sendWithEventHttp): the async.http.request request event carries the sender's span, so the client leg sits under its caller instead of floating.

  • Head and tail, never per token: EventStreamWriter stamps the producer's trace and span on the first segment and the terminal (it reads the current trace through the new thread-keyed EventEmitter.getCurrentTrace(), registered and torn down with the worker's ref-keyed entry), data segments carry no trace; AsyncHttpClient captures its own span on the worker (PostOffice.getTrace()) and stamps it on the synthesized head/eof/exception segments in both relay modes while raw token frames are delivered untraced (deliverUntraced); the reply lane (AsyncHttpResponse.trackStreamFrames) counts data segments and annotates the terminal's record with frames.

  • Forwarder (extensions/opentelemetry-forwarder): kind SERVER iff service == http.request; every function execution is INTERNAL (from: http.request no longer decides the kind). README table updated.

  • Tests: new EventOverHttpStreamTest.edgeRelaySpansAreConnected (the whole tree: edge → relay → {client leg, peer edge → event.api.service → producer → two lane records per edge}, four lane records, two tails annotated frames=2, no data-frame spans; the test route is now tracing: true and the mock relay forwards through a trace-aware PostOffice), EventHttpTest.eventApiServiceIsAVisibleSpanInTheTrace (the /api/event edge record sits between the caller and event.api.service), TraceMetricsSpanDataTest.edgeRoundTripRecordIsTheServerSpan, and the playground engine's GraphSpanPropagationTest.downstreamHttpServiceChainsToCallerSpanViaTraceparent now expects the downstream edge's round-trip span between the fetcher and the downstream service. Guides: observability.md (The edge's round-trip span, the layers table, skip.rpc.tracing), http-streaming.md (Tracing a stream), event-over-http.md, configuration-reference.md.

  • Report: docs/test-reports/otel-dynatrace-certification.md gains Scenario 9 — one connected tree per request: the finding, the three causes, the fix and the re-drive (four traces rebuilt as single trees rooted at the edge span; 429 error traces — the provider's quota was spent — plus the token-bearing tree from the run before, frames: 2); What remains asks for your Dynatrace view of the four re-drive traces and records Scenario 8's backend view as confirmed.

  • Verification: platform-core 504 tests / 0 failures; opentelemetry-forwarder 35 / 0; event-script-engine SpanPropagationTest 4 / 0; full reactor mvn clean install BUILD SUCCESS (35 modules; the contract provider's snapshot link check caught one out-of-tree relative link in the guide, replaced by the GitHub URL); mkdocs build --strict OK.

Why

Eric's Dynatrace review of the v4.12.15 four-runtime certification traces found the span trees broken: the stream relay's client leg floated without a parent, every relayed token frame became an unparented reply-lane span, and no span covered the HTTP round trip — so a service's response time in the backend read as the first function's own execution time (0.5 ms for a 6 s stream). His rulings: fix the client leg in both engines, trace the head and the tail of a token stream and annotate the tail with the frame count, and add the round-trip span. Telemetry is operational readiness; a broken span tree is a defect. Lock-step twin: mercury fix/connected-edge-spans; the Python and Node.js forwarders adopt the same kind rule.

Behaviour change to read: a trace has one more span per traced request (http.request, the SERVER span); the first function is INTERNAL and parents onto it; an Event-over-HTTP hop's callee edge records its own round trip between the caller's span and event.api.service; a Java-to-Java or Java-to-Rust stream's reply-lane records are now parented (they were orphans) and there is no longer one per token. Dashboards keyed on kind=SERVER move from the first function to the edge record.

🤖 Generated with Claude Code

Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com

What
- span kind: SERVER iff the record's service is http.request (an engine edge's round-trip
  record); every function execution is INTERNAL - a record's "from" no longer decides it.
- tests pin both kinds (a function execution encodes as kind 1; the edge record as kind 2);
  CHANGELOG "Unreleased" carries the behaviour change.

Why
The engines now record the HTTP round trip as its own span (mercury-composable and
mercury fix/connected-edge-spans) and map only that record to SERVER; the four
forwarders keep one rule so a mixed-runtime trace renders one consistent tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@acn-ericlaw
acn-ericlaw merged commit cbf1f7f into main Sep 22, 2026
5 checks passed
@acn-ericlaw
acn-ericlaw deleted the fix/otel-span-kind-edge branch September 22, 2026 22:06
acn-ericlaw added a commit that referenced this pull request Sep 22, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@acn-ericlaw acn-ericlaw mentioned this pull request Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant