Skip to content

feat(observability): structured ctx.log fields, trace correlation, durable log sink - #147

Merged
prisis merged 11 commits into
alphafrom
feat/observability-logs
Jul 20, 2026
Merged

feat(observability): structured ctx.log fields, trace correlation, durable log sink#147
prisis merged 11 commits into
alphafrom
feat/observability-logs

Conversation

@prisis

@prisis prisis commented Jul 20, 2026

Copy link
Copy Markdown
Member

What & why

Enriches the framework's structured logging (ctx.log) so an app produces rich, filterable, trace-linked logs that a log-management backend (the Cloud log viewer, or any OTLP collector) can use — all backward-compatible and fully usable with zero cloud dependency. This closes the framework side of the "full log management" gap.

ctx.log, a full sink layer, and the OTLP-JSON wire contract already existed; this is enrichment, not greenfield.

What's new

  • Structured fieldsctx.log.info(message, fields) and a chainable ctx.log.with(fields) child logger. Fields become OTLP log-record attributes and ride the Workers-Logs console event (raw positional args still don't, preserving the PII boundary).
  • Full severity ramp — adds trace (1) and fatal (21) so ctx.log spans the whole OpenTelemetry range.
  • Trace correlation — log records carry the dispatch's traceId/spanId (from the inbound traceparent), linking a line to its RPC span.
  • Durable pipelineLogSink — persists every ctx.log line to a Cloudflare Pipeline → R2 (queryable via R2 SQL) in the app's own account, no cloud required — the durable counterpart to the streaming otlpSink.
  • Fields surfaced everywherewebhookSink/sentrySink now forward logs (onLog, with transformLog/captureLog redactors); the dev-terminal formatter (CLI + Vite) renders fields + a short trace= suffix; the Studio Logs panel renders fields and searches on them.

Behavior change

A two-arg call whose second argument is a plain object — ctx.log.info("saved", user) — is now the structured form (message "saved", userfields) instead of a joined message. Non-object second args, or 3+ args, are unchanged. Documented with a caution callout in concepts/observability.mdx.

Verification

  • End-to-end against the blog example under a live dev server: a real posts:list dispatch emitted the fields on the console event with a populated trace_id/span_id.
  • Full lint:types across all 59 projects; touched suites green (runtime, do, studio, config, container, testing).

Thermo review pass (2 reviewers) — all findings fixed

Bugs: bigint/circular in fields crashing getLogs (normalize to JSON-safe primitives at parse time), mutate-after-log aliasing (fresh snapshot), duplicate OTLP attribute keys (Map dedup, field wins), and a loud upgrade callout for webhookSink egress. Each with a regression test.

Structure: consolidated three field-renderers into shared/log-fields.ts and the hand-mirrored LogEvent/level-union into shared/log-event.ts (bundler-inlined, no new dependency edge — same pattern as shared/otlp.ts), making the cross-package onLog call structurally guaranteed; moved the pure parseLogArgs/isLogFields helpers out of the 8k-line shard-do.ts.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added structured ctx.log support with (message, fields) and with(fields).
    • Introduced a durable pipelineLogSink for persisted application logs.
    • Extended observability sinks with optional captureLog (Sentry) and transformLog (webhook).
  • Improvements
    • Enriched emitted logs with trace correlation and structured fields; improved console routing (treats fatal like error).
    • Enhanced OTLP log export with richer severity mapping and safer field handling.
  • Documentation
    • Updated observability/OTLP log docs for structured logging, sink behaviors, and schema/upgrade cautions.

prisis and others added 5 commits July 20, 2026 07:20
Enrich the existing `ctx.log` emission so the framework produces the
rich, filterable, trace-linked logs a log-management backend (the Cloud
log viewer, or any OTLP collector) needs — all backward-compatible and
usable with no cloud dependency.

- Structured fields: `ctx.log.info(message, fields)` and a chainable
  `ctx.log.with(fields)` child logger. Fields become OTLP log-record
  attributes and ride the Workers-Logs console event (raw positional
  args still do not, preserving the PII boundary). The `(string, object)`
  call is the structured form; every other shape stays console-style, so
  existing calls are unchanged.
- Full severity ramp: add `trace` (1) and `fatal` (21) to the console
  tiers so `ctx.log` spans the whole OpenTelemetry range.
- Trace correlation: log records carry the dispatch's `traceId`/`spanId`
  (from the inbound `traceparent`), linking a line to its RPC span.
- Move the emitted logger closure into a `makeLogger` DO-base method
  (arg parsing, field merge, buffer/console/sink fan-out) so the codegen
  template stays thin; regenerate the golden fixture + example _generated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an opt-in built-in sink that persists every `ctx.log` line to a
Cloudflare Pipeline → R2, so an app has a queryable log store (read back
with R2 SQL) in its own account with no cloud dependency — the durable
counterpart to the streaming `otlpSink`.

- `pipelineLogSink({ pipeline })` writes one structured record per line
  (message, level, functionPath, fields, trace/span ids, shard, user,
  ts). Log-only; RPC-span metrics stay in `analyticsEngineSink`.
- Thread a `waitUntil` context through the DO `LogSink.onLog` contract
  (from `state.waitUntil`) so a durable send survives isolate teardown;
  falls back to fire-and-forget when unavailable.
- Document the sink, the structured `ctx.log` fields/`.with(...)` API, and
  the extended severity ramp in the observability concept doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The observability commit added `trace`, `fatal`, and the chainable `with`
to `LunoraLogger`, but `@lunora/testing`'s `noopLog` test double still only
implemented the original five methods (TS2739). Complete it — `with` returns
the noop itself so `.with(...).with(...)` chains stay inert.

(Surfaced by the oxc/isolatedDeclarations lint:types on alpha; pre-existing gap
in this branch, not the migration. Squash into the trace-levels commit if desired.)
Close the follow-up gaps so the structured `ctx.log` fields added earlier
are actually usable across every surface, not just the OTLP/pipeline sinks.

- Sinks: `webhookSink` and `sentrySink` now forward `ctx.log` lines
  (`onLog`) — webhook takes a `transformLog` redactor, sentry an opt-in
  `captureLog`. Previously logs only reached otlpSink/pipelineLogSink.
- Dev terminal: the shared `formatLunoraEvent` renders structured fields
  as compact `key=value` pairs, appends a short `trace=` suffix, and maps
  `trace`/`debug`→info, `fatal`→error (used by both the CLI and Vite).
- Studio Logs panel: the buffer entry + getLogs carry `fields`; the panel
  renders them and the search box matches on field values.
- Docs: flag the `(string, object)` behavior change (structured, not a
  joined message) and note which sinks forward logs.

Verified end-to-end against the blog example under a live dev server: a
real `posts:list` dispatch emitted the fields on the console event with a
populated trace_id/span_id (trace correlation working on a real request).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address the two-reviewer thermo audit of the structured-logging change.

Bugs:
- Normalize `ctx.log` fields once at parse time — coerce each value to a
  JSON-safe primitive, snapshot into a fresh object, drop an empty bag. Fixes
  a `bigint`/circular value crashing the `getLogs` serialization (Studio Logs
  panel), a mutate-after-log aliasing footgun, and an empty `{}` riding every
  surface.
- Dedupe OTLP log-record attributes by key (Map) so a field reusing a reserved
  `lunora.*` key overrides it instead of emitting a duplicate KeyValue.
- Loud upgrade callout: `webhookSink` now egresses `ctx.log` lines; existing
  configs need `transformLog` and `onlyErrors` doesn't gate logs.

Structure (dedup the hand-mirrored contracts into `shared/`, inlined so no
cross-package dependency edge — the same pattern this feature already used for
`shared/otlp.ts`):
- `shared/log-fields.ts` — one field renderer/coercer/normalizer, replacing the
  three copies in runtime/config/studio (two byte-identical).
- `shared/log-event.ts` — one `LogEvent` shape, `ContextLogLevel` union, and
  `BUFFER_LEVEL`, replacing the do↔runtime hand-mirrors; the `onLog` call is now
  structurally guaranteed. `shared/otlp.ts` reuses the level union too.
- Move the pure `parseLogArgs`/`isLogFields` helpers out of the 8k-line
  shard-do.ts into request-log.ts, next to their collaborators.
- Studio: render fields once per row and skip an empty chip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploy Preview for lunorash ready!

Name Link
🔨 Latest commit 4fe4b07
🔍 Latest deploy log https://app.netlify.com/projects/lunorash/deploys/6a5ddf5ac56ddb0009155441
😎 Deploy Preview https://deploy-preview-147--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.

@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! 🙏

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@prisis, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e2714e43-8141-484f-bfa7-e2336f58a56c

📥 Commits

Reviewing files that changed from the base of the PR and between b8dbffb and 4fe4b07.

📒 Files selected for processing (1)
  • apps/docs/src/content/docs/concepts/observability.mdx

Walkthrough

The PR adds structured ctx.log fields and levels, trace correlation, durable Pipeline delivery, webhook and Sentry log hooks, shared observability contracts, and structured log rendering and search.

Changes

Structured observability logging

Layer / File(s) Summary
Logger contracts and event parsing
packages/server/src/types.ts, packages/runtime/src/observability.ts, packages/do/src/request-log.ts
Logger types support additional levels, structured fields, and with(fields); log events use shared contracts and parse structured arguments.
Durable Object logger integration
packages/codegen/src/emit.ts, packages/do/*, packages/testing/src/harness.ts
Generated contexts delegate logging to makeLogger; ShardDO records enriched events, buffers fields and trace IDs, and passes waitUntil to sinks.
Sink delivery and runtime exports
packages/runtime/src/observability-sinks.ts, packages/runtime/src/index.ts
OTLP, webhook, console, Sentry, and Pipeline sinks handle structured log events and correlation data.
Log output and documentation
packages/config/src/log-format.ts, packages/studio/src/features/logs/*, apps/docs/src/content/docs/concepts/observability.mdx
Formatted logs and Studio searches include structured fields and trace suffixes; documentation describes updated logging and sink contracts.

Tooling and formatting alignment

Layer / File(s) Summary
Pinned pnpm setup
.github/CONTRIBUTING.md, .github/workflows/*.yml
Contributor instructions and CI workflows use the pnpm version declared by the repository.
Package type-check configuration
packages/config/tsconfig.json, packages/studio/tsconfig.json
Package compiler settings omit vestigial root and output directory options.
Studio component formatting
packages/studio/src/components/...
Selected Studio component declarations are reformatted without runtime behavior changes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant ShardDO
  participant ObservabilitySink
  participant Pipeline
  Application->>ShardDO: ctx.log(message, fields)
  ShardDO->>ObservabilitySink: onLog(event, waitUntil)
  ObservabilitySink->>Pipeline: send structured log record
  Pipeline-->>ShardDO: durable delivery promise
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately summarizes the main observability changes in the PR.
Description check ✅ Passed The description clearly covers the why, key changes, behavior change, and verification, so it is mostly complete for the template.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/observability-logs

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

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 4fe4b07.

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/runtime/src/observability-sinks.ts (1)

197-205: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider an explicit opt-in/opt-out for ctx.log forwarding in webhookSink, mirroring sentrySink's opt-in captureLog.

onLog is always installed here — an existing webhookSink({ url, transform }) consumer will start forwarding every ctx.log line to the same endpoint after upgrading, with no toggle besides writing a transformLog: () => null. sentrySink took the opposite (safer) default: log capture is off unless captureLog is supplied. A boolean gate (or requiring transformLog to be a filter rather than the sole disable mechanism) would make the two sinks' privacy/cost posture consistent.

Also applies to: 229-267

🤖 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/runtime/src/observability-sinks.ts` around lines 197 - 205, The
webhookSink log-forwarding path should be opt-in, matching sentrySink’s
captureLog behavior, rather than always installing onLog. Update webhookSink and
its options so ctx.log events are forwarded only when an explicit
captureLog-style boolean is enabled, while preserving transformLog processing
for enabled forwarding and leaving existing non-log webhook events unchanged.
🤖 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.

Inline comments:
In `@apps/docs/src/content/docs/concepts/observability.mdx`:
- Around line 41-45: Correct the observability documentation sentence beginning
“All five network/log sinks” so its count matches the four listed sinks:
otlpSink, webhookSink, pipelineLogSink, and sentrySink. Update only the count or
wording needed to accurately describe these sinks.

In `@packages/do/src/request-log.ts`:
- Around line 372-391: Update isLogFields to accept only actual plain objects,
excluding Error, Date, Map, custom class instances, and other non-plain objects
while retaining null and array rejection. Keep parseLogArgs unchanged so only a
string plus validated plain-object fields uses structured logging; all other
arguments must continue through renderLogMessage.

---

Nitpick comments:
In `@packages/runtime/src/observability-sinks.ts`:
- Around line 197-205: The webhookSink log-forwarding path should be opt-in,
matching sentrySink’s captureLog behavior, rather than always installing onLog.
Update webhookSink and its options so ctx.log events are forwarded only when an
explicit captureLog-style boolean is enabled, while preserving transformLog
processing for enabled forwarding and leaving existing non-log webhook events
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8fc1de7f-6a12-48c3-acd4-05379598aecc

📥 Commits

Reviewing files that changed from the base of the PR and between ce59723 and d1c3427.

⛔ Files ignored due to path filters (15)
  • examples/auth-playground/lunora/_generated/shard.ts is excluded by !**/_generated/** and included by none
  • examples/blog/lunora/_generated/shard.ts is excluded by !**/_generated/** and included by none
  • examples/expo/lunora/_generated/shard.ts is excluded by !**/_generated/** and included by none
  • examples/offline-rejections/lunora/_generated/shard.ts is excluded by !**/_generated/** and included by none
  • examples/payment-demo/lunora/_generated/shard.ts is excluded by !**/_generated/** and included by none
  • examples/realtime-cursors/lunora/_generated/shard.ts is excluded by !**/_generated/** and included by none
  • examples/todo-app/lunora/_generated/shard.ts is excluded by !**/_generated/** and included by none
  • packages/codegen/__tests__/fixtures/simple/expected/_generated/shard.ts is excluded by !**/_generated/**, !**/__tests__/** and included by packages/**
  • packages/config/__tests__/log-format.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/do/__tests__/shard-do.admin.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/runtime/__tests__/observability-sinks.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/studio/__tests__/features/logs/logs-panel.test.tsx is excluded by !**/__tests__/** and included by packages/**
  • shared/log-event.ts is excluded by none and included by none
  • shared/log-fields.ts is excluded by none and included by none
  • shared/otlp.ts is excluded by none and included by none
📒 Files selected for processing (16)
  • apps/docs/src/content/docs/concepts/observability.mdx
  • packages/codegen/src/emit.ts
  • packages/config/src/log-format.ts
  • packages/config/tsconfig.json
  • packages/do/src/log-buffer.ts
  • packages/do/src/request-log.ts
  • packages/do/src/shard-do.ts
  • packages/runtime/src/index.ts
  • packages/runtime/src/observability-sinks.ts
  • packages/runtime/src/observability.ts
  • packages/server/src/index.ts
  • packages/server/src/types.ts
  • packages/studio/src/features/logs/logs-panel.tsx
  • packages/studio/src/lib/admin.ts
  • packages/studio/tsconfig.json
  • packages/testing/src/harness.ts

Comment thread apps/docs/src/content/docs/concepts/observability.mdx Outdated
Comment thread packages/do/src/request-log.ts Outdated
prisis and others added 2 commits July 20, 2026 08:51
- `pnpm run api:update` — the structured-logging exports drifted the public
  API of @lunora/runtime, /server, /do, /studio, and the lunora umbrella
  (`pipelineLogSink`, `LogFields`, `LunoraLogMethod`, `LogEntry.fields`, the
  shared `LogEvent` shape). Also catches up errors/nuxt snapshots that drifted
  on alpha independently of this change.
- Format the config log-format test to the root Prettier config the CI
  `prettier --check .` gate uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `pnpm/action-setup` steps pinned `version: 11.5.3` while
package.json's `packageManager` is `pnpm@11.15.0`, so the setup action
aborted with "Multiple versions of pnpm specified" (Benchmarks / CodSpeed,
release). Drop the pin so the action reads `packageManager` — one source of
truth, no drift. Align the CONTRIBUTING note accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

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

Actionable comments posted: 1

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

Inline comments:
In @.github/CONTRIBUTING.md:
- Line 10: Update the Corepack setup guidance in CONTRIBUTING so it works for
the documented Node.js range including 25+. Add the standalone Corepack
installation step before corepack enable, or revise the supported Node.js range
to exclude 25 and later; keep the packageManager pinning guidance consistent
with the selected approach.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32e2a9d0-377e-42f4-830b-73c1517f9681

📥 Commits

Reviewing files that changed from the base of the PR and between d1c3427 and ade431b.

⛔ Files ignored due to path filters (8)
  • api-snapshots/do.api.md is excluded by none and included by none
  • api-snapshots/errors.api.md is excluded by none and included by none
  • api-snapshots/lunora.api.md is excluded by none and included by none
  • api-snapshots/nuxt.api.md is excluded by none and included by none
  • api-snapshots/runtime.api.md is excluded by none and included by none
  • api-snapshots/server.api.md is excluded by none and included by none
  • api-snapshots/studio.api.md is excluded by none and included by none
  • packages/config/__tests__/log-format.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
📒 Files selected for processing (3)
  • .github/CONTRIBUTING.md
  • .github/workflows/codspeed.yml
  • .github/workflows/semantic-release.yml

Comment thread .github/CONTRIBUTING.md Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 20, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 28.36%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 159 untouched benchmarks
⏩ 1 skipped benchmark1

Performance Changes

Benchmark BASE HEAD Efficiency
compare cached argsKey (new) 78.3 µs 61 µs +28.36%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/observability-logs (4fe4b07) with alpha (a2d60e5)2

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

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

`prettier --check .` (the CI gate) flagged 8 files unrelated to this branch
— AGENTS.md and 7 studio UI components — that predate it under the root
Prettier config. Formatting them (plus the config test fixed earlier) makes
the prettier gate pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…itive

- E2E: `scaffold.spec.ts` resolved `typescript/lib/tsc.js`, which TS7-native's
  package `exports` no longer expose (nor `bin/tsc`). Read the launcher path
  from the compiler's own `package.json` `bin.tsc` — version-agnostic across
  classic and native.
- Secrets: `projectId` in a values type-test tripped the scanner's Cypress
  rule; annotate `secret-scanner:allow` — it's a schema field name, not a
  credential.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@prisis

prisis commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

CI status

Fixed on this branch (were failing due to the TS7/oxc migration on alpha, not this change):

  • api-surface — regenerated snapshots for the new logging exports (+ errors/nuxt baseline catch-up)
  • Benchmarks / CodSpeed — dropped the pnpm version pin so pnpm/action-setup reads packageManager
  • prettier — formatted the files the root config flagged
  • E2E — resolve tsc via the compiler's package.json bin.tsc (TS7-native drops lib/tsc.js from exports)
  • secretssecret-scanner:allow on a projectId type-test false-positive

Lint (eslint) remains red and is NOT fixable in this PR — ESLint crashes repo-wide under TypeScript 7 native (eslint-plugin-sonarjs + @typescript-eslint both read the classic compiler API that native omits). It's pre-existing on alpha and blocks every PR. Tracked in #148; it needs an upstream toolchain bump owned by the TS7 migration.

Address CodeRabbit review on #147.

- `isLogFields` accepted any non-array object, so `ctx.log.error("failed", err)`
  (the standard console-style idiom) routed the `Error` into the structured
  branch, where it has no own enumerable fields and was silently dropped.
  Restrict to plain objects (Object.prototype / null-prototype) so Errors,
  Dates, Maps, and class instances stay console-style; add a regression test.
- docs: "All five network/log sinks" → "four" (only four are listed).
- CONTRIBUTING: note Corepack isn't bundled on Node.js 25+ (install it, or
  install pnpm directly) since the supported range includes 25+.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
apps/docs/src/content/docs/concepts/observability.mdx (2)

80-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clarify “no cloud required.”

Cloudflare Pipeline → R2 is itself cloud-backed. If the intent is “no Lunora-managed cloud required,” state that explicitly to avoid promising local/offline durability.

🤖 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 `@apps/docs/src/content/docs/concepts/observability.mdx` around lines 80 - 82,
Update the observability documentation text describing Cloudflare Pipeline → R2
to clarify that “no cloud required” means no Lunora-managed cloud or hosted
service is required, not that storage is local or offline. Preserve the
explanation that logs are durably stored in the user’s own Cloudflare account.

92-94: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document optional fields and conditional waitUntil.

The implementation only adds fields, shardKey, userId, traceId, and spanId when defined, and calls waitUntil only when available. Replace “Each record carries” and “The send is registered” with conditional wording.

🤖 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 `@apps/docs/src/content/docs/concepts/observability.mdx` around lines 92 - 94,
Update the observability documentation around the record-field description to
state that fields, shardKey, userId, traceId, and spanId are included only when
defined. Also describe waitUntil registration as conditional on its
availability, rather than implying every request provides it.
🤖 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.

Inline comments:
In `@apps/docs/src/content/docs/concepts/observability.mdx`:
- Around line 40-44: Update the observability documentation around `onlyErrors`
to state that it applies only to sinks whose APIs support RPC filtering,
excluding `pipelineLogSink`. Keep the separate description of log-event
forwarding and `transformLog` behavior accurate, and do not imply that every
sink accepts `onlyErrors`.

---

Outside diff comments:
In `@apps/docs/src/content/docs/concepts/observability.mdx`:
- Around line 80-82: Update the observability documentation text describing
Cloudflare Pipeline → R2 to clarify that “no cloud required” means no
Lunora-managed cloud or hosted service is required, not that storage is local or
offline. Preserve the explanation that logs are durably stored in the user’s own
Cloudflare account.
- Around line 92-94: Update the observability documentation around the
record-field description to state that fields, shardKey, userId, traceId, and
spanId are included only when defined. Also describe waitUntil registration as
conditional on its availability, rather than implying every request provides it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 69c5b7f7-362a-4bf0-95c9-5c4883f033cd

📥 Commits

Reviewing files that changed from the base of the PR and between 0ff519a and b8dbffb.

⛔ Files ignored due to path filters (1)
  • packages/do/__tests__/shard-do.admin.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
📒 Files selected for processing (3)
  • .github/CONTRIBUTING.md
  • apps/docs/src/content/docs/concepts/observability.mdx
  • packages/do/src/request-log.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/CONTRIBUTING.md
  • packages/do/src/request-log.ts

Comment thread apps/docs/src/content/docs/concepts/observability.mdx Outdated
CodeRabbit review on #147: `pipelineLogSink` is log-only (`{ pipeline }`, no
`onlyErrors`), so "every sink accepts onlyErrors" was inaccurate. Qualify it to
sinks that carry RPC events.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@prisis
prisis merged commit 716c1cd into alpha Jul 20, 2026
43 of 45 checks passed
@prisis
prisis deleted the feat/observability-logs branch July 20, 2026 10:44
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