Skip to content

ref(nitro): Move channel-based instrumentation into @sentry/server-utils - #24861

Open
mydea wants to merge 10 commits into
developfrom
feat/nitro-move-to-server-utils
Open

mydea wants to merge 10 commits into
developfrom
feat/nitro-move-to-server-utils

Conversation

@mydea

@mydea mydea commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Moves Nitro's node:diagnostics_channel-based instrumentation out of @sentry/nitro and into @sentry/server-utils, so it is no longer tied to the Nitro package and can be provided by any server SDK. This mirrors the Hono → server-utils move.

The runtime instrumentation splits into two integrations:

  • nitroIntegration — h3/srvx HTTP + middleware spans and unstorage cache spans. It only creates spans, so it is a tracing integration, registered via getTracingIntegrations(). That makes it available (and on by default) in node, deno and bun.
  • nitroServerTimingIntegration — sets the Server-Timing response headers that carry sentry-trace/baggage so the browser SDK can connect a pageload to the backend trace. This must work even when tracing is disabled (trace-without-performance), so it is a default (non-tracing) integration, registered via getErrorIntegrations(). It is opt-out-able independently.

Both integrations subscribe to h3's tracing channels, so they are inert unless a Nitro app (the only real emitter of those channels) is running.

Cloudflare (and other bundler-only SDKs) get both integrations auto-injected via orchestrion registration-only configs: h3 and unstorage are transformed at build time to register the matching factory, which @sentry/cloudflare instantiates at init(). These are registration-only (no channels injected — the libraries publish their own), so they are excluded from the runtime loader and are a no-op on node/deno/bun, where the integrations are registered statically.

Non-breaking: @sentry/nitro's init() goes through @sentry/node's default integrations, which now include both of the above, so existing Nitro-on-Node apps get exactly the same spans and headers as before — just sourced from the integrations rather than the runtime plugin. @sentry/nitro no longer depends on @sentry/server-utils.

Why error capture stays in @sentry/nitro

Error capture is intentionally not moved. Nitro's error hook is a broad boundary (request/response-hook, plugin-init, cache and process-level unhandled* errors, plus contexts.nitro tags/method/path). The tracing channels only see errors thrown through a traced handler, so folding error capture into a channel-based integration would lose coverage and force an h3 coupling into the framework-neutral package. It remains in the Nitro runtime plugin's error hook.

E2E coverage

A new cloudflare-nitro e2e app (raw Nitro 3 on the cloudflare_module preset) exercises the Cloudflare auto-injection end to end — asserting auto.http.nitro.* spans, auto.cache.nitro cache spans, and the Server-Timing header, which also confirms the tracing channels bind under Cloudflare's async-context strategy. Since raw Nitro has no first-class Cloudflare init helper yet, the app wires init by hand (a Nitro server plugin wrapping nitroApp.fetch with wrapRequestHandler, mirroring @sentry/nuxt); a supported helper could be a good follow-up.

🤖 Generated with Claude Code

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

Stale Bugbot comment from a previous run.

Comment thread packages/server-utils/src/integrations/index.ts
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.36 kB - -
@sentry/browser - with treeshaking flags 27.65 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.54 kB - -
@sentry/browser (incl. Tracing) 51.3 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 51.32 kB - -
@sentry/browser (incl. Tracing, Profiling) 54.31 kB - -
@sentry/browser (incl. Tracing, Replay) 90.89 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.99 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 95.58 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 108.57 kB - -
@sentry/browser (incl. Feedback) 46.88 kB - -
@sentry/browser (incl. sendFeedback) 34.43 kB - -
@sentry/browser (incl. FeedbackAsync) 39.54 kB - -
@sentry/browser (incl. Metrics) 30.38 kB - -
@sentry/browser (incl. Logs) 30.66 kB - -
@sentry/browser (incl. Metrics & Logs) 31.33 kB - -
@sentry/react 31.2 kB - -
@sentry/react (incl. Tracing) 53.67 kB - -
@sentry/vue 36.9 kB - -
@sentry/vue (incl. Tracing) 53.88 kB - -
@sentry/svelte 29.39 kB - -
CDN Bundle 31.18 kB - -
CDN Bundle (incl. Tracing) 51.95 kB - -
CDN Bundle (incl. Logs, Metrics) 33.43 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 53.89 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 74.16 kB - -
CDN Bundle (incl. Tracing, Replay) 89.52 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 91.5 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 95.7 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 97.67 kB - -
CDN Bundle - uncompressed 92.05 kB - -
CDN Bundle (incl. Tracing) - uncompressed 154.44 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 98.62 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 160.39 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 228.19 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 274.17 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 280.1 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 287.87 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 293.8 kB - -
@sentry/nextjs (client) 55.91 kB - -
@sentry/sveltekit (client) 51.74 kB - -
@sentry/core/server 39.99 kB - -
@sentry/core/browser 13.63 kB - -
@sentry/node 145.98 kB +1.16% +1.67 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 83.04 kB +0.04% +28 B 🔺
@sentry/node - without tracing 93.24 kB +0.32% +292 B 🔺
@sentry/node - without channel injection 124.33 kB +1.38% +1.69 kB 🔺
@sentry/aws-serverless 101.53 kB +0.28% +281 B 🔺
@sentry/cloudflare (withSentry) - minified 206.69 kB - -
@sentry/cloudflare (withSentry) 514.13 kB - -

