Skip to content

feat: experimental tracing logger#4406

Merged
pi0 merged 2 commits into
mainfrom
feat/tracing-logger
Jul 7, 2026
Merged

feat: experimental tracing logger#4406
pi0 merged 2 commits into
mainfrom
feat/tracing-logger

Conversation

@pi0x

@pi0x pi0x commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

continue from #4001 > #4355

This PR refactors span creation logic from vercel preset into generic internals and adds new experimental.tracingLogger to easily do tracing in dev and prod via console

image

@pi0x pi0x requested a review from pi0 as a code owner July 7, 2026 11:37
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nitro.build Ready Ready Preview, Comment Jul 7, 2026 12:23pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds Node diagnostics-channel-based tracing to Nitro, including OTLP span models, traced channel subscription, a console logger plugin, Vercel telemetry wiring, tracing config options, and a tracing example app.

Changes

Tracing infrastructure and integrations

Layer / File(s) Summary
OTLP types and span model
src/runtime/internal/telemetry/types.ts, src/runtime/internal/telemetry/span.ts
Defines OTLP trace/span interfaces and implements the Span class with timestamp/id generation and error-event handling.
Traced channel describers and subscription
src/runtime/internal/telemetry/channels.ts, src/runtime/internal/telemetry/subscribe.ts
Adds TRACED_CHANNELS describers for h3, srvx, and unstorage operations, and subscribeTracedChannels to derive SpanInfo from diagnostics-channel events.
Console telemetry logger plugin
src/runtime/internal/telemetry/logger-plugin.ts, test/unit/telemetry-logger.test.ts
Implements the per-request console timeline logger, a flat fallback logger, and unit tests for rendering, grouping, and error output.
Vercel telemetry export and config wiring
src/presets/vercel/runtime/telemetry/plugin.ts, src/config/resolvers/tracing.ts, src/types/config.ts
Switches Vercel telemetry to the shared subscription helper and adds experimental.tracingLogger config/resolver wiring.
Tracing example application
examples/tracing/*
Adds an example Nitro app, tracing config, and README instructions showing traced requests and console output.

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

Possibly related PRs

  • nitrojs/nitro#4001: This PR also touches src/config/resolvers/tracing.ts and tracing-channel enablement, with overlapping resolver wiring for Nitro tracing.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title follows conventional commit format and accurately summarizes the main change.
Description check ✅ Passed The description is directly related to the tracing logger refactor and the new experimental option.
✨ 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/tracing-logger

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.

@socket-security

socket-security Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​typescript/​native-preview@​7.0.0-dev.20260706.19410076100100

View full report

@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4406

commit: cc4646f

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/config/resolvers/tracing.ts (1)

18-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent override of features.runtimeHooks when a user explicitly disabled it.

options.features.runtimeHooks = true unconditionally overwrites any explicit false the user may have set (e.g. to opt out for perf reasons). Since this is a hard requirement for tracingLogger to function, consider logging a short notice when overriding an explicit false so the behavior isn't silently surprising.

🤖 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 `@src/config/resolvers/tracing.ts` around lines 18 - 24, The tracing logger
setup in the tracing resolver unconditionally flips
options.features.runtimeHooks to true, which can silently override an explicit
false. Update the logic in the tracingLogger branch to detect when the user has
set runtimeHooks to false, then emit a short notice through the existing logging
mechanism before overriding it; keep the current behavior that forces
runtimeHooks on for the logger-plugin and preserve the existing
options.plugins.unshift and options.features initialization flow.
src/runtime/internal/telemetry/channels.ts (1)

106-128: 📐 Maintainability & Code Quality | 🔵 Trivial

Redundant re-derivation of operation inside the closure.

The describer recomputes operation from channel.slice(...), shadowing the outer operation already captured from the .map() iteration that produced this exact key. Since the closure is keyed to a specific channel name, the outer variable can be used directly.

♻️ Suggested simplification
     ].map((operation) => [
       `unstorage.${operation}`,
       (channel: string, data: unknown) => {
         const { driver, base, keys } = data as {
           driver?: { name?: string };
           base?: string;
           keys?: unknown[];
         };
-        const operation = channel.slice("unstorage.".length);
         // CLIENT: storage is a known outbound dependency (OTEL database semconv).
🤖 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 `@src/runtime/internal/telemetry/channels.ts` around lines 106 - 128, The
channel describer in `channels.ts` recomputes and shadows `operation` inside the
`unstorage.${operation}` callback even though the outer `.map()` iteration
already provides the exact value. Update the closure to use the captured outer
`operation` directly in the `attributes` and span `name` construction, and
remove the redundant `channel.slice(...)` re-derivation so the logic stays
simpler and avoids shadowing.
src/runtime/internal/telemetry/span.ts (2)

31-37: 📐 Maintainability & Code Quality | 🔵 Trivial

Constructor takes 5 positional args instead of an options object.

As per coding guidelines, src/**/*.{ts,js} should use "an options object as the second parameter" for multi-argument functions. This constructor takes traceId, parentSpanId, info, startTimeUnixNano, error positionally, which is error-prone to call correctly (e.g. mixing up parentSpanId/startTimeUnixNano, both strings) and harder to extend later.

