Skip to content

feat(nextjs): Add opt-in for orchestrion instrumentation#22043

Open
chargome wants to merge 12 commits into
developfrom
cg/nextjs-orchestrion-support
Open

feat(nextjs): Add opt-in for orchestrion instrumentation#22043
chargome wants to merge 12 commits into
developfrom
cg/nextjs-orchestrion-support

Conversation

@chargome

@chargome chargome commented Jul 8, 2026

Copy link
Copy Markdown
Member

Adds an experimental option for opting in to orchestrion instrumentation.

  • Once the opt-in flag is set we:
    • Un-externalize the bundle-safe instrumented packages; bundle-unsafe ones (mysql) stay external and are instrumented by the runtime module hook, which works because we also externalize the @apm-js-collab/* transformer packages.
    • transpilePackages for the ones that Next externalizes by default (e.g. pg) — removing them from serverExternalPackages isn't enough for Next's own defaults.
    • Inject the code-transform loader as a Turbopack rule and as a webpack plugin.
  • The webpack loader gets exported from our server utils package (which can be used for turbopack too)

Verified in e2e

closes #22009

@chargome chargome self-assigned this Jul 8, 2026
chargome and others added 6 commits July 8, 2026 10:06
Two fixes for diagnostics-channel injection under Turbopack:

Externalize @apm-js-collab/tracing-hooks and @apm-js-collab/code-transformer
when the flag is on. Bundled, the code transformer's parser breaks
('a.parse is not a function'), so the runtime module hook silently returned
untransformed sources and externalized packages never produced spans.

Keep mysql in serverExternalPackages instead of force-bundling it. Turbopack
cannot bundle mysql 2.x correctly (the wire-protocol handshake fails with
'Received packet in the wrong sequence' even untransformed). External, it is
now instrumented by the working runtime hook instead of the build-time loader.

Also drop the bundler marker from the orchestrion webpack plugin: it made
registerDiagnosticsChannelInjection() skip the runtime hook, but the hybrid
setup (loader for bundled deps, runtime hook for external ones) needs both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Instead of un-externalizing every instrumented package and forcing the
Next-default-external ones through transpilePackages, only packages on an
explicit bundle-safe allowlist (currently ioredis) are removed from Sentry's
own serverExternalPackages defaults. Everything else — Next's defaults, the
user's externals, the rest of Sentry's defaults — stays external and is
instrumented by the runtime module hook on require, which works since the
orchestrion machinery is externalized.

This is safer: bundling a server package changes real behavior (mysql 2.x
corrupts its wire protocol when bundled by Turbopack), and new upstream
instrumentations (e.g. hapi) now default to the external/runtime-hook path
instead of silently becoming bundled. It also removes the Next
server-external-packages list parsing and the pg-native webpack workaround,
both only needed to support force-bundling.

Verified in the nextjs-16-orchestrion e2e: ioredis via the build-time loader,
pg and mysql via the runtime hook.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t@13

Use createRequire(__filename) instead of aliasing the CJS require in the
orchestrion webpack bundler module. Next.js 13 bundles @sentry/nextjs into
the server build, and webpack flags the aliased require with 'Critical
dependency: require function is used in a way in which dependencies cannot
be statically extracted', which the nextjs-app-dir e2e treats as a failure.

Add isDiagnosticsChannelInjectionEnabled to the consistent-exports ignore
list: like its companions experimentalUseDiagnosticsChannelInjection and
diagnosticsChannelInjectionIntegrations, the Node-runtime-only opt-in is not
surfaced through the framework / serverless SDKs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chargome

chargome commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

bugbot run

Comment thread packages/nextjs/src/config/webpack.ts
The plugin previously ran for all server compilations, including the edge
runtime, which diagnostics-channel injection does not target. Gate it on the
already-derived runtime instead of isServer and add unit tests covering the
server/edge/client/flag-off cases.

Also trim down the orchestrion-related comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chargome

chargome commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

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 a3a3595. Configure here.

Comment thread packages/nextjs/src/server/index.ts
expect(externals).toContain('mysql');
expect(externals).not.toContain('@apm-js-collab/tracing-hooks');
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Feat lacks integration or E2E

Medium Severity

This feature PR adds unit and webpack config tests but no integration or E2E test in the diff, though the description references separate e2e verification. Review guidelines expect at least one integration or E2E test for feat changes.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit a3a3595. Configure here.

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.

handled in #22080

// module don't emit a "Critical dependency" warning.
function getOrchestrionRequire(): ReturnType<typeof createRequire> {
let nodeRequire: ReturnType<typeof createRequire>;
/*! rollup-include-cjs-only */

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.

big thanks to @timfish for this :D

@chargome chargome marked this pull request as ready for review July 9, 2026 08:34
@chargome chargome requested review from a team as code owners July 9, 2026 08:34
@chargome chargome requested review from a team, JPeer264, andreiborza, logaretm, mydea, nicohrubec, s1gr1d and timfish and removed request for a team, JPeer264, andreiborza and s1gr1d July 9, 2026 08:34
Comment thread packages/nextjs/src/config/webpack.ts
When the SDK is bundled into a Next.js server build, the runtime module
hook's bare require of @apm-js-collab/tracing-hooks resolves relative to
the emitted chunk, which fails under isolated installs (pnpm) where the
package is not linked at the app root — the hook silently no-ops and
externalized packages (pg, mysql) lose their spans.