View base workflow run

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

Stale Bugbot comment from a previous run.

Comment thread packages/server-utils/src/orchestrion/config/nitro.ts
@mydea
mydea added this pull request to stack #24871 September 30, 2026 08:41
@mydea
mydea force-pushed the feat/nitro-move-to-server-utils branch 2 times, most recently from e8a310d to 810fa6c Compare September 30, 2026 12:26
@mydea
mydea marked this pull request as ready for review September 30, 2026 12:30
@mydea
mydea requested review from a team as code owners September 30, 2026 12:30
@mydea
mydea requested review from a team, JPeer264, isaacs, logaretm, nicohrubec and s1gr1d and removed request for a team September 30, 2026 12:30
{ exportName: 'redisIntegration', modules: ['redis', '@redis/client', 'ioredis'] },
{ exportName: 'dataloaderIntegration', modules: ['dataloader'] },
{ exportName: 'nitroIntegration', modules: ['h3'] },
{ exportName: 'nitroServerTimingIntegration', modules: ['unstorage'] },

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.

Bug: The nitroServerTimingIntegration may silently fail to register on Cloudflare if unstorage is tree-shaken from a minimal Nitro app, breaking trace propagation.
Severity: MEDIUM

Suggested Fix

To ensure robust registration, the module dependency for nitroServerTimingIntegration should be changed from ['unstorage'] to ['h3']. Since h3 is a guaranteed dependency in any Nitro application, this change ensures the integration is always registered when needed. The configuration should be updated to { exportName: 'nitroServerTimingIntegration', modules: ['h3'] }.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
packages/server-utils/src/orchestrion/config/channel-integration-definitions.ts#L64

Potential issue: The registration of `nitroServerTimingIntegration` for Cloudflare
Workers is configured to trigger upon detection of the `unstorage` module. This relies
on the assumption that `unstorage` is a core, non-tree-shakable dependency in all Nitro
applications. However, if a minimal Nitro application can be built without `unstorage`,
the integration will silently fail to register. This would result in the `sentry-trace`
and `baggage` headers not being set, breaking distributed tracing for affected
applications without any error or warning.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added a fix for this in a separate PR on this stack here: #24897

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d93c0f9. Configure here.

@logaretm logaretm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we merge both nitro integrations into one integration that enables either instrumentation with options?

nitroIntegration({
  // defaults, or flip em to `disabeXXX`
  serverTiming: true,
  storageInstrumentation: true,
   // other instrumentations
})

Extract Nitro's diagnostics-channel instrumentation out of `@sentry/nitro`
into `@sentry/server-utils`, so any server SDK can provide it:

- `nitroIntegration` (h3/srvx HTTP + middleware spans, unstorage cache spans)
  is a tracing integration, registered via `getTracingIntegrations()` and so
  available in node, deno and bun.
- `nitroServerTimingIntegration` sets the `Server-Timing` trace-propagation
  headers. It is a default (non-tracing) integration so it also runs in
  tracing-without-performance mode, and can be opted out separately.

Error capture stays in `@sentry/nitro`'s runtime plugin (its Nitro `error`
hook), which a channel-based integration cannot replace without losing
coverage. `@sentry/nitro` keeps getting all of this through the underlying
`@sentry/node` defaults, so nothing changes for existing Nitro-on-Node apps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mydea and others added 9 commits October 1, 2026 12:59
…orchestrion

Add registration-only orchestrion configs so the Nitro integrations are
auto-registered on bundler-only SDKs like `@sentry/cloudflare`, which discover
a bundled module via the module-injected event and instantiate its factory.

