Skip to content

feat(observability): worker→container trace waterfall (W3C trace-context) - #143

Merged
prisis merged 5 commits into
alphafrom
feat/observability-trace-waterfall
Jul 12, 2026
Merged

feat(observability): worker→container trace waterfall (W3C trace-context)#143
prisis merged 5 commits into
alphafrom
feat/observability-trace-waterfall

Conversation

@prisis

@prisis prisis commented Jul 11, 2026

Copy link
Copy Markdown
Member

Phase 4 — Worker→container trace correlation (complete)

Final piece of the observability plan. A pure OSS framework change → alpha (builds on the merged Phase 2 OTLP transport, #139). A worker's RPC span and the spans of any container the handler calls now stitch into one W3C trace, end to end.

The waterfall, seam by seam

  1. Worker generates the trace context (@lunora/runtime): a traceId/spanId is minted at single-shard dispatch entry (before the shard runs the handler), added to ObservabilityEvent, reused by otlpSink for the dispatch span (no more random-at-emit ids), and forwarded to the shard as a traceparent header.
  2. DO reads it (@lunora/do): the ShardDO pulls traceparent off the inbound /rpc request into a per-request currentRequestTraceparent field (cleared each request), exposed via getCurrentTraceparent() — mirroring the existing currentRequestIp/getCurrentIp plumbing.
  3. ctx.containers becomes trace-aware (@lunora/codegen): the generated buildCtx passes this.getCurrentTraceparent() into createContainerContext.
  4. Container fetch carries it (@lunora/container): createContainerContext threads the traceparent through the accessor chain (get/any/pooltoRequest), which stamps it on every outbound container request.
  5. Container consumes it (@lunora/container/otel): createContainerTelemetry({ traceparent }) (or LUNORA_TRACEPARENT) makes every span inherit the parent trace id and hang off its span id; a missing/malformed value falls back to a fresh root trace.

shared (shared/otlp.ts): buildTraceparent/parseTraceparent W3C helpers (00-<32hex>-<16hex>-01), inlined into both packages.

Verification

  • @lunora/runtime 500/500, @lunora/container 135/135, @lunora/do 1036/1036, @lunora/codegen 763/763 (full suites — no regressions). New tests cover: otlpSink reuses the event's ids; the container stitches under a parent traceparent (and falls back on a malformed one); ctx.containers get/any/pool all stamp the forwarded traceparent; codegen wires getCurrentTraceparent() into the generated ctx.
  • tsc clean and eslint 0 on every changed file.
  • Every seam is unit-tested; the one thing only a live worker+container can exercise is the full network round trip.

Backward compatibility

traceId/spanId on ObservabilityEvent and the new createContainerContext argument are optional — existing sinks, dispatch paths, and generated code without containers are unaffected (otlpSink falls back to random ids; a container with no traceparent forms a fresh trace as before).

🤖 Generated with Claude Code

https://claude.ai/code/session_018sRFb1136YE8KDmDbFMYmm

Summary by CodeRabbit

  • New Features
    • Added end-to-end W3C traceparent propagation across worker dispatches, shard request handling, and container fetches to enable trace stitching.
    • Introduced per-dispatch trace/span identifiers and reused them for OTLP exports, aligning container span relationships with the upstream trace.
  • Bug Fixes
    • Improved tracing continuity for success, error, and retried flows by preserving trace context consistently across retries.
    • Prevented trace-context leakage between requests while maintaining consistent OTLP linkage.

@netlify

netlify Bot commented Jul 11, 2026

Copy link
Copy Markdown

Deploy Preview for lunorash ready!

Name Link
🔨 Latest commit 095c4cd
🔍 Latest deploy log https://app.netlify.com/projects/lunorash/deploys/6a5385b07014b40009167522
😎 Deploy Preview https://deploy-preview-143--lunorash.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 76da1778-ab71-4ab4-b5ab-645835f18f20

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce9b4c and c1c1d91.

⛔ Files ignored due to path filters (4)
  • packages/container/__tests__/otel.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • shared/otlp.ts is excluded by none and included by none
  • templates/expo/src/auth-client.ts is excluded by none and included by none
  • tests/vis-templates/__tests__/expo-example-drift.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by none
📒 Files selected for processing (1)
  • packages/container/src/otel.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/container/src/otel.ts

Walkthrough

Worker dispatches now generate W3C trace context, forward it through shard and container requests, and include it in observability events. Runtime and container OTLP exporters reuse the context to preserve parent-child trace relationships.

Changes

Trace context propagation

Layer / File(s) Summary
Dispatch trace context
packages/runtime/src/observability.ts, packages/runtime/src/create-worker.ts
Observability events accept optional trace IDs; single-shard dispatch generates identifiers, forwards traceparent, and includes the IDs in success and error events.
Shard and container request propagation
packages/do/src/shard-do.ts, packages/codegen/src/emit.ts, packages/container/src/client.ts
Shard requests retain inbound traceparent; generated shard code passes it to container contexts, which preserve it across container handles, retries, pools, and port rebinding.
OTLP span stitching
packages/runtime/src/observability-sinks.ts, packages/container/src/otel.ts
Runtime spans reuse event identifiers, while container telemetry parses traceparent and sets the derived trace and parent span IDs.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Worker as dispatchSingleShard
  participant Shard as ShardDO
  participant Container as Container request
  participant Sink as OTLP sink
  Worker->>Worker: Generate traceId and spanId
  Worker->>Shard: Forward traceparent
  Shard->>Container: Propagate traceparent
  Worker->>Sink: Emit event with trace context
  Sink->>Sink: Reuse traceId and spanId
  Container->>Container: Export span with parentSpanId
Loading

Possibly related PRs

  • anolilab/lunora#139: Introduced the OTLP-over-HTTP transport extended here with trace context propagation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: W3C trace-context propagation from worker to container.
Description check ✅ Passed The description covers the change and verification well, but it omits several template sections like linked issues, checklist, notes, and the CLA text.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/observability-trace-waterfall

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for confirming the Contributor License Agreement! 🙏

@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 160 untouched benchmarks


Comparing feat/observability-trace-waterfall (095c4cd) with alpha (639e8cd)1

Open in CodSpeed

Footnotes

  1. No successful run was found on alpha (0415e20) during the generation of this report, so 639e8cd was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@prisis prisis changed the title feat(observability): W3C trace context — RPC spans + container stitching feat(observability): worker→container trace waterfall (W3C trace-context) Jul 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/do/src/shard-do.ts (1)

4630-4692: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Forward traceparent into batch re-entry.
buildBatchEntryRequest() should copy the outer traceparent header too; otherwise /rpc-batch drops trace context when it re-enters fetch(), while single /rpc requests keep it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/do/src/shard-do.ts` around lines 4630 - 4692, Update
buildBatchEntryRequest to copy the outer request’s traceparent header into each
generated batch-entry request, preserving the existing header forwarding
behavior so re-entered /rpc calls retain trace context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/do/src/shard-do.ts`:
- Around line 4630-4692: Update buildBatchEntryRequest to copy the outer
request’s traceparent header into each generated batch-entry request, preserving
the existing header forwarding behavior so re-entered /rpc calls retain trace
context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9766461f-69fe-4b62-a282-25abb7fae0ef

📥 Commits

Reviewing files that changed from the base of the PR and between 448365f and 9d70f95.

⛔ Files ignored due to path filters (2)
  • packages/codegen/__tests__/discover-containers.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/container/__tests__/client.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
📒 Files selected for processing (3)
  • packages/codegen/src/emit.ts
  • packages/container/src/client.ts
  • packages/do/src/shard-do.ts

prisis and others added 2 commits July 12, 2026 13:39
…titching

Phase 4 of the observability plan — the trace-correlation foundation. The
worker now generates one W3C trace context per RPC and both rides it on the
dispatch span and can hand it to a container, so container and worker spans
stitch into one trace instead of forming disconnected traces.

- shared: `buildTraceparent` / `parseTraceparent` W3C helpers in the shared
  OTLP encoder both packages inline (`00-<32hex>-<16hex>-01`).
- runtime: generate `traceId`/`spanId` at single-shard dispatch entry; add
  them to `ObservabilityEvent`; `otlpSink` reuses them for the span (instead
  of minting random ids at emit time); and the runtime forwards them to the
  shard as a `traceparent` header.
- container: `createContainerTelemetry({ traceparent })` (or the
  `LUNORA_TRACEPARENT` env var) makes every span inherit the parent trace id
  and hang off its span id; a missing/malformed value falls back to a fresh
  root trace.

Both ENDS of the waterfall plus the wire format, fully unit-tested. The
remaining middle hop — the ShardDO reading the forwarded `traceparent` and
threading it into `ctx.containers`' outbound container fetch — is a follow-up
(it spans @lunora/do + @lunora/container and can only be validated against a
live worker+container).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sRFb1136YE8KDmDbFMYmm
…ner fetches

Completes the worker→container trace waterfall started in the previous
commit. The trace context the worker forwards as a `traceparent` header now
reaches the container's outbound fetch, so container spans stitch under the
worker's trace end to end.

- @lunora/do: the ShardDO reads `traceparent` off the inbound `/rpc` request
  into a `currentRequestTraceparent` field (cleared per request), exposed via
  a `getCurrentTraceparent()` getter — mirroring the `currentRequestIp` /
  `getCurrentIp` plumbing.
- codegen: the generated `buildCtx` passes `this.getCurrentTraceparent()` to
  `createContainerContext`, so `ctx.containers` is trace-aware.
- @lunora/container: `createContainerContext` threads the traceparent through
  the accessor chain (`get`/`any`/`pool` → `toRequest`), which stamps it on
  every outbound container request next to the target-port header.

Unit-tested end to end at each seam (worker forwards, DO reads, ctx.containers
injects, container otel consumes). The one thing only a live worker+container
can exercise is the full round trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sRFb1136YE8KDmDbFMYmm
@prisis
prisis force-pushed the feat/observability-trace-waterfall branch from 9d70f95 to 8ce9b4c Compare July 12, 2026 11:39
prisis and others added 2 commits July 12, 2026 13:57
Accept a future `traceparent` version that appends trailing fields
(parse the first four, per the spec) instead of dropping it, and reject
the reserved `ff` version; validate the version and flags fields. Also
document the per-request wiring a long-lived container needs to stitch
each request's spans under the inbound `traceparent` header.

Addresses thermo-nuclear review findings L1/L2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a drift guard asserting the app-logic files shared between
examples/expo and templates/expo (Chat/Login UI + messages backend)
stay byte-identical, so a forgotten copy-paste is a red CI run rather
than a silently regressed example. Also rename the template's
web-fallback `sessionStorage` local (localStorage-backed, shadowed the
browser global) to `sessionStore`.

Addresses thermo-nuclear review findings P1/L4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@prisis
prisis merged commit 9317961 into alpha Jul 12, 2026
13 of 14 checks passed
@prisis
prisis deleted the feat/observability-trace-waterfall branch July 12, 2026 12:16
prisis added a commit that referenced this pull request Jul 21, 2026
The in-memory LogBuffer folded the seven ctx.log severities onto four tiers
(trace→debug, log/info→info, fatal→error), so a line logged at trace or fatal
was indistinguishable from debug or error in the Studio Logs panel — the two
tiers #147 added were unreadable at the only place they surface locally.

Buffer the level the caller actually logged at, widen the studio's mirrored
LogLevel union to match, and source the panel's chip list + grouped summary from
a new LOG_LEVEL_ORDER in the shared contract so the ordering has one home.

Also drops a stale comment on otlpSink claiming trace correlation is "a later
phase" — it shipped in #143 and the sink already reuses event.traceId/spanId.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197KjhmBDB3PXAc6TXiPqZ9
prisis added a commit that referenced this pull request Jul 21, 2026
…dispatch

PR #143 propagated W3C trace context worker→shard→container and #147 stamped
traceId/spanId onto every ctx.log line, but a handler still had no way to
instrument a sub-operation: one SERVER span per dispatch was the entire worker
trace surface. A slow request was one opaque bar.

Adds ctx.trace(name, fn, attributes?) on Query/Mutation/ActionCtx. It returns the
body's value unchanged and re-throws a failure after recording it as an error
span — instrumentation, never flow control. Nesting is lexical, so the shape of
the code is the shape of the waterfall.

Design notes:

- The span stack lives in a closure scoped to the ctx, NOT on `this` like the
  surrounding currentRequest* fields. Those are set-at-entry/cleared-at-exit and
  so are only sound across code that doesn't span an interleaving point, whereas
  a span stack is by definition held across the awaited body. Scoping it to the
  ctx makes concurrent dispatches structurally unable to corrupt each other's
  nesting.
- The trace anchor IS resolved once per dispatch on `this`, so ctx.trace and the
  synthetic root span agree on the ids even with no inbound traceparent.
- The root span is recorded only when the dispatch actually produced spans:
  minting one per request would fill the bounded ring with single-bar traces from
  uninstrumented handlers and evict the instrumented ones the panel exists for.
  It is not sent to onSpan — the runtime already emits the dispatch to onRpc, and
  a collector would otherwise show it twice.
- foldTraces orders rows by (offset, depth), not arrival. Spans are recorded on
  completion, so a child is buffered before its parent, and at millisecond
  resolution the two routinely share a startTs — ordering has to come from the
  structure. The anchor is likewise picked structurally (the span whose parent is
  absent), since a fast parent/child pair is indistinguishable by timing. Caught
  by a test; both cases are now regression-covered.
- The fold is total: the ring routinely holds partial traces (eviction can drop a
  parent, and a trace can be read before its root settles), so an orphan is
  re-parented onto the anchor rather than dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197KjhmBDB3PXAc6TXiPqZ9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant