Skip to content

Surface missing or late diagnostics-channel injection instead of silently dropping spans #25677

Open

Description

@mydea

Library instrumentation (DB, framework, queue, AI) depends on diagnostics channels being injected, either at runtime (--import / hooks registered in init(), Deno --preload) or at build time (bundler plugins, @sentry/bun/plugin, @sentry/cloudflare/vite). When injection does not happen, or happens too late, these libraries produce no spans. HTTP spans still work, so a setup that is only partly instrumented looks healthy to users, and in several cases nothing tells them otherwise unless debug: true is set.

Cases that are currently silent

  • Node: a library is imported before Sentry.init(). NodeClient registers the hooks in init() (packages/node/src/sdk/client.ts), so any instrumented library loaded earlier stays uninstrumented. This happens when users skip --import, import a driver in the same file as init, or use a framework that loads drivers early (e.g. Nuxt dev server loads DB drivers before Sentry.init() #23876). Nothing checks for it today.
  • Bun without @sentry/bun/plugin. Runtime hook registration is skipped with only a debug.warn (packages/server-runtime-injection/src/register.ts), and detectOrchestrionSetup() also only uses debug.warn.
  • Deno without --preload=npm:@sentry/deno/import. Injection only happens in the preload. Deno's init does not call detectOrchestrionSetup() at all.
  • Cloudflare without the Vite plugin. The channel integrations are never registered, and no warning is printed.

On Node, registration failures and a bundled @sentry/server-runtime-injection already warn without debug, so these are not covered here.

Proposal

  1. Warn by default when nothing is injected, on every runtime. If spans are enabled and neither runtime nor bundler injection ran, detectOrchestrionSetup() should warn once through console.warn (with consoleSandbox) instead of debug.warn. Call it from the Bun, Deno and Cloudflare SDKs too, each with its own fix hint (Bun plugin, Deno --preload, Cloudflare Vite plugin, Node --import). This condition cannot be a false positive: tracing is on and nothing can produce library spans.

  2. Node: warn about instrumented libraries that loaded before init(). When the hooks are registered, scan require.cache for paths under node_modules/<name>/ for every instrumented package name (from the orchestrion configs, see getModuleNames). Any match was loaded too early and will not be traced. Warn once and name the libraries, e.g. "pg, ioredis were imported before Sentry.init() and will not be traced; use --import …". CJS packages imported from ESM also end up in require.cache, so this works for ESM apps as well, and most affected packages (pg, mysql2, ioredis, mongodb, express, …) are CJS. ESM-only packages are not detectable this way, and that gap is accepted. The cost is one pass over the cache keys at init.

  3. Report the injection state in SDK metadata. Attach a general indicator of how the process was instrumented (e.g. runtime / bundler / both / none) to the SDK metadata on outgoing envelopes. That lets us measure how common broken or partial setups are, and lets the product surface a hint later. Reporting which individual libraries were instrumented is out of scope here and will be handled separately.

Activity

  1. linear-code commented on Oct 23, 2026

    @linear-code
  2. isaacs commented on Oct 23, 2026

    @isaacs
    Member

    One tiny correction: as of #25546, Deno does register on init(), just like Node. But of course, that doesn't change the overall point, just means that Deno needs effectively the same detection as Node. Ie, we can't shortcut Deno by warning whenever --preload is missing, since init() might be doing it.

  3. isaacs commented on Oct 23, 2026

    @isaacs
    Member

    Digging a bit deeper into this:

    Avoiding a class of user footguns is appealing. But there's some obstacles to doing all three parts of this as described.

    Warn by default when nothing is injected, on every runtime.

    On Node and Deno, a successful registration in the client constructor sets marker.runtime = [] before detection runs. The "nothing injected" branch is then only reached in three cases:

    1. Registration failed. That already warns without debug.
    2. There is no hook API. That only warns with debug, but also, all supported versions of node and deno have either Module.registerHooks or Module.register, so there's really no gap there to worry about.
    3. The user set enableRuntimeChannelInjection: false and has no bundler. They asked for that, so a default warning would be noise.

    The other case for Node and Deno where nothing gets injected is if there's no preload, and modules get loaded before calling Sentry.init(), discussed below, because that's Item 2.

    That leaves Bun and Cloudflare as the cases this item really fixes, and "cannot be a false positive" is not true:

    • An app with tracing on may use no orchestrion-instrumented libraries at all, for example only Bun.serve or plain Workers fetch handlers. So they're getting spans, but from our pov, nothing was injected and no module was instrumented.
    • Users can bring their own OTel instrumentations.
    • Such apps would get a warning on every start, or on every isolate start on Cloudflare, which spams the logs.
    • A Cloudflare user who builds with wrangler/esbuild instead of Vite has no plugin to install. So, the warning gives them no fix. (Officially we only support Cloudflare with Vite, I believe, at least for tracing, so this might not be too relevant.)

    Cloudflare detection has to look at __SENTRY_ORCHESTRION__.integrations (packages/cloudflare/src/baseSdk.ts line 38), not only at runtime/bundler. The Vite plugin writes there, and detectOrchestrionSetup() does not read it.

    scan require.cache for libraries loaded before init()

    require.cache doesn't work on ESM, and there's no corollary to require.cache for ESM modules. So we'd have to decide that it's worth detecting this sort of shortcoming, but only in CommonJS modules, which are likely to continue to drop in popularity.

    • Matching node_modules/<name>/ is too coarse. The configs target specific files: module.filePath, e.g. mysql → lib/Connection.js. If a package has loaded but its target file has not, that file still gets instrumented when it loads later. Packages that load sub-files lazily (AWS SDK, Prisma, AI SDKs) would warn wrongly. So we'd have to match node_modules/<name>/<filePath> instead, and normalize Windows separators.
    • The scan must run only when registration actually happens inside the constructor. If --import already registered the hooks, modules in the cache were already transformed. The early return in registerDiagnosticsChannelInjection handles this as long as the scan lives after that guard.
    • A loaded version outside versionRange would never be traced anyway, so "use --import" is the wrong fix for it. We'll have to either check the version, or word the message more softly.

    Imo, being unable to detect this failure for ESM pretty dramatically reduces the value of this scan.

    report the injection state in SDK metadata

    This is reasonable, and imo a good idea. We need to decide the field name.

    • The envelope header sdk carries only name and version.
    • The event sdk has features (a string array) and settings, which Relay keeps.
    • sdk.features: ['orchestrion:runtime'] or similar would avoid schema work.
    • Under span streaming, check which payload actually carries the event sdk.
    • The bundler Set is empty at boot and fills as modules load. "bundler" therefore means "the plugin ran", not "the plugin instrumented something". That matches the proposed runtime/bundler/both/none values, but the data has to be read that way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

BugjavascriptPull requests that update javascript code

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions