From 0a383efb8b6f519570502a3e4b1fc0dd275b5169 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 12:19:52 +0200 Subject: [PATCH 1/7] feat(node)!: Remove import-in-the-middle ESM loader and deprecated instrumentation base With all vendored OpenTelemetry instrumentations removed, `@sentry/node` no longer has any import-in-the-middle module patchers. This removes the remaining iitm plumbing: - Remove the `registerEsmLoaderHooks` option and drop the `import-in-the-middle` dependency; the SDK no longer registers ESM loader hooks. - Delete the shared `InstrumentationNodeModuleFile` helper and the ESM loader. - Remove the deprecated `SentryHttpInstrumentation` and `SentryNodeFetchInstrumentation` classes (the diagnostics-channel-based `instrumentHttpOutgoingRequests` / `instrumentUndici` functions are kept). Closes: #22346 Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 4 + .../suites/esm/import-in-the-middle/app.mjs | 22 -- .../esm/import-in-the-middle/sub-module.mjs | 2 - .../suites/esm/import-in-the-middle/test.ts | 15 -- packages/node/package.json | 3 +- .../http/SentryHttpInstrumentation.ts | 32 --- .../node/src/integrations/http/constants.ts | 1 - .../SentryNodeFetchInstrumentation.ts | 188 ------------------ .../tracing/InstrumentationNodeModuleFile.ts | 45 ----- packages/node/src/sdk/esmLoader.ts | 30 --- packages/node/src/sdk/index.ts | 5 - packages/node/src/sdk/initOtel.ts | 3 - packages/node/src/types.ts | 10 - packages/node/src/utils/detection.ts | 16 -- .../instrumentationNodeModuleFile.test.ts | 39 ---- 15 files changed, 5 insertions(+), 410 deletions(-) delete mode 100644 dev-packages/node-integration-tests/suites/esm/import-in-the-middle/app.mjs delete mode 100644 dev-packages/node-integration-tests/suites/esm/import-in-the-middle/sub-module.mjs delete mode 100644 dev-packages/node-integration-tests/suites/esm/import-in-the-middle/test.ts delete mode 100644 packages/node/src/integrations/http/constants.ts delete mode 100644 packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts delete mode 100644 packages/node/src/integrations/tracing/InstrumentationNodeModuleFile.ts delete mode 100644 packages/node/src/sdk/esmLoader.ts delete mode 100644 packages/node/src/utils/detection.ts delete mode 100644 packages/node/test/integrations/tracing/instrumentationNodeModuleFile.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 86436b9158be..c40e6fb165b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ Work in this release was contributed by @psh4607, @trinitiwowka, @nehaprasad-dev - `DenoMongoose` => `Mongoose` - `DenoMysql` => `Mysql` - `DenoPostgres` => `Postgres` +- feat(node)!: Remove `import-in-the-middle`-based instrumentations from `@sentry/node`. All performance integrations are now channel-based via `@sentry/server-utils`. As part of this: + - The `registerEsmLoaderHooks` option was removed from `NodeOptions`. The SDK no longer registers `import-in-the-middle` ESM loader hooks, and `import-in-the-middle` is no longer a dependency. + - `knexIntegration` and `dataloaderIntegration` are now channel-only and no longer fall back to OpenTelemetry instrumentation on runtimes without diagnostics-channel injection. + - Support for Fastify `<3.21.0` was dropped. ## 10.67.0 diff --git a/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/app.mjs b/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/app.mjs deleted file mode 100644 index 3f58aba41357..000000000000 --- a/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/app.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; -import * as iitm from 'import-in-the-middle'; - -new iitm.Hook((_, name) => { - if (name !== 'http') { - throw new Error(`'http' should be the only hooked modules but we just hooked '${name}'`); - } -}); - -Sentry.init({ - traceLifecycle: 'static', - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - transport: loggingTransport, -}); - -(async () => { - await import('./sub-module.mjs'); - await import('http'); - await import('os'); -})(); diff --git a/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/sub-module.mjs b/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/sub-module.mjs deleted file mode 100644 index 9940c57857eb..000000000000 --- a/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/sub-module.mjs +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-console -console.assert(true); diff --git a/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/test.ts b/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/test.ts deleted file mode 100644 index 99dea0e9193a..000000000000 --- a/dev-packages/node-integration-tests/suites/esm/import-in-the-middle/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { spawnSync } from 'child_process'; -import { join } from 'path'; -import { afterAll, describe, expect, test } from 'vitest'; -import { cleanupChildProcesses } from '../../../utils/runner'; - -afterAll(() => { - cleanupChildProcesses(); -}); - -describe('import-in-the-middle', () => { - test('should only instrument modules that we have instrumentation for', () => { - const result = spawnSync('node', [join(__dirname, 'app.mjs')], { encoding: 'utf-8' }); - expect(result.stderr).not.toMatch('should be the only hooked modules but we just hooked'); - }); -}); diff --git a/packages/node/package.json b/packages/node/package.json index 62f389db2b8d..cfc950d8669a 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -105,8 +105,7 @@ "@sentry/core": "10.67.0", "@sentry/opentelemetry": "10.67.0", "@sentry/server-utils": "10.67.0", - "@sentry/bundler-plugins": "10.67.0", - "import-in-the-middle": "^3.0.0" + "@sentry/bundler-plugins": "10.67.0" }, "devDependencies": { "@types/node": "^18.19.1" diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index e909b40bc944..f4b482b2f5c2 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -2,17 +2,14 @@ import type { ChannelListener } from 'node:diagnostics_channel'; import { subscribe, unsubscribe } from 'node:diagnostics_channel'; import { context, trace } from '@opentelemetry/api'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; -import { InstrumentationBase } from '@opentelemetry/instrumentation'; import type { ClientRequest, IncomingMessage, ServerResponse } from 'node:http'; import type { HttpClientRequest, HttpIncomingMessage, HttpInstrumentationOptions, Span } from '@sentry/core'; import { getHttpClientSubscriptions, patchHttpModuleClient, - SDK_VERSION, getRequestOptions, isTracingSuppressed, } from '@sentry/core'; -import { INSTRUMENTATION_NAME } from './constants'; import { HTTP_ON_CLIENT_REQUEST } from '@sentry/core'; import { NODE_VERSION } from '../../nodeVersion'; import { errorMonitor } from 'node:events'; @@ -224,32 +221,3 @@ function instrumentHttpOutgoingRequestsViaMonkeyPatching(options: HttpInstrument // Patching http also patches https, as this uses the same underlying object patchHttpModuleClient(http, options); } - -/** - * This custom HTTP instrumentation handles outgoing HTTP requests. - * - * It provides: - * - Breadcrumbs for all outgoing requests - * - Trace propagation headers (when enabled) - * - Span creation for outgoing requests (when createSpansForOutgoingRequests is enabled) - * - * Span creation requires Node 22+ and uses diagnostic channels to avoid monkey-patching. - * By default, this is only enabled in the node SDK, not in other runtime SDKs that reuse this instrumentation. - * - * Important note: Contrary to other OTEL instrumentation, this one cannot be unwrapped. - * - * This is heavily inspired & adapted from: - * https://github.com/open-telemetry/opentelemetry-js/blob/f8ab5592ddea5cba0a3b33bf8d74f27872c0367f/experimental/packages/opentelemetry-instrumentation-http/src/http.ts - * - * @deprecated This will be removed in v11. Use instrumentHttpOutgoingRequests() instead. - */ -export class SentryHttpInstrumentation extends InstrumentationBase { - public constructor(config: SentryHttpInstrumentationOptions = {}) { - super(INSTRUMENTATION_NAME, SDK_VERSION, config); - } - - /** @inheritdoc */ - public init(): void { - instrumentHttpOutgoingRequests(this.getConfig()); - } -} diff --git a/packages/node/src/integrations/http/constants.ts b/packages/node/src/integrations/http/constants.ts deleted file mode 100644 index 35073ae0491d..000000000000 --- a/packages/node/src/integrations/http/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const INSTRUMENTATION_NAME = '@sentry/instrumentation-http'; diff --git a/packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts b/packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts deleted file mode 100644 index ffdd56bb2f8c..000000000000 --- a/packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts +++ /dev/null @@ -1,188 +0,0 @@ -import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; -import { InstrumentationBase } from '@opentelemetry/instrumentation'; -import { LRUMap, SDK_VERSION, isTracingSuppressed } from '@sentry/core'; -import * as diagch from 'diagnostics_channel'; -import { - addFetchRequestBreadcrumb, - addTracePropagationHeadersToFetchRequest, - getAbsoluteUrl, -} from '../../utils/outgoingFetchRequest'; -import type { UndiciRequest, UndiciResponse } from './types'; - -export type SentryNodeFetchInstrumentationOptions = InstrumentationConfig & { - /** - * Whether breadcrumbs should be recorded for requests. - * - * @default `true` - */ - breadcrumbs?: boolean; - - /** - * Whether to inject trace propagation headers (sentry-trace, baggage, traceparent) into outgoing fetch requests. - * - * When set to `false`, Sentry will not inject any trace propagation headers, but will still create breadcrumbs - * (if `breadcrumbs` is enabled). This is useful when `skipOpenTelemetrySetup: true` is configured and you want - * to avoid duplicate trace headers being injected by both Sentry and OpenTelemetry's UndiciInstrumentation. - * - * @default `true` - */ - tracePropagation?: boolean; - - /** - * Do not capture breadcrumbs or inject headers for outgoing fetch requests to URLs where the given callback returns `true`. - * The same option can be passed to the top-level httpIntegration where it controls both, breadcrumb and - * span creation. - * - * @param url Contains the entire URL, including query string (if any), protocol, host, etc. of the outgoing request. - */ - ignoreOutgoingRequests?: (url: string) => boolean; -}; - -interface ListenerRecord { - name: string; - unsubscribe: () => void; -} - -/** - * This custom node-fetch instrumentation is used to instrument outgoing fetch requests. - * It does not emit any spans. - * - * The reason this is isolated from the OpenTelemetry instrumentation is that users may overwrite this, - * which would lead to Sentry not working as expected. - * - * This is heavily inspired & adapted from: - * https://github.com/open-telemetry/opentelemetry-js-contrib/blob/28e209a9da36bc4e1f8c2b0db7360170ed46cb80/plugins/node/instrumentation-undici/src/undici.ts - * - * @deprecated This class is no longer used internally and will be removed in a future major version. Use `nativeNodeFetchIntegration` instead. - */ -export class SentryNodeFetchInstrumentation extends InstrumentationBase { - // Keep ref to avoid https://github.com/nodejs/node/issues/42170 bug and for - // unsubscribing. - private _channelSubs: Array; - private _propagationDecisionMap: LRUMap; - private _ignoreOutgoingRequestsMap: WeakMap; - - public constructor(config: SentryNodeFetchInstrumentationOptions = {}) { - super('@sentry/instrumentation-node-fetch', SDK_VERSION, config); - this._channelSubs = []; - this._propagationDecisionMap = new LRUMap(100); - this._ignoreOutgoingRequestsMap = new WeakMap(); - } - - /** No need to instrument files/modules. */ - public init(): void { - return undefined; - } - - /** Disable the instrumentation. */ - public disable(): void { - super.disable(); - this._channelSubs.forEach(sub => sub.unsubscribe()); - this._channelSubs = []; - } - - /** Enable the instrumentation. */ - public enable(): void { - // "enabled" handling is currently a bit messy with InstrumentationBase. - // If constructed with `{enabled: false}`, this `.enable()` is still called, - // and `this.getConfig().enabled !== this.isEnabled()`, creating confusion. - // - // For now, this class will setup for instrumenting if `.enable()` is - // called, but use `this.getConfig().enabled` to determine if - // instrumentation should be generated. This covers the more likely common - // case of config being given a construction time, rather than later via - // `instance.enable()`, `.disable()`, or `.setConfig()` calls. - super.enable(); - - // This method is called by the super-class constructor before ours is - // called. So we need to ensure the property is initalized. - this._channelSubs = this._channelSubs || []; - - // Avoid to duplicate subscriptions - if (this._channelSubs.length > 0) { - return; - } - - this._subscribeToChannel('undici:request:create', this._onRequestCreated.bind(this)); - this._subscribeToChannel('undici:request:headers', this._onResponseHeaders.bind(this)); - } - - /** - * This method is called when a request is created. - * You can still mutate the request here before it is sent. - */ - private _onRequestCreated({ request }: { request: UndiciRequest }): void { - const config = this.getConfig(); - const enabled = config.enabled !== false; - - if (!enabled) { - return; - } - - const shouldIgnore = this._shouldIgnoreOutgoingRequest(request); - // We store this decisision for later so we do not need to re-evaluate it - // Additionally, the active context is not correct in _onResponseHeaders, so we need to make sure it is evaluated here - this._ignoreOutgoingRequestsMap.set(request, shouldIgnore); - - if (shouldIgnore) { - return; - } - - if (config.tracePropagation !== false) { - addTracePropagationHeadersToFetchRequest(request, this._propagationDecisionMap); - } - } - - /** - * This method is called when a response is received. - */ - private _onResponseHeaders({ request, response }: { request: UndiciRequest; response: UndiciResponse }): void { - const config = this.getConfig(); - const enabled = config.enabled !== false; - - if (!enabled) { - return; - } - - const _breadcrumbs = config.breadcrumbs; - const breadCrumbsEnabled = typeof _breadcrumbs === 'undefined' ? true : _breadcrumbs; - - const shouldIgnore = this._ignoreOutgoingRequestsMap.get(request); - - if (breadCrumbsEnabled && !shouldIgnore) { - addFetchRequestBreadcrumb(request, response); - } - } - - /** Subscribe to a diagnostics channel. */ - private _subscribeToChannel( - diagnosticChannel: string, - onMessage: (message: unknown, name: string | symbol) => void, - ): void { - diagch.subscribe?.(diagnosticChannel, onMessage); - - this._channelSubs.push({ - name: diagnosticChannel, - unsubscribe: () => diagch.unsubscribe?.(diagnosticChannel, onMessage), - }); - } - - /** - * Check if the given outgoing request should be ignored. - */ - private _shouldIgnoreOutgoingRequest(request: UndiciRequest): boolean { - if (isTracingSuppressed()) { - return true; - } - - // Add trace propagation headers - const url = getAbsoluteUrl(request.origin, request.path); - const ignoreOutgoingRequests = this.getConfig().ignoreOutgoingRequests; - - if (typeof ignoreOutgoingRequests !== 'function' || !url) { - return false; - } - - return ignoreOutgoingRequests(url); - } -} diff --git a/packages/node/src/integrations/tracing/InstrumentationNodeModuleFile.ts b/packages/node/src/integrations/tracing/InstrumentationNodeModuleFile.ts deleted file mode 100644 index 429c937eaa31..000000000000 --- a/packages/node/src/integrations/tracing/InstrumentationNodeModuleFile.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * NOTICE from the Sentry authors: - * - Vendored locally from @opentelemetry/instrumentation to work around a Bun - * --bytecode bug (https://github.com/getsentry/sentry-javascript/issues/21256). - * - The upstream class imports `normalize` via an indirect platform/index - * re-export chain, which Bun's bytecode bundler renames and loses scope for. - * - This copy imports `normalize` directly from 'path' to break that chain. - */ -/* oxlint-disable */ - -import { normalize } from 'path'; -import type { InstrumentationModuleFile } from '@opentelemetry/instrumentation'; - -export class InstrumentationNodeModuleFile implements InstrumentationModuleFile { - public name: string; - public supportedVersions: string[]; - public patch: (moduleExports: any, moduleVersion?: string) => any; - public unpatch: (moduleExports?: any, moduleVersion?: string) => void; - - constructor( - name: string, - supportedVersions: string[], - patch: (moduleExports: any, moduleVersion?: string) => any, - unpatch: (moduleExports?: any, moduleVersion?: string) => void, - ) { - this.name = normalize(name); - this.supportedVersions = supportedVersions; - this.patch = patch; - this.unpatch = unpatch; - } -} diff --git a/packages/node/src/sdk/esmLoader.ts b/packages/node/src/sdk/esmLoader.ts deleted file mode 100644 index e0bc0a73964a..000000000000 --- a/packages/node/src/sdk/esmLoader.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { debug, GLOBAL_OBJ } from '@sentry/core'; -import { createAddHookMessageChannel } from 'import-in-the-middle'; -import * as moduleModule from 'module'; -import { supportsEsmLoaderHooks } from '../utils/detection'; - -/** - * Initialize the ESM loader - This method is private and not part of the public - * API. - * - * @ignore - */ -export function initializeEsmLoader(): void { - if (!supportsEsmLoaderHooks()) { - return; - } - - if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered) { - GLOBAL_OBJ._sentryEsmLoaderHookRegistered = true; - - try { - const { addHookMessagePort } = createAddHookMessageChannel(); - moduleModule.register('import-in-the-middle/hook.mjs', import.meta.url, { - data: { addHookMessagePort, include: [] }, - transferList: [addHookMessagePort], - }); - } catch (error) { - debug.warn("Failed to register 'import-in-the-middle' hook", error); - } - } -} diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index bdcdd4c8702f..4c39ede26ac3 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -44,7 +44,6 @@ import { getEntryPointType } from '../utils/entry-point'; import { getSpotlightConfig } from '../utils/spotlight'; import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; -import { initializeEsmLoader } from './esmLoader'; import { initOpenTelemetry } from './initOtel'; /** @@ -176,10 +175,6 @@ function _init( const clientOptions = getClientOptions({ ...options, defaultIntegrations }, getDefaultIntegrationsImpl); - if (clientOptions.registerEsmLoaderHooks !== false) { - initializeEsmLoader(); - } - setOpenTelemetryContextAsyncContextStrategy(clientOptions); const scope = getCurrentScope(); diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 42c3e6fb0c40..ae4ade78350e 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -4,7 +4,6 @@ import { debug as coreDebug, hasSpanStreamingEnabled } from '@sentry/core'; import { SentryContextManager } from '../otel/contextManager'; import { setupOpenTelemetryLogger } from '../otel/logger'; import type { NodeClient } from './client'; -import { initializeEsmLoader } from './esmLoader'; import { applyOtelSpanData, type AsyncLocalStorageLookup, @@ -101,8 +100,6 @@ export function preloadOpenTelemetry(options: NodePreloadOptions = {}): void { coreDebug.enable(); } - initializeEsmLoader(); - // These are all integrations that we need to pre-load to ensure they are set up before any other code runs getPreloadMethods(options.integrations).forEach(fn => { fn(); diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index d1fddafdfc15..839db1b7547a 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -75,16 +75,6 @@ export interface BaseNodeOptions extends OpenTelemetryServerRuntimeOptions { * Requires the `LocalVariables` integration. */ includeLocalVariables?: boolean; - - /** - * Whether to register ESM loader hooks to automatically instrument libraries. - * This is necessary to auto instrument libraries that are loaded via ESM imports, but it can cause issues - * with certain libraries. If you run into problems running your app with this enabled, - * please raise an issue in https://github.com/getsentry/sentry-javascript. - * - * Defaults to `true`. - */ - registerEsmLoaderHooks?: boolean; } /** diff --git a/packages/node/src/utils/detection.ts b/packages/node/src/utils/detection.ts deleted file mode 100644 index 98fcf3975609..000000000000 --- a/packages/node/src/utils/detection.ts +++ /dev/null @@ -1,16 +0,0 @@ -function isCjs(): boolean { - /*! rollup-include-cjs-only */ - return true; - /*! rollup-include-cjs-only-end */ - - /*! rollup-include-esm-only */ - return false; - /*! rollup-include-esm-only-end */ -} - -/** - * Check if the current Node.js version supports module.register - */ -export function supportsEsmLoaderHooks(): boolean { - return !isCjs(); -} diff --git a/packages/node/test/integrations/tracing/instrumentationNodeModuleFile.test.ts b/packages/node/test/integrations/tracing/instrumentationNodeModuleFile.test.ts deleted file mode 100644 index 00c014835b62..000000000000 --- a/packages/node/test/integrations/tracing/instrumentationNodeModuleFile.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { describe, expect, it } from 'vitest'; - -const TRACING_DIR = path.resolve(__dirname, '../../../src/integrations/tracing'); - -/** - * Importing InstrumentationNodeModuleFile from @opentelemetry/instrumentation causes - * Bun's --bytecode compiler to inline a re-export chain that loses scope bindings. - * All instrumentations must use the local vendored copy instead. - */ -describe('InstrumentationNodeModuleFile import guard', () => { - it('no file should import InstrumentationNodeModuleFile from @opentelemetry/instrumentation', () => { - const offendingFiles: string[] = []; - - function walkDir(dir: string): void { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - walkDir(fullPath); - } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')) { - const content = fs.readFileSync(fullPath, 'utf-8'); - // Match multi-line imports: look for an import block from @opentelemetry/instrumentation - // that includes InstrumentationNodeModuleFile as a named import - const importBlockRegex = - /import\s*\{[^}]*InstrumentationNodeModuleFile[^}]*\}\s*from\s*['"]@opentelemetry\/instrumentation['"]/s; - if (importBlockRegex.test(content)) { - offendingFiles.push(path.relative(TRACING_DIR, fullPath)); - } - } - } - } - - walkDir(TRACING_DIR); - - expect(offendingFiles).toEqual([]); - }); -}); From 32891c49a24ee5c54e5f21ce26d768899ab3cf02 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 13:23:50 +0200 Subject: [PATCH 2/7] Document removals in migration guide Add the `registerEsmLoaderHooks` option removal to the v11 migration guide, and fold `SentryNodeFetchInstrumentation` into the existing `SentryHttpInstrumentation` removal entry. --- MIGRATION.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 006380d9f4e0..36b9fbd0f248 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -351,7 +351,8 @@ Sentry.init({ - The deprecated `honoIntegration` was removed. Use the [`@sentry/hono`](https://www.npmjs.com/package/@sentry/hono) SDK to instrument Hono. - The `connect` instrumentation was removed. - The deprecated `prismaInstrumentation` option was removed. It was no longer used, as Prisma works out of the box. -- The deprecated `SentryHttpInstrumentation` export was removed. Use `instrumentHttpOutgoingRequests()` instead. +- The `registerEsmLoaderHooks` option was removed. All instrumentation is now channel-based (via `@sentry/server-utils`), so the SDK no longer registers `import-in-the-middle` ESM loader hooks and the option no longer had any effect. +- The deprecated `SentryHttpInstrumentation` and `SentryNodeFetchInstrumentation` exports were removed. Use `instrumentHttpOutgoingRequests()` and the `nativeNodeFetchIntegration` respectively. - (Fastify) The deprecated `setShouldHandleError` method was removed. - (AWS Lambda) The deprecated `disableAwsContextPropagation` option was removed. It no longer had any effect. - (AWS Lambda) The deprecated `startTrace` option was removed. It no longer had any effect; to disable tracing, set `tracesSampleRate` to `0`. From a8440967c60b1380fe6a6fd46663958b04d6f2b5 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 14:56:45 +0200 Subject: [PATCH 3/7] Add import-in-the-middle dependency to nuxt and solidstart Both packages inject `import 'import-in-the-middle/hook.mjs'` into their built server output and previously relied on the package being present transitively through `@sentry/node`. Now that `@sentry/node` no longer depends on it, they must declare it directly, otherwise the built server fails to start with `ERR_MODULE_NOT_FOUND` for `import-in-the-middle/hook.mjs`. --- packages/nuxt/package.json | 1 + packages/solidstart/package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index e758e5799bb0..23a05f085f02 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -61,6 +61,7 @@ "@sentry/bundler-plugins": "10.67.0", "@sentry/server-utils": "10.67.0", "@sentry/vue": "10.67.0", + "import-in-the-middle": "^3.0.0", "local-pkg": "^1.1.2" }, "devDependencies": { diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index d2a4196fc90c..b3aac7c6ba10 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -69,7 +69,8 @@ "@sentry/core": "10.67.0", "@sentry/node": "10.67.0", "@sentry/solid": "10.67.0", - "@sentry/bundler-plugins": "10.67.0" + "@sentry/bundler-plugins": "10.67.0", + "import-in-the-middle": "^3.0.0" }, "devDependencies": { "@solidjs/router": "^0.15.0", From 5dc1cace4cce11ff59d87274ec5a9e5841a327aa Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 15:15:53 +0200 Subject: [PATCH 4/7] Revert "Add import-in-the-middle dependency to nuxt and solidstart" This reverts commit a8440967c60b1380fe6a6fd46663958b04d6f2b5. --- packages/nuxt/package.json | 1 - packages/solidstart/package.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 23a05f085f02..e758e5799bb0 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -61,7 +61,6 @@ "@sentry/bundler-plugins": "10.67.0", "@sentry/server-utils": "10.67.0", "@sentry/vue": "10.67.0", - "import-in-the-middle": "^3.0.0", "local-pkg": "^1.1.2" }, "devDependencies": { diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index b3aac7c6ba10..d2a4196fc90c 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -69,8 +69,7 @@ "@sentry/core": "10.67.0", "@sentry/node": "10.67.0", "@sentry/solid": "10.67.0", - "@sentry/bundler-plugins": "10.67.0", - "import-in-the-middle": "^3.0.0" + "@sentry/bundler-plugins": "10.67.0" }, "devDependencies": { "@solidjs/router": "^0.15.0", From c10c78c45799011c1125c6004cda09d6903e411a Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 15:21:23 +0200 Subject: [PATCH 5/7] Remove import-in-the-middle hook injection from nuxt and solidstart Both packages injected `import 'import-in-the-middle/hook.mjs'` into their built server entry so bundlers would include the file for `@sentry/node`'s esmLoader to `module.register()` at runtime. With the esmLoader removed, nothing registers the hook anymore (the file is inert on import), so the injection only forced a now-dead dependency into the output and broke server startup with `ERR_MODULE_NOT_FOUND`. Drops the injection (and the solidstart `additionalImports` plumbing that existed solely for it). --- packages/nuxt/src/vite/addServerConfig.ts | 12 +--------- .../src/config/addInstrumentation.ts | 3 +-- .../wrapServerEntryWithDynamicImport.ts | 22 ++----------------- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts index 7638764bbfc2..8d4d5db0259a 100644 --- a/packages/nuxt/src/vite/addServerConfig.ts +++ b/packages/nuxt/src/vite/addServerConfig.ts @@ -93,7 +93,7 @@ export function addSentryTopImport(moduleOptions: SentryNuxtModuleOptions, nitro * This function modifies the Rollup configuration to include a plugin that wraps the entry file with a dynamic import (`import()`) * and adds the Sentry server config with the static `import` declaration. * - * With this, the Sentry server config can be loaded before all other modules of the application (which is needed for import-in-the-middle). + * With this, the Sentry server config can be loaded before all other modules of the application. * See: https://nodejs.org/api/module.html#enabling */ export function addDynamicImportEntryFileWrapper( @@ -187,14 +187,6 @@ function wrapEntryWithDynamicImport({ return { id: source, moduleSideEffects: true }; } - if (source === 'import-in-the-middle/hook.mjs') { - // We are importing "import-in-the-middle" in the returned code of the `load()` function below - // By setting `moduleSideEffects` to `true`, the import is added to the bundle, although nothing is imported from it - // By importing "import-in-the-middle/hook.mjs", we can make sure this file is included, as not all node builders are including files imported with `module.register()`. - // Prevents the error "Failed to register ESM hook Error: Cannot find module 'import-in-the-middle/hook.mjs'" - return { id: source, moduleSideEffects: true, external: true }; - } - if (options.isEntry && source.includes('.mjs') && !source.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { const resolution = await this.resolve(source, importer, options); @@ -238,8 +230,6 @@ function wrapEntryWithDynamicImport({ // Dynamic `import()` for the previous, actual entry point. // `import()` can be used for any code that should be run after the hooks are registered (https://nodejs.org/api/module.html#enabling) `import(${JSON.stringify(entryId)});\n` + - // By importing "import-in-the-middle/hook.mjs", we can make sure this file wil be included, as not all node builders are including files imported with `module.register()`. - "import 'import-in-the-middle/hook.mjs';\n" + `${reExportedFunctions}\n` ); } diff --git a/packages/solidstart/src/config/addInstrumentation.ts b/packages/solidstart/src/config/addInstrumentation.ts index fe141f92c2ea..5c585faada70 100644 --- a/packages/solidstart/src/config/addInstrumentation.ts +++ b/packages/solidstart/src/config/addInstrumentation.ts @@ -143,7 +143,7 @@ export async function addSentryTopImport(nitro: Nitro): Promise { * This function modifies the Rollup configuration to include a plugin that wraps the entry file with a dynamic import (`import()`) * and adds the Sentry server config with the static `import` declaration. * - * With this, the Sentry server config can be loaded before all other modules of the application (which is needed for import-in-the-middle). + * With this, the Sentry server config can be loaded before all other modules of the application. * See: https://nodejs.org/api/module.html#enabling */ export async function addDynamicImportEntryFileWrapper({ @@ -177,7 +177,6 @@ export async function addDynamicImportEntryFileWrapper({ serverEntrypointFileName: sentryPluginOptions.serverEntrypointFileName || nitro.options.preset, resolvedServerConfigPath: serverInstrumentationPath, entrypointWrappedFunctions: sentryPluginOptions.experimental_entrypointWrappedFunctions, - additionalImports: ['import-in-the-middle/hook.mjs'], debug: sentryPluginOptions.debug, }), ); diff --git a/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts b/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts index 24c21b405cc4..4f14a54391d5 100644 --- a/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts +++ b/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts @@ -13,7 +13,6 @@ export type WrapServerEntryPluginOptions = { serverConfigFileName: string; resolvedServerConfigPath: string; entrypointWrappedFunctions: string[]; - additionalImports?: string[]; debug?: boolean; }; @@ -27,18 +26,11 @@ export type WrapServerEntryPluginOptions = { * @param config.serverEntrypointFileName The server entrypoint (with file extension). Usually, this is defined by the Nitro preset and is something like 'node-server.mjs' * @param config.resolvedServerConfigPath Resolved path of the Sentry server config (based on `src` directory) * @param config.entryPointWrappedFunctions Exported bindings of the server entry file, which are wrapped as async function. E.g. ['default', 'handler', 'server'] - * @param config.additionalImports Adds additional imports to the entry file. Can be e.g. 'import-in-the-middle/hook.mjs' * @param config.debug Whether debug logs are enabled in the build time environment */ export function wrapServerEntryWithDynamicImport(config: WrapServerEntryPluginOptions): InputPluginOption { - const { - serverConfigFileName, - serverEntrypointFileName, - resolvedServerConfigPath, - entrypointWrappedFunctions, - additionalImports, - debug, - } = config; + const { serverConfigFileName, serverEntrypointFileName, resolvedServerConfigPath, entrypointWrappedFunctions, debug } = + config; // In order to correctly import the server config file // and dynamically import the nitro runtime, we need to @@ -53,14 +45,6 @@ export function wrapServerEntryWithDynamicImport(config: WrapServerEntryPluginOp return { id: source, moduleSideEffects: true }; } - if (additionalImports?.includes(source)) { - // When importing additional imports like "import-in-the-middle/hook.mjs" in the returned code of the `load()` function below: - // By setting `moduleSideEffects` to `true`, the import is added to the bundle, although nothing is imported from it - // By importing "import-in-the-middle/hook.mjs", we can make sure this file is included, as not all node builders are including files imported with `module.register()`. - // Prevents the error "Failed to register ESM hook Error: Cannot find module 'import-in-the-middle/hook.mjs'" - return { id: source, moduleSideEffects: true, external: true }; - } - if ( options.isEntry && source.includes(serverEntrypointFileName) && @@ -105,8 +89,6 @@ export function wrapServerEntryWithDynamicImport(config: WrapServerEntryPluginOp // Dynamic `import()` for the previous, actual entry point. // `import()` can be used for any code that should be run after the hooks are registered (https://nodejs.org/api/module.html#enabling) `import(${JSON.stringify(entryId)});\n` + - // By importing additional imports like "import-in-the-middle/hook.mjs", we can make sure this file wil be included, as not all node builders are including files imported with `module.register()`. - `${additionalImports ? additionalImports.map(importPath => `import "${importPath}";\n`) : ''}` + `${reExportedFunctions}\n` ); } From ce100e2d0ca50d43dd121d895ca8b5e6049e29c1 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 15:42:26 +0200 Subject: [PATCH 6/7] Fix formatting --- .../src/config/wrapServerEntryWithDynamicImport.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts b/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts index 4f14a54391d5..ef22da065d14 100644 --- a/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts +++ b/packages/solidstart/src/config/wrapServerEntryWithDynamicImport.ts @@ -29,8 +29,13 @@ export type WrapServerEntryPluginOptions = { * @param config.debug Whether debug logs are enabled in the build time environment */ export function wrapServerEntryWithDynamicImport(config: WrapServerEntryPluginOptions): InputPluginOption { - const { serverConfigFileName, serverEntrypointFileName, resolvedServerConfigPath, entrypointWrappedFunctions, debug } = - config; + const { + serverConfigFileName, + serverEntrypointFileName, + resolvedServerConfigPath, + entrypointWrappedFunctions, + debug, + } = config; // In order to correctly import the server config file // and dynamically import the nitro runtime, we need to From c0304930360acce9dd8ada9990cf672070dfffc3 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 16:32:08 +0200 Subject: [PATCH 7/7] Remove changelog entry --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c40e6fb165b5..86436b9158be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,6 @@ Work in this release was contributed by @psh4607, @trinitiwowka, @nehaprasad-dev - `DenoMongoose` => `Mongoose` - `DenoMysql` => `Mysql` - `DenoPostgres` => `Postgres` -- feat(node)!: Remove `import-in-the-middle`-based instrumentations from `@sentry/node`. All performance integrations are now channel-based via `@sentry/server-utils`. As part of this: - - The `registerEsmLoaderHooks` option was removed from `NodeOptions`. The SDK no longer registers `import-in-the-middle` ESM loader hooks, and `import-in-the-middle` is no longer a dependency. - - `knexIntegration` and `dataloaderIntegration` are now channel-only and no longer fall back to OpenTelemetry instrumentation on runtimes without diagnostics-channel injection. - - Support for Fastify `<3.21.0` was dropped. ## 10.67.0