♻️ Suggested signature
-  constructor(
-    traceId: string,
-    parentSpanId: string,
-    info: SpanInfo,
-    startTimeUnixNano: string,
-    error: unknown
-  ) {
-    this.traceId = traceId;
+  constructor(
+    traceId: string,
+    info: SpanInfo,
+    options: { parentSpanId: string; startTimeUnixNano: string; error?: unknown }
+  ) {
+    this.traceId = traceId;
+    const { parentSpanId, startTimeUnixNano, error } = options;
🤖 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 `@src/runtime/internal/telemetry/span.ts` around lines 31 - 37, The Span
constructor currently accepts five positional arguments, which violates the
options-object guideline and makes call sites easy to misuse. Update the Span
class constructor to take a single options object (including traceId,
parentSpanId, info, startTimeUnixNano, and error) and adjust any instantiation
sites to pass named properties instead of ordered parameters. Keep the Span
class and its constructor as the primary symbols to locate and refactor.

Source: Coding guidelines


81-90: 📐 Maintainability & Code Quality | 🔵 Trivial

Consider a stronger RNG for span IDs.

Math.random() is not collision-resistant; the Web Crypto API (crypto.getRandomValues), which is available globally and fits the "prefer Web APIs" guideline for src/runtime/, would reduce collision risk at negligible cost.

♻️ Suggested implementation
-  static randomSpanId(): string {
-    let id = "";
-    for (let i = 0; i < 8; i++) {
-      id += Math.floor(Math.random() * 256)
-        .toString(16)
-        .padStart(2, "0");
-    }
-    return id === "0000000000000000" ? "0000000000000001" : id;
-  }
+  static randomSpanId(): string {
+    const bytes = crypto.getRandomValues(new Uint8Array(8));
+    const id = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
+    return id === "0000000000000000" ? "0000000000000001" : id;
+  }
🤖 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 `@src/runtime/internal/telemetry/span.ts` around lines 81 - 90, The span ID
generator in `Span.randomSpanId()` currently uses `Math.random()`, which is too
weak for collision resistance. Update it to use the Web Crypto API via
`crypto.getRandomValues` to generate the 8 random bytes, keeping the same
hex-encoded 16-character output and the existing all-zero sentinel fallback.
Make the change within `src/runtime/internal/telemetry/span.ts` so the runtime
follows the preferred Web API approach.

Source: Coding guidelines

🤖 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 `@src/presets/vercel/runtime/telemetry/plugin.ts`:
- Around line 69-96: The fire-and-forget flush inside the telemetry plugin can
reject if sink.reportSpans() throws, which leaves the context.waitUntil task
unhandled. Update the flush callback in plugin.ts to wrap the sink.reportSpans
call in a try/catch and swallow or safely log failures so the pendingSpans batch
cleanup still runs and the request lifecycle is not destabilized. Use the
existing root.traceId, pendingSpans, and sink references to locate the flush
path.

---

Nitpick comments:
In `@src/config/resolvers/tracing.ts`:
- Around line 18-24: The tracing logger setup in the tracing resolver
unconditionally flips options.features.runtimeHooks to true, which can silently
override an explicit false. Update the logic in the tracingLogger branch to
detect when the user has set runtimeHooks to false, then emit a short notice
through the existing logging mechanism before overriding it; keep the current
behavior that forces runtimeHooks on for the logger-plugin and preserve the
existing options.plugins.unshift and options.features initialization flow.

In `@src/runtime/internal/telemetry/channels.ts`:
- Around line 106-128: The channel describer in `channels.ts` recomputes and
shadows `operation` inside the `unstorage.${operation}` callback even though the
outer `.map()` iteration already provides the exact value. Update the closure to
use the captured outer `operation` directly in the `attributes` and span `name`
construction, and remove the redundant `channel.slice(...)` re-derivation so the
logic stays simpler and avoids shadowing.

In `@src/runtime/internal/telemetry/span.ts`:
- Around line 31-37: The Span constructor currently accepts five positional
arguments, which violates the options-object guideline and makes call sites easy
to misuse. Update the Span class constructor to take a single options object
(including traceId, parentSpanId, info, startTimeUnixNano, and error) and adjust
any instantiation sites to pass named properties instead of ordered parameters.
Keep the Span class and its constructor as the primary symbols to locate and
refactor.
- Around line 81-90: The span ID generator in `Span.randomSpanId()` currently
uses `Math.random()`, which is too weak for collision resistance. Update it to
use the Web Crypto API via `crypto.getRandomValues` to generate the 8 random
bytes, keeping the same hex-encoded 16-character output and the existing
all-zero sentinel fallback. Make the change within
`src/runtime/internal/telemetry/span.ts` so the runtime follows the preferred
Web API 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 50502117-a3b9-4ee2-9826-cbe864816984

📥 Commits

Reviewing files that changed from the base of the PR and between ca1e904 and 4143074.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • examples/tracing/README.md
  • examples/tracing/nitro.config.ts
  • examples/tracing/package.json
  • examples/tracing/server/middleware/timing.ts
  • examples/tracing/server/routes/index.ts
  • examples/tracing/server/routes/users/[id].ts
  • examples/tracing/tsconfig.json
  • examples/tracing/vite.config.ts
  • src/config/resolvers/tracing.ts
  • src/presets/vercel/preset.ts
  • src/presets/vercel/runtime/telemetry/plugin.ts
  • src/runtime/internal/telemetry/channels.ts
  • src/runtime/internal/telemetry/logger-plugin.ts
  • src/runtime/internal/telemetry/span.ts
  • src/runtime/internal/telemetry/subscribe.ts
  • src/runtime/internal/telemetry/types.ts
  • src/types/config.ts
  • test/unit/telemetry-logger.test.ts
  • test/unit/vercel-telemetry.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/config/resolvers/tracing.ts (1)

18-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent override of features.runtimeHooks when a user explicitly disabled it.

options.features.runtimeHooks = true unconditionally overwrites any explicit false the user may have set (e.g. to opt out for perf reasons). Since this is a hard requirement for tracingLogger to function, consider logging a short notice when overriding an explicit false so the behavior isn't silently surprising.

🤖 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 `@src/config/resolvers/tracing.ts` around lines 18 - 24, The tracing logger
setup in the tracing resolver unconditionally flips
options.features.runtimeHooks to true, which can silently override an explicit
false. Update the logic in the tracingLogger branch to detect when the user has
set runtimeHooks to false, then emit a short notice through the existing logging
mechanism before overriding it; keep the current behavior that forces
runtimeHooks on for the logger-plugin and preserve the existing
options.plugins.unshift and options.features initialization flow.
src/runtime/internal/telemetry/channels.ts (1)

106-128: 📐 Maintainability & Code Quality | 🔵 Trivial

Redundant re-derivation of operation inside the closure.

The describer recomputes operation from channel.slice(...), shadowing the outer operation already captured from the .map() iteration that produced this exact key. Since the closure is keyed to a specific channel name, the outer variable can be used directly.

♻️ Suggested simplification
     ].map((operation) => [
       `unstorage.${operation}`,
       (channel: string, data: unknown) => {
         const { driver, base, keys } = data as {
           driver?: { name?: string };
           base?: string;
           keys?: unknown[];
         };
-        const operation = channel.slice("unstorage.".length);
         // CLIENT: storage is a known outbound dependency (OTEL database semconv).
🤖 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 `@src/runtime/internal/telemetry/channels.ts` around lines 106 - 128, The
channel describer in `channels.ts` recomputes and shadows `operation` inside the
`unstorage.${operation}` callback even though the outer `.map()` iteration
already provides the exact value. Update the closure to use the captured outer
`operation` directly in the `attributes` and span `name` construction, and
remove the redundant `channel.slice(...)` re-derivation so the logic stays
simpler and avoids shadowing.
src/runtime/internal/telemetry/span.ts (2)

31-37: 📐 Maintainability & Code Quality | 🔵 Trivial

Constructor takes 5 positional args instead of an options object.

As per coding guidelines, src/**/*.{ts,js} should use "an options object as the second parameter" for multi-argument functions. This constructor takes traceId, parentSpanId, info, startTimeUnixNano, error positionally, which is error-prone to call correctly (e.g. mixing up parentSpanId/startTimeUnixNano, both strings) and harder to extend later.

♻️ Suggested signature
-  constructor(
-    traceId: string,
-    parentSpanId: string,
-    info: SpanInfo,
-    startTimeUnixNano: string,
-    error: unknown
-  ) {
-    this.traceId = traceId;
+  constructor(
+    traceId: string,
+    info: SpanInfo,
+    options: { parentSpanId: string; startTimeUnixNano: string; error?: unknown }
+  ) {
+    this.traceId = traceId;
+    const { parentSpanId, startTimeUnixNano, error } = options;
🤖 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 `@src/runtime/internal/telemetry/span.ts` around lines 31 - 37, The Span
constructor currently accepts five positional arguments, which violates the
options-object guideline and makes call sites easy to misuse. Update the Span
class constructor to take a single options object (including traceId,
parentSpanId, info, startTimeUnixNano, and error) and adjust any instantiation
sites to pass named properties instead of ordered parameters. Keep the Span
class and its constructor as the primary symbols to locate and refactor.

Source: Coding guidelines


81-90: 📐 Maintainability & Code Quality | 🔵 Trivial

Consider a stronger RNG for span IDs.

Math.random() is not collision-resistant; the Web Crypto API (crypto.getRandomValues), which is available globally and fits the "prefer Web APIs" guideline for src/runtime/, would reduce collision risk at negligible cost.

♻️ Suggested implementation
-  static randomSpanId(): string {
-    let id = "";
-    for (let i = 0; i < 8; i++) {
-      id += Math.floor(Math.random() * 256)
-        .toString(16)
-        .padStart(2, "0");
-    }
-    return id === "0000000000000000" ? "0000000000000001" : id;
-  }
+  static randomSpanId(): string {
+    const bytes = crypto.getRandomValues(new Uint8Array(8));
+    const id = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
+    return id === "0000000000000000" ? "0000000000000001" : id;
+  }
🤖 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 `@src/runtime/internal/telemetry/span.ts` around lines 81 - 90, The span ID
generator in `Span.randomSpanId()` currently uses `Math.random()`, which is too
weak for collision resistance. Update it to use the Web Crypto API via
`crypto.getRandomValues` to generate the 8 random bytes, keeping the same
hex-encoded 16-character output and the existing all-zero sentinel fallback.
Make the change within `src/runtime/internal/telemetry/span.ts` so the runtime
follows the preferred Web API approach.

Source: Coding guidelines

🤖 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 `@src/presets/vercel/runtime/telemetry/plugin.ts`:
- Around line 69-96: The fire-and-forget flush inside the telemetry plugin can
reject if sink.reportSpans() throws, which leaves the context.waitUntil task
unhandled. Update the flush callback in plugin.ts to wrap the sink.reportSpans
call in a try/catch and swallow or safely log failures so the pendingSpans batch
cleanup still runs and the request lifecycle is not destabilized. Use the
existing root.traceId, pendingSpans, and sink references to locate the flush
path.

---

Nitpick comments:
In `@src/config/resolvers/tracing.ts`:
- Around line 18-24: The tracing logger setup in the tracing resolver
unconditionally flips options.features.runtimeHooks to true, which can silently
override an explicit false. Update the logic in the tracingLogger branch to
detect when the user has set runtimeHooks to false, then emit a short notice
through the existing logging mechanism before overriding it; keep the current
behavior that forces runtimeHooks on for the logger-plugin and preserve the
existing options.plugins.unshift and options.features initialization flow.

In `@src/runtime/internal/telemetry/channels.ts`:
- Around line 106-128: The channel describer in `channels.ts` recomputes and
shadows `operation` inside the `unstorage.${operation}` callback even though the
outer `.map()` iteration already provides the exact value. Update the closure to
use the captured outer `operation` directly in the `attributes` and span `name`
construction, and remove the redundant `channel.slice(...)` re-derivation so the
logic stays simpler and avoids shadowing.

In `@src/runtime/internal/telemetry/span.ts`:
- Around line 31-37: The Span constructor currently accepts five positional
arguments, which violates the options-object guideline and makes call sites easy
to misuse. Update the Span class constructor to take a single options object
(including traceId, parentSpanId, info, startTimeUnixNano, and error) and adjust
any instantiation sites to pass named properties instead of ordered parameters.
Keep the Span class and its constructor as the primary symbols to locate and
refactor.
- Around line 81-90: The span ID generator in `Span.randomSpanId()` currently
uses `Math.random()`, which is too weak for collision resistance. Update it to
use the Web Crypto API via `crypto.getRandomValues` to generate the 8 random
bytes, keeping the same hex-encoded 16-character output and the existing
all-zero sentinel fallback. Make the change within
`src/runtime/internal/telemetry/span.ts` so the runtime follows the preferred
Web API 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 50502117-a3b9-4ee2-9826-cbe864816984

📥 Commits

Reviewing files that changed from the base of the PR and between ca1e904 and 4143074.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • examples/tracing/README.md
  • examples/tracing/nitro.config.ts
  • examples/tracing/package.json
  • examples/tracing/server/middleware/timing.ts
  • examples/tracing/server/routes/index.ts
  • examples/tracing/server/routes/users/[id].ts
  • examples/tracing/tsconfig.json
  • examples/tracing/vite.config.ts
  • src/config/resolvers/tracing.ts
  • src/presets/vercel/preset.ts
  • src/presets/vercel/runtime/telemetry/plugin.ts
  • src/runtime/internal/telemetry/channels.ts
  • src/runtime/internal/telemetry/logger-plugin.ts
  • src/runtime/internal/telemetry/span.ts
  • src/runtime/internal/telemetry/subscribe.ts
  • src/runtime/internal/telemetry/types.ts
  • src/types/config.ts
  • test/unit/telemetry-logger.test.ts
  • test/unit/vercel-telemetry.test.ts
🛑 Comments failed to post (1)
src/presets/vercel/runtime/telemetry/plugin.ts (1)

69-96: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wrap sink.reportSpans() in try/catch to avoid an unhandled rejection from the fire-and-forget flush task.

The async task handed to context.waitUntil calls sink.reportSpans(data) (line 80) with no error handling. If this throws (network hiccup, malformed payload rejected by Vercel's IPC layer, etc.), the returned promise rejects with nothing to catch it — this telemetry sink should never risk destabilizing the request lifecycle it's attached to.

🛡️ Proposed fix
     context.waitUntil(async () => {
       const batch = pendingSpans.get(traceId);
       pendingSpans.delete(traceId);
       if (!batch || batch.length === 0) return;
-      // Wrap the batch in a single OTLP `ExportTraceServiceRequest` envelope.
-      sink.reportSpans({
-        resourceSpans: [
-          {
-            resource: { attributes: [], droppedAttributesCount: 0 },
-            scopeSpans: [
-              {
-                scope: { name: SCOPE_NAME, version: "", attributes: [], droppedAttributesCount: 0 },
-                spans: batch,
-                schemaUrl: "",
-              },
-            ],
-            schemaUrl: "",
-          },
-        ],
-      });
+      try {
+        // Wrap the batch in a single OTLP `ExportTraceServiceRequest` envelope.
+        sink.reportSpans({
+          resourceSpans: [
+            {
+              resource: { attributes: [], droppedAttributesCount: 0 },
+              scopeSpans: [
+                {
+                  scope: { name: SCOPE_NAME, version: "", attributes: [], droppedAttributesCount: 0 },
+                  spans: batch,
+                  schemaUrl: "",
+                },
+              ],
+              schemaUrl: "",
+            },
+          ],
+        });
+      } catch {
+        // Telemetry export failures must never surface to the request lifecycle.
+      }
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  // Buffer per request; the first span schedules a single flush at freeze time.
  let spans = pendingSpans.get(root.traceId);
  if (!spans) {
    spans = [];
    const { traceId } = root;
    const sink = telemetry;
    context.waitUntil(async () => {
      const batch = pendingSpans.get(traceId);
      pendingSpans.delete(traceId);
      if (!batch || batch.length === 0) return;
      try {
        // Wrap the batch in a single OTLP `ExportTraceServiceRequest` envelope.
        sink.reportSpans({
          resourceSpans: [
            {
              resource: { attributes: [], droppedAttributesCount: 0 },
              scopeSpans: [
                {
                  scope: { name: SCOPE_NAME, version: "", attributes: [], droppedAttributesCount: 0 },
                  spans: batch,
                  schemaUrl: "",
                },
              ],
              schemaUrl: "",
            },
          ],
        });
      } catch {
        // Telemetry export failures must never surface to the request lifecycle.
      }
    });
    pendingSpans.set(traceId, spans);
🤖 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 `@src/presets/vercel/runtime/telemetry/plugin.ts` around lines 69 - 96, The
fire-and-forget flush inside the telemetry plugin can reject if
sink.reportSpans() throws, which leaves the context.waitUntil task unhandled.
Update the flush callback in plugin.ts to wrap the sink.reportSpans call in a
try/catch and swallow or safely log failures so the pendingSpans batch cleanup
still runs and the request lifecycle is not destabilized. Use the existing
root.traceId, pendingSpans, and sink references to locate the flush path.

Add `experimental.tracingLogger`, a built-in, dependency-free telemetry
sink that groups each request's tracing-channel spans (h3, srvx,
unstorage) into a per-request console timeline. Grouping uses the
`request`/`response` runtime hooks + async context, so it works in both
`vite dev` and production.

Move the platform-agnostic telemetry core (channels, span, OTLP types)
from the vercel preset to `src/runtime/internal/telemetry` as the shared
source of truth; the vercel exporter and the new logger both consume it
via `subscribeTracedChannels`. Includes an `examples/tracing` demo.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@pi0 pi0 changed the title feat/tracing logger feat: experimental tracing logger Jul 7, 2026
@pi0x pi0x force-pushed the feat/tracing-logger branch from a0901c1 to 837628b Compare July 7, 2026 12:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@examples/tracing/README.md`:
- Around line 32-38: The rendered example block in the tracing README is missing
a language tag, which triggers markdownlint MD040. Update the fenced code block
in the example output to use a plain text/console label so the docs stay
lint-clean, and keep the content unchanged otherwise.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f233ec9-b8af-4b89-8c7c-b1bcffcfa5b5

📥 Commits

Reviewing files that changed from the base of the PR and between a0901c1 and 837628b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • examples/tracing/README.md
  • examples/tracing/nitro.config.ts
  • examples/tracing/package.json
  • examples/tracing/server/middleware/timing.ts
  • examples/tracing/server/routes/index.ts
  • examples/tracing/server/routes/users/[id].ts
  • examples/tracing/tsconfig.json
  • examples/tracing/vite.config.ts
  • src/config/resolvers/tracing.ts
  • src/presets/vercel/runtime/telemetry/plugin.ts
  • src/runtime/internal/telemetry/channels.ts
  • src/runtime/internal/telemetry/logger-plugin.ts
  • src/runtime/internal/telemetry/span.ts
  • src/runtime/internal/telemetry/subscribe.ts
  • src/runtime/internal/telemetry/types.ts
  • src/types/config.ts
  • test/unit/telemetry-logger.test.ts
  • test/unit/vercel-telemetry.test.ts
✅ Files skipped from review due to trivial changes (2)
  • examples/tracing/tsconfig.json
  • examples/tracing/server/middleware/timing.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • examples/tracing/vite.config.ts
  • examples/tracing/package.json
  • examples/tracing/server/routes/index.ts
  • examples/tracing/server/routes/users/[id].ts
  • src/types/config.ts
  • examples/tracing/nitro.config.ts
  • src/config/resolvers/tracing.ts
  • src/runtime/internal/telemetry/subscribe.ts
  • test/unit/telemetry-logger.test.ts
  • src/presets/vercel/runtime/telemetry/plugin.ts
  • src/runtime/internal/telemetry/logger-plugin.ts

Comment thread examples/tracing/README.md
@pi0 pi0 merged commit 27c2f57 into main Jul 7, 2026
13 checks passed
@pi0 pi0 deleted the feat/tracing-logger branch July 7, 2026 12:38
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.

2 participants