Resolve the package location in withSentryConfig, where the SDK is a
real on-disk package, and inline it as a build-time env value that the
runtime prefers over the bare specifier. Also construct nodeRequire via
createRequire in both build flavors so bundlers don't statically trace
the call (Turbopack otherwise tries to resolve the injected absolute
path at build time).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@timfish timfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I added a webpack plugin in this PR:

Why is the createRequire needed?

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 27.59 kB - -
@sentry/browser - with treeshaking flags 26.03 kB - -
@sentry/browser (incl. Tracing) 46.34 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 48.13 kB - -
@sentry/browser (incl. Tracing, Profiling) 51.12 kB - -
@sentry/browser (incl. Tracing, Replay) 85.62 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 75.26 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 90.32 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 102.97 kB - -
@sentry/browser (incl. Feedback) 44.76 kB - -
@sentry/browser (incl. sendFeedback) 32.38 kB - -
@sentry/browser (incl. FeedbackAsync) 37.51 kB - -
@sentry/browser (incl. Metrics) 28.67 kB - -
@sentry/browser (incl. Logs) 28.91 kB - -
@sentry/browser (incl. Metrics & Logs) 29.59 kB - -
@sentry/react 29.38 kB - -
@sentry/react (incl. Tracing) 48.61 kB - -
@sentry/vue 33.03 kB - -
@sentry/vue (incl. Tracing) 48.3 kB - -
@sentry/svelte 27.61 kB - -
CDN Bundle 30 kB - -
CDN Bundle (incl. Tracing) 48.32 kB - -
CDN Bundle (incl. Logs, Metrics) 31.57 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 49.64 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 70.81 kB - -
CDN Bundle (incl. Tracing, Replay) 85.84 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 87.14 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 91.64 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.92 kB - -
CDN Bundle - uncompressed 89.35 kB - -
CDN Bundle (incl. Tracing) - uncompressed 146.1 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 94.05 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 150.07 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 218.78 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 265.3 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 269.26 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 282.95 kB - -
@sentry/nextjs (client) 51.16 kB - -
@sentry/sveltekit (client) 46.78 kB - -
@sentry/core/server 78.42 kB - -
@sentry/core/browser 64.74 kB - -
@sentry/node-core 62.72 kB - -
@sentry/node 125.37 kB - -
@sentry/node (incl. diagnostics channel injection) 138.61 kB +0.05% +67 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 69.95 kB - -
@sentry/node/light 50.72 kB - -
@sentry/node - without tracing 74.06 kB - -
@sentry/aws-serverless 85.5 kB - -
@sentry/cloudflare (withSentry) - minified 181.71 kB - -
@sentry/cloudflare (withSentry) 449.16 kB - -

View base workflow run

Comment thread packages/server-utils/src/orchestrion/runtime/register.ts Outdated
@timfish

timfish commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

I guess the createRequire and then the require obfuscation are to stop webpack including all the orchestrion stuff in a bundle when that feature is not used?

Ideally we should avoid using config options to enable orchestrion mode because it requires all these hacks. I also think this will result in webpack not including the orchestrion code in a bundle even if you use it and result in a runtime error when it can't be loaded.

For the Node SDK we have Sentry.experimentalUseDiagnosticsChannelInjection() which ensures the code is only included in the bundle if you actually reference that.

The only valid use I've seen for createRequire is to ensure that tracing-hooks isn't directly in our import graph because it's ESM and this needs require(esm) to load it from CJS (ie. Node v20.19). The argument could be made that these new usages stop webpack from building a bundle that fails to run on Node v18 but I suspect webpack removes the require(esm) issue anyway.

@timfish timfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The getTracingHooksSpecifier obfuscation will mean that no other bundler will be able to resolve and bundle @apm-js-collab/tracing-hooks/hook-sync.mjs. This will force us (and users) to make it external everywhere.

I think in the long term we need to solve the Turbopack issue (happy to take a look!) but for now to get this merged I would create a nextjs specific registerDiagnosticsChannelInjection() so only nextjs has this obfuscation!

chargome and others added 3 commits July 9, 2026 14:30
registerDiagnosticsChannelInjection() now takes an optional tracingHooksDir
and by default loads @apm-js-collab/tracing-hooks via its bare specifier
again, keeping the require statically analyzable for bundlers. Only the
Next.js SDK — whose server builds bundle the SDK and can't resolve the bare
specifier under pnpm — passes the build-time-resolved package location
(via a Next.js-specific experimentalUseDiagnosticsChannelInjection wrapper),
where loading switches to an opaque createRequire.

webpack ignore-comments were evaluated as an alternative: turbopackIgnore
works on require(), but webpack only honors webpackIgnore on import() and
compiles the call to a broken module stub, so createRequire it is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chargome

chargome commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

@timfish

I reworked the pr so we have a nextjs specific registerDiagnosticsChannelInjection call which takes an optional tracingHooksDir that uses the createRequire path in the nextjs case. This enables us to not bundle in the transformer hook in turbopack and keep the hybrid buildtime/runtime approach as long as this breaks.

On magic comments: require(/* webpackIgnore: true */ /* turbopackIgnore: true */ specifier); did work for turbopack but not for webpack (apparently webpackIgnore only works for import). I'd rather have a solutions that works uniformly across bundlers in nextjs which is createRequire.

Once the transformer becomes bundle-safe, we can update the code and internalize all the modules for both bundlers.

@timfish timfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Great bundler wrangling!

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.

Support orchestrion auto-instrumentation in turbopack

3 participants