diff --git a/packages/node-core/src/index.ts b/packages/node-core/src/index.ts index cf581bd63b66..57d8e2ece0ae 100644 --- a/packages/node-core/src/index.ts +++ b/packages/node-core/src/index.ts @@ -35,7 +35,8 @@ export { getSentryRelease, defaultStackParser } from './sdk/api'; export { createGetModuleFromFilename } from './utils/module'; export { addOriginToSpan } from './utils/addOriginToSpan'; export { getRequestUrl } from './utils/getRequestUrl'; -export { isCjs } from './utils/commonjs'; +export { initializeEsmLoader } from './sdk/esmLoader'; +export { isCjs } from './utils/detection'; export { ensureIsWrapped } from './utils/ensureIsWrapped'; export { createMissingInstrumentationContext } from './utils/createMissingInstrumentationContext'; export { envToBool } from './utils/envToBool'; diff --git a/packages/node-core/src/integrations/modules.ts b/packages/node-core/src/integrations/modules.ts index 6724a473b5bb..7079f4a2fab8 100644 --- a/packages/node-core/src/integrations/modules.ts +++ b/packages/node-core/src/integrations/modules.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import type { IntegrationFn } from '@sentry/core'; -import { isCjs } from '../utils/commonjs'; +import { isCjs } from '../utils/detection'; type ModuleInfo = Record; diff --git a/packages/node-core/src/sdk/esmLoader.ts b/packages/node-core/src/sdk/esmLoader.ts index 2f0d8b405333..14f24f0df311 100644 --- a/packages/node-core/src/sdk/esmLoader.ts +++ b/packages/node-core/src/sdk/esmLoader.ts @@ -1,31 +1,31 @@ -import { consoleSandbox, debug, GLOBAL_OBJ } from '@sentry/core'; +import { debug, GLOBAL_OBJ } from '@sentry/core'; import { createAddHookMessageChannel } from 'import-in-the-middle'; -import moduleModule from 'module'; +import * as moduleModule from 'module'; +import { supportsEsmLoaderHooks } from '../utils/detection'; -/** Initialize the ESM loader. */ -export function maybeInitializeEsmLoader(): void { - const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); +/** + * 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; - // Register hook was added in v20.6.0 and v18.19.0 - if (nodeMajor >= 21 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { - if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered) { - try { - const { addHookMessagePort } = createAddHookMessageChannel(); - // @ts-expect-error register is available in these versions - moduleModule.register('import-in-the-middle/hook.mjs', import.meta.url, { - data: { addHookMessagePort, include: [] }, - transferList: [addHookMessagePort], - }); - } catch (error) { - debug.warn('Failed to register ESM hook', error); - } + try { + const { addHookMessagePort } = createAddHookMessageChannel(); + // @ts-expect-error register is available in these versions + 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); } - } else { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, - ); - }); } } diff --git a/packages/node-core/src/sdk/index.ts b/packages/node-core/src/sdk/index.ts index e5b12166d962..c4a16d76a1d0 100644 --- a/packages/node-core/src/sdk/index.ts +++ b/packages/node-core/src/sdk/index.ts @@ -35,11 +35,11 @@ import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } import { systemErrorIntegration } from '../integrations/systemError'; import { makeNodeTransport } from '../transports'; import type { NodeClientOptions, NodeOptions } from '../types'; -import { isCjs } from '../utils/commonjs'; +import { isCjs } from '../utils/detection'; import { envToBool } from '../utils/envToBool'; import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; -import { maybeInitializeEsmLoader } from './esmLoader'; +import { initializeEsmLoader } from './esmLoader'; /** * Get default integrations for the Node-Core SDK. @@ -106,8 +106,8 @@ function _init( } } - if (!isCjs() && options.registerEsmLoaderHooks !== false) { - maybeInitializeEsmLoader(); + if (options.registerEsmLoaderHooks !== false) { + initializeEsmLoader(); } setOpenTelemetryContextAsyncContextStrategy(); @@ -131,7 +131,7 @@ function _init( client.init(); - debug.log(`Running in ${isCjs() ? 'CommonJS' : 'ESM'} mode.`); + debug.log(`SDK initialized from ${isCjs() ? 'CommonJS' : 'ESM'}`); client.startClientReportTracking(); diff --git a/packages/node-core/src/utils/commonjs.ts b/packages/node-core/src/utils/commonjs.ts deleted file mode 100644 index 23a9b97f9fc1..000000000000 --- a/packages/node-core/src/utils/commonjs.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** Detect CommonJS. */ -export function isCjs(): boolean { - try { - return typeof module !== 'undefined' && typeof module.exports !== 'undefined'; - } catch { - return false; - } -} diff --git a/packages/node-core/src/utils/createMissingInstrumentationContext.ts b/packages/node-core/src/utils/createMissingInstrumentationContext.ts index 1930bcf782eb..3af8dc9e176a 100644 --- a/packages/node-core/src/utils/createMissingInstrumentationContext.ts +++ b/packages/node-core/src/utils/createMissingInstrumentationContext.ts @@ -1,5 +1,5 @@ import type { MissingInstrumentationContext } from '@sentry/core'; -import { isCjs } from './commonjs'; +import { isCjs } from './detection'; export const createMissingInstrumentationContext = (pkg: string): MissingInstrumentationContext => ({ package: pkg, diff --git a/packages/node-core/src/utils/detection.ts b/packages/node-core/src/utils/detection.ts new file mode 100644 index 000000000000..f7ae9a792c27 --- /dev/null +++ b/packages/node-core/src/utils/detection.ts @@ -0,0 +1,39 @@ +import { consoleSandbox } from '@sentry/core'; +import { NODE_MAJOR, NODE_MINOR } from '../nodeVersion'; + +/** Detect CommonJS. */ +export function isCjs(): boolean { + try { + return typeof module !== 'undefined' && typeof module.exports !== 'undefined'; + } catch { + return false; + } +} + +let hasWarnedAboutNodeVersion: boolean | undefined; + +/** + * Check if the current Node.js version supports module.register + */ +export function supportsEsmLoaderHooks(): boolean { + if (isCjs()) { + return false; + } + + if (NODE_MAJOR >= 21 || (NODE_MAJOR === 20 && NODE_MINOR >= 6) || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) { + return true; + } + + if (!hasWarnedAboutNodeVersion) { + hasWarnedAboutNodeVersion = true; + + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, + ); + }); + } + + return false; +} diff --git a/packages/node-core/src/utils/ensureIsWrapped.ts b/packages/node-core/src/utils/ensureIsWrapped.ts index 70253d9debb7..a73c087ebaf2 100644 --- a/packages/node-core/src/utils/ensureIsWrapped.ts +++ b/packages/node-core/src/utils/ensureIsWrapped.ts @@ -1,8 +1,8 @@ import { isWrapped } from '@opentelemetry/instrumentation'; import { consoleSandbox, getClient, getGlobalScope, hasSpansEnabled, isEnabled } from '@sentry/core'; import type { NodeClient } from '../sdk/client'; -import { isCjs } from './commonjs'; import { createMissingInstrumentationContext } from './createMissingInstrumentationContext'; +import { isCjs } from './detection'; /** * Checks and warns if a framework isn't wrapped by opentelemetry. diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index ef27be0514c3..67de29821537 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -7,11 +7,14 @@ import { ATTR_SERVICE_VERSION, SEMRESATTRS_SERVICE_NAMESPACE, } from '@opentelemetry/semantic-conventions'; -import { consoleSandbox, debug as coreDebug, GLOBAL_OBJ, SDK_VERSION } from '@sentry/core'; -import { type NodeClient, isCjs, SentryContextManager, setupOpenTelemetryLogger } from '@sentry/node-core'; +import { debug as coreDebug, SDK_VERSION } from '@sentry/core'; +import { + type NodeClient, + initializeEsmLoader, + SentryContextManager, + setupOpenTelemetryLogger, +} from '@sentry/node-core'; import { SentryPropagator, SentrySampler, SentrySpanProcessor } from '@sentry/opentelemetry'; -import { createAddHookMessageChannel } from 'import-in-the-middle'; -import moduleModule from 'module'; import { DEBUG_BUILD } from '../debug-build'; import { getOpenTelemetryInstrumentationToPreload } from '../integrations/tracing'; @@ -35,34 +38,6 @@ export function initOpenTelemetry(client: NodeClient, options: AdditionalOpenTel client.traceProvider = provider; } -/** Initialize the ESM loader. */ -export function maybeInitializeEsmLoader(): void { - const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); - - // Register hook was added in v20.6.0 and v18.19.0 - if (nodeMajor >= 21 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { - if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered) { - try { - const { addHookMessagePort } = createAddHookMessageChannel(); - // @ts-expect-error register is available in these versions - moduleModule.register('import-in-the-middle/hook.mjs', import.meta.url, { - data: { addHookMessagePort, include: [] }, - transferList: [addHookMessagePort], - }); - } catch (error) { - coreDebug.warn('Failed to register ESM hook', error); - } - } - } else { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, - ); - }); - } -} - interface NodePreloadOptions { debug?: boolean; integrations?: string[]; @@ -80,9 +55,7 @@ export function preloadOpenTelemetry(options: NodePreloadOptions = {}): void { coreDebug.enable(); } - if (!isCjs()) { - maybeInitializeEsmLoader(); - } + 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 => {