The snippet maps one integration per module, so two anchors are used, both core
Nitro dependencies evaluated at startup: `h3` (dist/h3.mjs) registers
`nitroIntegration` (spans) and `unstorage` (dist/index.mjs) registers
`nitroServerTimingIntegration`. These are registration-only (no channels are
injected, the libraries publish their own), so they are excluded from the
runtime loader and are a no-op on node/deno/bun, where the integrations are
registered statically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…trion config

Document why the h3/unstorage registration-only ranges use the `-0` suffix: the
orchestrion matcher is the vendored `semifies` (not node-semver), where a range
carrying a prerelease tag matches prereleases across patch tuples — so
`>=2.0.0-0` matches the `2.0.1-rc.*` / `2.0.0-alpha.*` versions Nitro 3 ships.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a raw Nitro 3 app deployed to Cloudflare (Workers, `cloudflare_module`
preset) that exercises the orchestrion registration-only path on a bundler-only
SDK: the app imports no Nitro instrumentation, and `nitroIntegration` /
`nitroServerTimingIntegration` are auto-injected via the orchestrion transform
over h3/unstorage and instantiated by `@sentry/cloudflare` at init.

Tests assert `auto.http.nitro.*` spans, `auto.cache.nitro` cache spans, and the
`Server-Timing` trace-propagation header — i.e. the whole auto-injection path,
including that the tracing channels bind under Cloudflare's async-context
strategy.

Init mirrors what @sentry/nuxt does for Nitro-on-Cloudflare (no first-class
raw-Nitro Cloudflare helper exists): a Nitro server plugin wraps `nitroApp.fetch`
with `wrapRequestHandler`, and the orchestrion rollup/rolldown plugin is wired
into the Nitro build config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `mod.test.ts` snapshots captured the full `sdk.integrations` list, so any
change to the default integrations (such as the new nitro integrations) broke
them even though the list is incidental to what these tests verify. Normalize
`sdk.integrations` to a placeholder, like the other volatile fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stacked on develop's hono size bump: the nitro integrations add on top, so
@sentry/node needs 146 KB and the no-channel-injection variant 125 KB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`nitroIntegration` is part of the Node tracing integrations, but the Nitro SDK
should always include it regardless of that gating. Add it explicitly to the
Nitro SDK's default integrations (deduped by name when both are added), and
re-export `nitroIntegration`/`nitroServerTimingIntegration` from `@sentry/node`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nitroIntegration and nitroServerTimingIntegration are exported from
@sentry/node, so every SDK with an explicit re-export list needs them too
(caught by the node-exports-test-app consistency check). Deno and Bun also
enable them by default via getTracingIntegrations().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mydea
mydea force-pushed the feat/nitro-move-to-server-utils branch from d93c0f9 to a6ecc88 Compare October 1, 2026 10:59

@JPeer264 JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. The merge conflict might be the release we've just done


const INTEGRATION_NAME = 'Nitro';

const _nitroIntegration = (() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: Would be nice if we could add a node integration test for this somehow that will run it for bun and deno. Then we would have proof that it would run on these runtimes. Wdyt?

import { captureTracingEvents } from './captureTracingEvents';
import { captureServerTimingHeaders } from './setServerTimingHeaders';

const INTEGRATION_NAME = 'Nitro';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l:

Suggested change
const INTEGRATION_NAME = 'Nitro';
const INTEGRATION_NAME = 'Nitro' as const;

*/
export const nitroIntegration = defineIntegration(_nitroIntegration);

const SERVER_TIMING_INTEGRATION_NAME = 'NitroServerTiming';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l:

Suggested change
const SERVER_TIMING_INTEGRATION_NAME = 'NitroServerTiming';
const SERVER_TIMING_INTEGRATION_NAME = 'NitroServerTiming' as const;

@JPeer264

JPeer264 commented Oct 1, 2026

Copy link
Copy Markdown
Member

Should we merge both nitro integrations into one integration that enables either instrumentation with options?

Currently they are added independently as tracing and error integration within Nitro


Btw I think the PR description is stale, there are no e2e tests in here anymore

@logaretm

logaretm commented Oct 1, 2026

Copy link
Copy Markdown
Member

Currently they are added independently as tracing and error integration within Nitro

Yea, that was never the intention of them I think. This happened as a side effect of other changes. I think rarely anyone will use one without the other.

This branch has not been deployed

No deployments
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.

3 participants