Skip to content

presets(vercel): export tracing channels messages as OTLP spans#4355

Open
RihanArfan wants to merge 9 commits into
mainfrom
feat/vercel-telemetry-tracing-channels
Open

presets(vercel): export tracing channels messages as OTLP spans#4355
RihanArfan wants to merge 9 commits into
mainfrom
feat/vercel-telemetry-tracing-channels

Conversation

@RihanArfan

Copy link
Copy Markdown
Member

🔗 Linked issue

#4001

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Export tracing channel messages as OpenTelemetry spans when deployed on Vercel. This adds a Vercel-preset runtime plugin that turns those events into OpenTelemetry spans and reports them to Vercel's tracing pipeline, so app-level spans show up in Vercel session traces with no OpenTelemetry SDK bundled.

See https://vercel.com/docs/tracing/session-tracing for Vercel usage

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

POC notes:

  • Spans are parented flat under the platform root (siblings, not nested) and tagged internal
  • One span per IPC message; per-request batching (like @vercel/otel's BatchSpanProcessor + waitUntil) is a TODO for high-span-count routes.
  • Only channels created after the plugin runs are captured (no API to enumerate pre-existing ones); current producers create theirs lazily, so they're covered.

@vercel

vercel Bot commented Jun 16, 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 Jun 18, 2026 8:41pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Vercel-specific Nitro runtime telemetry plugin that instruments node:diagnostics_channel tracing producers, converts completed channel operations to OTLP spans using vendored type definitions, buffers them per request trace ID, and flushes a batched ExportTraceServiceRequest to the Vercel telemetry sink via waitUntil. The Vercel preset's build:before hook conditionally registers this plugin when tracingChannel is enabled.

Changes

Vercel Runtime Telemetry Plugin

Layer / File(s) Summary
OTLP and SpanContext type contracts
src/presets/vercel/runtime/telemetry-types.ts
Defines vendored minimal OTLP wire types: IExportTraceServiceRequest with nested resource/scope/span structures, ISpan with unix-nano timestamps and kind, ISpanEvent, IKeyValue/IAnyValue primitive union, and SpanContext.
Request context, Span class, and helpers
src/presets/vercel/runtime/telemetry.ts
Adds REQUEST_CONTEXT_SYMBOL-based lookup for per-request VercelTelemetry and waitUntil, a Span class implementing ISpan with nanosecond timing and error-to-OTLP-status mapping, and OTLP attribute/value utilities including exceptionEvent.
Per-request span buffering and OTLP flush
src/presets/vercel/runtime/telemetry.ts
Implements pendingSpans map keyed by traceId, reportSpan() with sampling gating and single-waitUntil flush scheduling, and envelope() wrapping spans into an IExportTraceServiceRequest for delivery to the Vercel telemetry sink.
Channel-to-span describers
src/presets/vercel/runtime/telemetry.ts
Defines ChannelDescriber interface, exact/prefix-match registries, and describeSpan() with fallback to a generic INTERNAL span. Implements concrete describers for h3.request, srvx.request, srvx.middleware, and unstorage.* channels.
diagnostics_channel patching and plugin export
src/presets/vercel/runtime/telemetry.ts
Patches node:diagnostics_channel's tracingChannel once per plugin activation, subscribes start/asyncEnd on each new channel to record start timestamps and call reportSpan, and swallows telemetry errors to isolate traced operations.
Preset build:before hook registration
src/presets/vercel/preset.ts
Conditionally initializes nitro.options.plugins and unshifts the telemetry plugin path when nitro.options.tracingChannel is set.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • nitrojs/nitro#4001: Adds the virtual tracing channel plugins for h3/srvx that produce the diagnostics_channel events consumed by this PR's Vercel telemetry exporter, sharing the same nitro.options.tracingChannel feature gate.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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 follows conventional commits format with scope and descriptive message, and accurately summarizes the main changes related to tracing channel export.
Description check ✅ Passed The description comprehensively relates to the changeset, explaining the purpose of exporting tracing channels as OTLP spans and providing implementation context.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/vercel-telemetry-tracing-channels

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 and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Jun 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 6e462d3

@pi0 pi0 changed the title feat(vercel): pass tracing channels messages presets(vercel): pass tracing channels messages Jun 16, 2026
Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
@RihanArfan RihanArfan changed the title presets(vercel): pass tracing channels messages presets(vercel): export tracing channels messages as OTLP spans Jun 16, 2026
Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
@socket-security

socket-security Bot commented Jun 17, 2026

Copy link
Copy Markdown

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
Comment thread src/presets/vercel/runtime/telemetry.ts Outdated
@RihanArfan RihanArfan marked this pull request as ready for review June 18, 2026 20:38

@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 (2)
src/presets/vercel/runtime/telemetry.ts (2)

221-225: ⚡ Quick win

Emit warnings in recoverable catch blocks instead of fully silent fallback.

Line 223 and Line 347 currently discard error context entirely. Keeping fallback behavior is fine, but add a warning with channel/phase so telemetry regressions are diagnosable.

Proposed fix
   if (describe) {
     try {
       return describe(channel, data);
-    } catch {
-      // Fall through to the generic span below.
+    } catch (error) {
+      console.warn("[vercel-telemetry] span describer failed", { channel, error });
     }
   }
@@
       asyncEnd(message) {
         try {
           const start = starts.get(message);
           if (start === undefined) return;
           starts.delete(message);
           reportSpan(describeSpan(name, message), start, message.error);
-        } catch {
-          // Telemetry must never break the traced operation.
+        } catch (error) {
+          console.warn("[vercel-telemetry] asyncEnd processing failed", {
+            channel: name,
+            error,
+          });
         }
       },

As per coding guidelines: "Use warnings for recoverable situations; throw for invalid states".

Also applies to: 347-349

🤖 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.ts` around lines 221 - 225, In the catch
block for the describe function call where channel and data are used, add a
warning emission that includes the channel identifier and phase information
instead of silently falling through. This allows telemetry issues to be
diagnosed when the describe call fails. The same pattern should be applied to
the similar catch block mentioned at line 347-349 that also silently discards
error context. Ensure both recoverable error scenarios emit diagnostic warnings
with relevant context while maintaining the fallback behavior.

Source: Coding guidelines


1-406: 🏗️ Heavy lift

Split this runtime module; it exceeds the >200 LoC guideline.

This file currently mixes distinct concerns (span model, describers, buffering/export, diagnostics patching). Please split into focused runtime modules (for example _span.ts, _describers.ts, _exporter.ts) and keep the plugin entry thin.

As per coding guidelines: "Split logic across files; avoid long single-file modules (>200 LoC). Use _* prefix for internal files".

🤖 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.ts` around lines 1 - 406, The
telemetry.ts file currently contains over 400 lines mixing span model, channel
describers, buffering/export logic, and diagnostics patching. Split this into
focused internal modules: create `_span.ts` containing the `Span` class and
span-related utilities like `Span.nowUnixNano()` and `Span.randomSpanId()`;
create `_describers.ts` containing the channel describer functions
(`describeH3Request`, `describeSrvxRequest`, `describeSrvxMiddleware`,
`describeUnstorage`) and their helper types and constants (`CHANNEL_DESCRIBERS`,
`PREFIX_DESCRIBERS`, `describeSpan`); create `_exporter.ts` containing the
buffering and export logic (`pendingSpans`, `reportSpan`, `envelope`); and keep
the main telemetry.ts file thin with just the plugin export definition and the
diagnostics patching logic. Move helper utilities (`attr`, `asRecord`,
`asString`, `label`, `errorMessage`, `exceptionEvent`) to the appropriate module
where they are primarily used. Import the split modules back into the main file
to maintain the current behavior.

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.ts`:
- Around line 157-161: The `sink.reportSpans` call within the
`context.waitUntil` async callback is not protected from throwing errors, which
can cause promise rejection and leak telemetry failures into request teardown
behavior. Wrap the `sink.reportSpans(envelope(batch))` call in a try-catch block
to handle any potential errors gracefully, catching exceptions and logging them
as warnings rather than allowing the error to propagate and reject the waitUntil
promise.

---

Nitpick comments:
In `@src/presets/vercel/runtime/telemetry.ts`:
- Around line 221-225: In the catch block for the describe function call where
channel and data are used, add a warning emission that includes the channel
identifier and phase information instead of silently falling through. This
allows telemetry issues to be diagnosed when the describe call fails. The same
pattern should be applied to the similar catch block mentioned at line 347-349
that also silently discards error context. Ensure both recoverable error
scenarios emit diagnostic warnings with relevant context while maintaining the
fallback behavior.
- Around line 1-406: The telemetry.ts file currently contains over 400 lines
mixing span model, channel describers, buffering/export logic, and diagnostics
patching. Split this into focused internal modules: create `_span.ts` containing
the `Span` class and span-related utilities like `Span.nowUnixNano()` and
`Span.randomSpanId()`; create `_describers.ts` containing the channel describer
functions (`describeH3Request`, `describeSrvxRequest`, `describeSrvxMiddleware`,
`describeUnstorage`) and their helper types and constants (`CHANNEL_DESCRIBERS`,
`PREFIX_DESCRIBERS`, `describeSpan`); create `_exporter.ts` containing the
buffering and export logic (`pendingSpans`, `reportSpan`, `envelope`); and keep
the main telemetry.ts file thin with just the plugin export definition and the
diagnostics patching logic. Move helper utilities (`attr`, `asRecord`,
`asString`, `label`, `errorMessage`, `exceptionEvent`) to the appropriate module
where they are primarily used. Import the split modules back into the main file
to maintain the current behavior.
🪄 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: ba5022c7-a3ad-432e-8208-e5e97ced9d45

📥 Commits

Reviewing files that changed from the base of the PR and between e59431c and 6e462d3.

📒 Files selected for processing (3)
  • src/presets/vercel/preset.ts
  • src/presets/vercel/runtime/telemetry-types.ts
  • src/presets/vercel/runtime/telemetry.ts

Comment on lines +157 to +161
context.waitUntil(async () => {
const batch = pendingSpans.get(traceId);
pendingSpans.delete(traceId);
if (batch && batch.length > 0) sink.reportSpans(envelope(batch));
});

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the waitUntil flush from reportSpans throws.

On Line 160, sink.reportSpans(...) is not protected. If it throws, the async waitUntil task rejects and telemetry failures can leak into request teardown behavior.

Proposed fix
     context.waitUntil(async () => {
       const batch = pendingSpans.get(traceId);
       pendingSpans.delete(traceId);
-      if (batch && batch.length > 0) sink.reportSpans(envelope(batch));
+      if (!batch || batch.length === 0) return;
+      try {
+        sink.reportSpans(envelope(batch));
+      } catch (error) {
+        console.warn("[vercel-telemetry] reportSpans failed", {
+          traceId,
+          spanCount: batch.length,
+          error,
+        });
+      }
     });

As per coding guidelines: "Prefer explicit errors over silent failures in error handling" and "Use warnings for recoverable situations; throw for invalid states".

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

Suggested change
context.waitUntil(async () => {
const batch = pendingSpans.get(traceId);
pendingSpans.delete(traceId);
if (batch && batch.length > 0) sink.reportSpans(envelope(batch));
});
context.waitUntil(async () => {
const batch = pendingSpans.get(traceId);
pendingSpans.delete(traceId);
if (!batch || batch.length === 0) return;
try {
sink.reportSpans(envelope(batch));
} catch (error) {
console.warn("[vercel-telemetry] reportSpans failed", {
traceId,
spanCount: batch.length,
error,
});
}
});
🤖 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.ts` around lines 157 - 161, The
`sink.reportSpans` call within the `context.waitUntil` async callback is not
protected from throwing errors, which can cause promise rejection and leak
telemetry failures into request teardown behavior. Wrap the
`sink.reportSpans(envelope(batch))` call in a try-catch block to handle any
potential errors gracefully, catching exceptions and logging them as warnings
rather than allowing the error to propagate and reject the waitUntil promise.

Source: Coding guidelines

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