feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(node): Experimentally add orchestrion support - #20900

Merged
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation
Jun 18, 2026
Merged

feat(node): Experimentally add orchestrion support#20900
isaacs merged 3 commits into
developfrom
experiment/orchestrionjs-auto-instrumentation

Conversation

@mydea

@mydeamydea commented May 15, 2026

Copy link
Copy Markdown
Member

EDIT(@isaacs): updated description to reflect where this PR landed, for more straightforward review.

Use orchestrion for instrumentation, providing the shared implementation in packages/server-utils, and the Node integration. (Deno and Bun in subsequent PRs.)

This implementation avoids exposing the "orchestrion" terminology to any user-facing API surface. Instead, the Node SDK presents a minimal API surface, with an opt-in that triggers the orchestrion instrumentations to replace their OTel counterparts. (Currently, this is just Mysql, but more will be added in the future.)

A single opt-in method now does the entire Orchestrion setup.

Sentry.experimentalUsediagnosticsChannelInjection();Sentry.init({dsn: '__DSN__',experimentalDiagnosticsChannelInjection: true,})

Assuming that this is run in a user's local --import module (or, failing that, before the instrumented modules are loaded), then the appropriate hooks will be synchronously added, and Orchestrion diagnostics_channel-based integrations will be used instead of the legacy OTel integrations.

The channel-injection hooks are registered synchronously (mirroring esmLoader.ts: Module.register(...) + ModulePatch on Node <24.13, Module.registerHooks on newer / Deno 2.8+), so they are in place before the app's own imports resolve.

The @sentry/node/import loader hook script injects the channels unconditionally, as the presence of the channels is harmless when they are not being used. They are only subscribed to if the opt-in flag is set. (If tracing is disabled, then they're not enabled at all, since the integrations would not be in use. When and if we have an Orchestrion-based integration that is not spans-only, we can make this check more elaborate, but it seems like overkill for now.)

The version detection lives in a single source of truth in server-utils/orchestrion/runtime/register.ts, so that it can be initialized synchronously as part of Sentry.init(), or in the import-hook.mjs loader, and enables the appropriate module hooks for Deno 2.8.0+ and supported Node versions.

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@github-actions

github-actionsBot commented May 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

PathSize% ChangeChange
@sentry/browser27.45 kB--
@sentry/browser - with treeshaking flags25.88 kB--
@sentry/browser (incl. Tracing)45.89 kB--
@sentry/browser (incl. Tracing + Span Streaming)48.12 kB--
@sentry/browser (incl. Tracing, Profiling)50.67 kB--
@sentry/browser (incl. Tracing, Replay)85.08 kB--
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags74.69 kB--
@sentry/browser (incl. Tracing, Replay with Canvas)89.78 kB--
@sentry/browser (incl. Tracing, Replay, Feedback)102.45 kB--
@sentry/browser (incl. Feedback)44.62 kB--
@sentry/browser (incl. sendFeedback)32.25 kB--
@sentry/browser (incl. FeedbackAsync)37.38 kB--
@sentry/browser (incl. Metrics)28.52 kB--
@sentry/browser (incl. Logs)28.76 kB--
@sentry/browser (incl. Metrics & Logs)29.45 kB--
@sentry/react29.25 kB--
@sentry/react (incl. Tracing)48.18 kB--
@sentry/vue32.56 kB--
@sentry/vue (incl. Tracing)47.76 kB--
@sentry/svelte27.48 kB--
CDN Bundle29.86 kB--
CDN Bundle (incl. Tracing)48.29 kB--
CDN Bundle (incl. Logs, Metrics)31.4 kB--
CDN Bundle (incl. Tracing, Logs, Metrics)49.59 kB--
CDN Bundle (incl. Replay, Logs, Metrics)70.71 kB--
CDN Bundle (incl. Tracing, Replay)85.62 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)86.88 kB--
CDN Bundle (incl. Tracing, Replay, Feedback)91.46 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)92.71 kB--
CDN Bundle - uncompressed88.8 kB--
CDN Bundle (incl. Tracing) - uncompressed146.08 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed93.5 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.06 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.33 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.95 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.91 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.65 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.6 kB--
@sentry/nextjs (client)50.58 kB--
@sentry/sveltekit (client)46.27 kB--
@sentry/core/server76.16 kB--
@sentry/core/browser63.31 kB--
@sentry/node-core61.87 kB-0.01%-1 B 🔽
@sentry/node127.71 kB+0.02%+21 B 🔺
@sentry/node - without tracing74.23 kB-0.05%-30 B 🔽
@sentry/aws-serverless85.34 kB-0.01%-2 B 🔽
@sentry/cloudflare (withSentry) - minified174.48 kB--
@sentry/cloudflare (withSentry)436.52 kB--
@sentry/node/import (ESM hook with diagnostics-channel injection)70.05 kBaddedadded
@sentry/node/light50.92 kBaddedadded

View base workflow run

@mydea

mydea commented May 15, 2026

Copy link
Copy Markdown
MemberAuthor

Note: dependency warning stuff should be addressed when this is merged/released: apm-js-collab/code-transformer-bundler-plugins#2

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from b1b6ed6 to 9e8b070CompareMay 18, 2026 07:29
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 9e8b070 to 26ccdf4CompareMay 18, 2026 09:03
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 26ccdf4 to c8be420CompareMay 18, 2026 13:34
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a38481b to c095626CompareMay 19, 2026 07:20
Comment threadpackages/node/src/orchestrion/setup.ts Outdated
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threadpackages/server-utils/src/orchestrion/detect.ts
Comment threadpackages/node/src/orchestrion/bundler/vite.ts
Comment threadpackages/node/src/orchestrion/runtime/require-hook.cjs Outdated
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 5d957bf to 12ac21cCompareMay 21, 2026 11:02
@mydea

Copy link
Copy Markdown
MemberAuthor

I also added an e2e test app using the vite plugin, however this is failing, will need to wait on v0.2.0 of the plugins as this updates to the latest version of the code transformer, which we need here.

Comment threadpackages/server-utils/src/orchestrion/detect.ts
@mydea
mydeaforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7d6f1f7 to 3de78daCompareMay 26, 2026 09:40
Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
Comment threaddev-packages/e2e-tests/test-applications/node-express-orchestrion/src/app.mjs Dismissed
Comment threadpackages/node/src/sdk/index.ts

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

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

Comment threadpackages/node/src/orchestrion/runtime/import-hook.mjs Outdated
@mydea

Copy link
Copy Markdown
MemberAuthor

This is looking very good!

My only concern/suggestion is that it should really not be node-specific. Deno and Bun are both supported by orchestrion now, so it'd be good to move this logic into a shared location so they can all benefit from it as we add instrumentations.

I'm not sure if it's best to (a) land this first, and then refactor this and the other @sentry/core/server stuff into @sentry-internal/server-utils, or (b) if we should bite that bullet first and then move this implementation on top of it. If we go with (a), then the move would be to land this now, and do server-utils as a second step. If (b), then it'd be good to get that done, and port this on top of it.

jup, eventually this should go into a new server-utils package or similar I guess - we can move this up any time!

Comment threadpackages/node/src/sdk/index.ts Outdated
Comment threadpackages/server-utils/tsconfig.types.json Outdated
Comment threadyarn.lock Outdated
dependencies:
json-schema-to-ts "^3.1.1"

"@apm-js-collab/code-transformer-bundler-plugins@^0.3.0":

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

should we bump to 0.4.0 right away, while we're at it?

Comment threadyarn.lock Outdated
Comment threadyarn.lock Outdated
@mydeamydea changed the title WIP: POC to use orchestrion-js for instrumentationfeat(node): Experimentally add orchestrion supportJun 18, 2026
@mydea

Copy link
Copy Markdown
MemberAuthor

Can't submit a review because it is my PR lol xD but this is good to go from my POV, left some small nits but nothing important, maybe the only thing would be to bump the orchestrion dependencies to latest while we're at it.

// esbuild and friends rewrite `import.meta.url` to `{}` for CJS output,
// which would make `createRequire(undefined)` throw.
// Only use `import.meta.url` in true ESM, where there's no `require`
const nodeRequire = typeof require === 'function' ? require : createRequire(import.meta.url);

@timfishtimfishJun 18, 2026

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.

Unrelated specifically to this PR but I think we should move towards a bundler plugin with magic comments like we have here for dev/prod. This way we can write "pure" ESM/CJS and have the bundler keep the correct one.

Something like:

/*! rollup-include-cjs-only */constnodeRequire=require;/*! rollup-include-csj-only-end *//*! rollup-include-esm-only */constnodeRequire=createRequire(import.meta.url);/*! rollup-include-esm-only-end */

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

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

mydeaand others added 2 commits June 18, 2026 07:17
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
Also, fix the `import ... from 'node:module'` in the import-hook.mjs,
which was broken.
isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 7323f1a to 92b508dCompareJune 18, 2026 14:20
@isaacs

Copy link
Copy Markdown
Member

Happy for this to be merged but I don't think we should be using orchestrion in the named exports. Something less... internal detail not relevent to customers?

I think we only do that for server-utils and other "implementation detail" type things. Is there a piece that I missed? I assume that most users will just use the Node/Bun/Deno SDK surfaces, which only refer to this as diagnostics channel injection and such.

isaacs added a commit that referenced this pull request Jun 18, 2026
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from 92b508d to a1fba56CompareJune 18, 2026 15:56
Finish the the orchestrion.js-based auto-instrumentation so the Node SDK
presents a minimal API that does not expose the internal "orchestrion"
term.
A single opt-in method now does the entire Orchestrion setup.
```ts
Sentry.experimentalUseDiagnosticsChannelInjection();
// orchestrion integrations loaded and ready to go
Sentry.init({
dsn: '__DSN__'
tracesSampleRate: 1,
})
```
Assuming that this is run in a user's local `--import` module (or,
failing that, before init is called and instrumented modules are
loaded), then the appropriate hooks will be synchronously added, and
Orchestrion diagnostics_channel-based integrations will be used instead
of the legacy OTel integrations.
The previous three-step setup (`_experimentalUseOrchestrion` flag,
`--import @sentry/node/orchestrion`, plus a separate
`_experimentalSetupOrchestrion()` method call) is removed.
The channel-injection hooks are registered synchronously (mirroring
`esmLoader.ts`: `Module.register(...)` + `ModulePatch` on Node <24.13,
`Module.registerHooks` on newer / Deno 2.8+), so they are in place
before the app's own `import`s resolve. (A dynamic `import()` would have
raced module loading.)
The `@sentry/node/import` loader hook script injects the channels
**unconditionally**, as the presence of the channels is harmless when
they are not being used. They are only *subscribed* to if the opt-in
flag is set.
The version detection is moved to a single source of truth in
`server-utils/orchestrion/runtime/register.ts`, so that it can be
initialized synchronously as part of `Sentry.init()`, or in the
`import-hook.mjs` loader.
All references to `orchestrion` are removed from the public API surface.
The term remains in `server-utils`, of course, as this is an internal
package designed to house shared implementation details across
server-side JS platforms.
The `@sentry/node/orchestrion/vite` subpath export is removed, as that
really isn't used by anything, and was just a pass-through for
`@sentry/server-utils/orchestrion/vite` anyway. The Bun and Edge
computing SDKs will use this directly to instrument using Orchestrion.
Also, only install diagnostics channel injection when tracing enabled.
Every Orchestrion integration we're currently adding (ie, Mysql) or
likely to in the very near future, replaces an OTel integration that is
gated on span creation. So, there's no need to do this work if spans are
not enabled.
An e2e test is added to ensure that the orchestrion functionality is
*not* added in bundlers that do not explicitly opt into it.
@isaacs
isaacsforce-pushed the experiment/orchestrionjs-auto-instrumentation branch from a1fba56 to ce95b7aCompareJune 18, 2026 16:25
@isaacs
isaacs merged commit 9518574 into developJun 18, 2026
527 of 540 checks passed
@isaacs
isaacs deleted the experiment/orchestrionjs-auto-instrumentation branch June 18, 2026 17:38
isaacs pushed a commit that referenced this pull request Jun 18, 2026
This is a WIP POC trying out usage of orchestrion-js for node SDK
instrumentation.
1. Built a general plan document outlining how this can/should work
2. Implemented the generic utilities and building blocks needed
3. Implemented a example integration for mysql package using the new
pieces
Honestly it seems pretty straightforward... Usage for this POC is:
```bash
node --import @sentry/node/orchestrion app.mjs
```
And then
```js
// app.mjs
import * as Sentry from '@sentry/node';
const client = Sentry.init({
// regular setup...
_experimentalUseOrchestrion: true,
});
// Split this way for better tree shaking
Sentry._experimentalSetupOrchestrion(client);
```
This will disable the otel instrumentation that is already converted to
orchestrion (in this PR, only Mysql) and add the respective
orchestrion-based integrations instead. The exact API here is WIP and
really just geared towards experimentation, so could change, and it's
easy to see how this would be easier in v11 with this being the default.
Some general benefits of this approach:
1. preload becomes unnecessary as this approach generally behaves like
preload - the `--import` script only registers the mappings for
orchestrion, all actual code registering stuff etc. happens in
`Sentry.init()`. This makes a bunch of things easier...
2. Not tested here, but this should generally work exactly the same if
you add the respective vite (and others in the future) plugin,
allowing you to skip the `--import`. This also works when deploying
to e.g. cloudflare etc. as long as one of the bundler plugins is
used.
3. The whole approach is much easier to reconcile with dual-system
approaches where newer versions have native DC/TC support - just need
to register different channel names mostly to get stuff working.
nicohrubec added a commit that referenced this pull request Jul 21, 2026
…22141)
Rewrites the `Firebase` integration off `InstrumentationBase` to an
orchestrion diagnostics-channel listener in `@sentry/server-utils`,
following the mysql
([#20900](<#20900>))
and graphql (#21885) migrations. The
channel-based integration reuses the OTel `Firebase` name, so opting in
via `experimentalUseDiagnosticsChannelInjection()` swaps it in 1:1 for
the OTel one (which stays the default).
**Firestore** — subscribes to
`orchestrion:@firebase/firestore:{add,get,set,delete}-doc` (injected
into firestore's lite `addDoc`/`getDocs`/`setDoc`/`deleteDoc`) and emits
the same `db.query` client spans via `bindTracingChannelToSpan`, with a
distinct `auto.firebase.orchestrion.firestore` origin.
**firebase-functions** — the `onX(...)` provider functions *register* a
handler and return synchronously, so the span can't be bound to the
registration call. Instead the channel `start` subscriber rewraps the
handler argument (orchestrion forwards the mutated args array to the
real call), so the returned cloud function opens a `SERVER` span + error
boundary on invocation, with `auto.firebase.orchestrion.functions`
origin.
The `node-firebase` e2e firestore suite additionally runs with
`E2E_ORCHESTRION=true` to assert the orchestrion origin.
Closes#20919
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
Sign up for freeto 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.

4 participants

@mydea@isaacs@timfish@github-advanced-security