From 58d24fe2c395954c4e82af3b7db563a78f95cc6c Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 5 Aug 2026 19:54:54 +0200 Subject: [PATCH] feat(node)!: Remove `init`, `preload` entry points and `preloadOpenTelemetry` Removes the `@sentry/node/init` and `@sentry/node/preload` no-code entry points and the `preloadOpenTelemetry()` function. All instrumentation is channel-based via orchestrion and set up when the instrumented module loads, so preloading no longer does anything. Co-Authored-By: Claude Opus 4.8 --- MIGRATION.md | 4 +- .../scripts/consistentExports.ts | 1 - .../node-express-cjs-preload/package.json | 26 --- .../playwright.config.mjs | 7 - .../node-express-cjs-preload/src/app.js | 53 ------ .../start-event-proxy.mjs | 6 - .../tests/server.test.ts | 117 ------------- .../node-express-esm-preload/package.json | 26 --- .../playwright.config.mjs | 7 - .../node-express-esm-preload/src/app.mjs | 68 -------- .../start-event-proxy.mjs | 6 - .../tests/server.test.ts | 158 ------------------ .../express/ignore-layers-type/instrument.mjs | 12 ++ .../scenario.mjs | 2 +- .../{late-init => ignore-layers-type}/test.ts | 9 +- .../suites/express/late-init/instrument.mjs | 20 --- .../suites/no-code/app.mjs | 3 - .../suites/no-code/test.ts | 30 ---- .../utils/runner/createRunner.ts | 8 +- packages/node/package.json | 16 -- packages/node/rollup.npm.config.mjs | 2 - packages/node/src/index.ts | 2 +- packages/node/src/init.ts | 9 - packages/node/src/integrations/http/index.ts | 4 - .../node/src/integrations/tracing/index.ts | 9 - packages/node/src/preload.ts | 20 --- packages/node/src/sdk/initOtel.ts | 42 ----- packages/node/test/sdk/preload.test.ts | 70 -------- 28 files changed, 22 insertions(+), 715 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-cjs-preload/src/app.js delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-cjs-preload/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-esm-preload/src/app.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-esm-preload/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts create mode 100644 dev-packages/node-integration-tests/suites/express/ignore-layers-type/instrument.mjs rename dev-packages/node-integration-tests/suites/express/{late-init => ignore-layers-type}/scenario.mjs (85%) rename dev-packages/node-integration-tests/suites/express/{late-init => ignore-layers-type}/test.ts (76%) delete mode 100644 dev-packages/node-integration-tests/suites/express/late-init/instrument.mjs delete mode 100644 dev-packages/node-integration-tests/suites/no-code/app.mjs delete mode 100644 dev-packages/node-integration-tests/suites/no-code/test.ts delete mode 100644 packages/node/src/init.ts delete mode 100644 packages/node/src/preload.ts delete mode 100644 packages/node/test/sdk/preload.test.ts diff --git a/MIGRATION.md b/MIGRATION.md index 5e72bf8776b7..cfaa4fa824c0 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -236,8 +236,6 @@ node --require ./instrument.js app.js node --import ./instrument.js app.js ``` -The same applies to the no-code entry points, e.g. `node --import=@sentry/node/init app.js` and `node --import @sentry/node/preload app.js`. - ### Span streaming is now the default Affected SDKs: All SDKs. @@ -613,6 +611,8 @@ Sentry.init({ - 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. - The `generateInstrumentOnce` export was removed (from `@sentry/node` and the framework SDKs that re-exported it). It wrapped OpenTelemetry's `registerInstrumentations` and is no longer needed now that instrumentation is channel-based. +- The `@sentry/node/init` and `@sentry/node/preload` entry points were removed. Create your own instrument file that calls `Sentry.init()` and preload it with `node --import ./instrument.mjs app.js` instead. +- The `preloadOpenTelemetry()` function was removed. All instrumentation is now channel-based via `orchestrion` and is set up when the instrumented module loads, so preloading is no longer needed. - The `@sentry/node/loader` entry point was removed. Use `node --import @sentry/node/import` instead. - (Astro) The `@sentry/astro/loader` entry point was removed. Use `node --import @sentry/astro/import` instead. - (AWS Lambda) The `@sentry/aws-serverless/loader` entry point was removed. Use `node --import @sentry/aws-serverless/import` instead. diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 14aa5018e226..fd7b1adfe01a 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -18,7 +18,6 @@ const NODE_EXPORTS_IGNORE = [ 'setOpenTelemetryContextAsyncContextStrategy', 'getDefaultIntegrationsWithoutPerformance', 'initWithoutDefaultIntegrations', - 'preloadOpenTelemetry', // Internal helper only needed within integrations (e.g. bunRuntimeMetricsIntegration) '_INTERNAL_normalizeCollectionInterval', // not exported by bun diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json deleted file mode 100644 index 51ce8d899f7a..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "node-express-cjs-preload", - "version": "1.0.0", - "private": true, - "sentryTest": { - "skip": true - }, - "scripts": { - "start": "node --import @sentry/node/preload src/app.js", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install", - "test:assert": "playwright test" - }, - "dependencies": { - "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@sentry/opentelemetry": "file:../../packed/sentry-opentelemetry-packed.tgz", - "express": "^4.21.2" - }, - "devDependencies": { - "@playwright/test": "~1.56.0", - "@sentry-internal/test-utils": "link:../../../test-utils" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs deleted file mode 100644 index 31f2b913b58b..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/playwright.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -const config = getPlaywrightConfig({ - startCommand: `pnpm start`, -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/src/app.js b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/src/app.js deleted file mode 100644 index 134c2de6560c..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/src/app.js +++ /dev/null @@ -1,53 +0,0 @@ -const Sentry = require('@sentry/node'); -const express = require('express'); - -const app = express(); -const port = 3030; - -app.get('/test-success', function (req, res) { - setTimeout(() => { - res.status(200).end(); - }, 100); -}); - -app.get('/test-transaction/:param', function (req, res) { - setTimeout(() => { - res.status(200).end(); - }, 100); -}); - -app.get('/test-error', function (req, res) { - Sentry.captureException(new Error('This is an error')); - setTimeout(() => { - Sentry.flush(2000).then(() => { - res.status(200).end(); - }); - }, 100); -}); - -Sentry.setupExpressErrorHandler(app); - -app.use(function onError(err, req, res, next) { - // The error id is attached to `res.sentry` to be returned - // and optionally displayed to the user for support. - res.statusCode = 500; - res.end(res.sentry + '\n'); -}); - -async function run() { - await new Promise(resolve => setTimeout(resolve, 1000)); - - Sentry.init({ - traceLifecycle: 'static', - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.E2E_TEST_DSN, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1, - }); - - app.listen(port, () => { - console.log(`Example app listening on port ${port}`); - }); -} - -run(); diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/start-event-proxy.mjs deleted file mode 100644 index ae2ce096fdba..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'node-express-cjs-preload', -}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts deleted file mode 100644 index 2e4de0e27c7a..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; - -test('Should record exceptions captured inside handlers', async ({ request }) => { - const errorEventPromise = waitForError('node-express-cjs-preload', errorEvent => { - return !!errorEvent?.exception?.values?.[0]?.value?.includes('This is an error'); - }); - - await request.get('/test-error'); - - await expect(errorEventPromise).resolves.toBeDefined(); -}); - -test('Should record a transaction for a parameterless route', async ({ request }) => { - const transactionEventPromise = waitForTransaction('node-express-cjs-preload', transactionEvent => { - return transactionEvent?.transaction === 'GET /test-success'; - }); - - await request.get('/test-success'); - - await expect(transactionEventPromise).resolves.toBeDefined(); -}); - -test('Should record a transaction for route with parameters', async ({ request }) => { - const transactionEventPromise = waitForTransaction('node-express-cjs-preload', transactionEvent => { - return transactionEvent.contexts?.trace?.data?.['http.target'] === '/test-transaction/1'; - }); - - await request.get('/test-transaction/1'); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent).toBeDefined(); - expect(transactionEvent.transaction).toEqual('GET /test-transaction/:param'); - expect(transactionEvent.contexts?.trace?.data).toEqual( - expect.objectContaining({ - 'http.flavor': '1.1', - 'http.host': 'localhost:3030', - 'http.method': 'GET', - 'http.response.status_code': 200, - 'http.route': '/test-transaction/:param', - 'http.scheme': 'http', - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.target': '/test-transaction/1', - 'url.full': 'http://localhost:3030/test-transaction/1', - 'http.user_agent': expect.any(String), - 'net.host.ip': expect.any(String), - 'net.host.name': 'localhost', - 'net.host.port': 3030, - 'net.peer.ip': expect.any(String), - 'net.peer.port': expect.any(Number), - 'net.transport': 'ip_tcp', - 'sentry.kind': 'server', - 'sentry.op': 'http.server', - 'sentry.origin': 'auto.http.otel.http', - 'sentry.sample_rate': 1, - 'sentry.source': 'route', - }), - ); - - const spans = transactionEvent.spans || []; - expect(spans).toContainEqual({ - data: { - 'express.name': 'query', - 'express.type': 'middleware', - 'sentry.origin': 'auto.http.express', - 'sentry.op': 'middleware', - }, - op: 'middleware', - description: 'query', - origin: 'auto.http.express', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(spans).toContainEqual({ - data: { - 'express.name': 'expressInit', - 'express.type': 'middleware', - 'sentry.origin': 'auto.http.express', - 'sentry.op': 'middleware', - }, - op: 'middleware', - description: 'expressInit', - origin: 'auto.http.express', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(spans).toContainEqual({ - data: { - 'express.name': '/test-transaction/:param', - 'express.type': 'request_handler', - 'http.route': '/test-transaction/:param', - 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', - }, - op: 'request_handler.express', - description: '/test-transaction/:param', - origin: 'auto.http.express', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json deleted file mode 100644 index 404f975a8572..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "node-express-esm-preload", - "version": "1.0.0", - "private": true, - "sentryTest": { - "skip": true - }, - "scripts": { - "start": "node --import @sentry/node/preload src/app.mjs", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install", - "test:assert": "playwright test" - }, - "dependencies": { - "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@sentry/opentelemetry": "file:../../packed/sentry-opentelemetry-packed.tgz", - "express": "^4.21.2" - }, - "devDependencies": { - "@playwright/test": "~1.56.0", - "@sentry-internal/test-utils": "link:../../../test-utils" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs deleted file mode 100644 index 31f2b913b58b..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/playwright.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -const config = getPlaywrightConfig({ - startCommand: `pnpm start`, -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/src/app.mjs b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/src/app.mjs deleted file mode 100644 index 28e595e3f667..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/src/app.mjs +++ /dev/null @@ -1,68 +0,0 @@ -import * as http from 'http'; -import * as Sentry from '@sentry/node'; -import express from 'express'; - -const app = express(); -const port = 3030; - -app.get('/test-success', function (req, res) { - setTimeout(() => { - res.status(200).end(); - }, 100); -}); - -app.get('/test-transaction/:param', function (req, res) { - setTimeout(() => { - res.status(200).end(); - }, 100); -}); - -app.get('/test-error', function (req, res) { - Sentry.captureException(new Error('This is an error')); - setTimeout(() => { - Sentry.flush(2000).then(() => { - res.status(200).end(); - }); - }, 100); -}); - -app.get('/http-req', function (req, res) { - http - .request('http://example.com', httpRes => { - let data = ''; - httpRes.on('data', d => { - data += d; - }); - httpRes.on('end', () => { - res.status(200).send(data).end(); - }); - }) - .end(); -}); - -Sentry.setupExpressErrorHandler(app); - -app.use(function onError(err, req, res, next) { - // The error id is attached to `res.sentry` to be returned - // and optionally displayed to the user for support. - res.statusCode = 500; - res.end(res.sentry + '\n'); -}); - -async function run() { - await new Promise(resolve => setTimeout(resolve, 1000)); - - Sentry.init({ - traceLifecycle: 'static', - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.E2E_TEST_DSN, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1, - }); - - app.listen(port, () => { - console.log(`Example app listening on port ${port}`); - }); -} - -run(); diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/start-event-proxy.mjs deleted file mode 100644 index 152218840a7a..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'node-express-esm-preload', -}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts deleted file mode 100644 index 45e432b9d5a7..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; - -test('Should record exceptions captured inside handlers', async ({ request }) => { - const errorEventPromise = waitForError('node-express-esm-preload', errorEvent => { - return !!errorEvent?.exception?.values?.[0]?.value?.includes('This is an error'); - }); - - await request.get('/test-error'); - - await expect(errorEventPromise).resolves.toBeDefined(); -}); - -test('Should record a transaction for a parameterless route', async ({ request }) => { - const transactionEventPromise = waitForTransaction('node-express-esm-preload', transactionEvent => { - return transactionEvent?.transaction === 'GET /test-success'; - }); - - await request.get('/test-success'); - - await expect(transactionEventPromise).resolves.toBeDefined(); -}); - -test('Should record a transaction for route with parameters', async ({ request }) => { - const transactionEventPromise = waitForTransaction('node-express-esm-preload', transactionEvent => { - return transactionEvent.contexts?.trace?.data?.['http.target'] === '/test-transaction/1'; - }); - - await request.get('/test-transaction/1'); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent).toBeDefined(); - expect(transactionEvent.transaction).toEqual('GET /test-transaction/:param'); - expect(transactionEvent.contexts?.trace?.data).toEqual( - expect.objectContaining({ - 'http.flavor': '1.1', - 'http.host': 'localhost:3030', - 'http.method': 'GET', - 'http.response.status_code': 200, - 'http.route': '/test-transaction/:param', - 'http.scheme': 'http', - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.target': '/test-transaction/1', - 'url.full': 'http://localhost:3030/test-transaction/1', - 'http.user_agent': expect.any(String), - 'net.host.ip': expect.any(String), - 'net.host.name': 'localhost', - 'net.host.port': 3030, - 'net.peer.ip': expect.any(String), - 'net.peer.port': expect.any(Number), - 'net.transport': 'ip_tcp', - 'sentry.kind': 'server', - 'sentry.op': 'http.server', - 'sentry.origin': 'auto.http.otel.http', - 'sentry.sample_rate': 1, - 'sentry.source': 'route', - }), - ); - - const spans = transactionEvent.spans || []; - expect(spans).toContainEqual({ - data: { - 'express.name': 'query', - 'express.type': 'middleware', - 'sentry.origin': 'auto.http.express', - 'sentry.op': 'middleware', - }, - op: 'middleware', - description: 'query', - origin: 'auto.http.express', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(spans).toContainEqual({ - data: { - 'express.name': 'expressInit', - 'express.type': 'middleware', - 'sentry.origin': 'auto.http.express', - 'sentry.op': 'middleware', - }, - op: 'middleware', - description: 'expressInit', - origin: 'auto.http.express', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(spans).toContainEqual({ - data: { - 'express.name': '/test-transaction/:param', - 'express.type': 'request_handler', - 'http.route': '/test-transaction/:param', - 'sentry.origin': 'auto.http.express', - 'sentry.op': 'request_handler.express', - }, - op: 'request_handler.express', - description: '/test-transaction/:param', - origin: 'auto.http.express', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); -}); - -test('Should record spans from http instrumentation', async ({ request }) => { - const transactionEventPromise = waitForTransaction('node-express-esm-preload', transactionEvent => { - return transactionEvent.contexts?.trace?.data?.['http.target'] === '/http-req'; - }); - - await request.get('/http-req'); - - const transactionEvent = await transactionEventPromise; - - const httpClientSpan = transactionEvent.spans?.find(span => span.op === 'http.client'); - - expect(httpClientSpan).toEqual({ - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - data: expect.objectContaining({ - 'http.flavor': '1.1', - 'http.host': 'example.com', - 'http.method': 'GET', - 'http.response.status_code': 200, - 'http.status_code': 200, - 'http.status_text': 'OK', - 'http.target': '/', - 'url.full': 'http://example.com/', - 'net.peer.ip': expect.any(String), - 'net.peer.name': 'example.com', - 'net.peer.port': 80, - 'net.transport': 'ip_tcp', - 'sentry.kind': 'client', - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.client', - }), - description: 'GET http://example.com/', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - status: 'ok', - op: 'http.client', - origin: 'auto.http.client', - }); -}); diff --git a/dev-packages/node-integration-tests/suites/express/ignore-layers-type/instrument.mjs b/dev-packages/node-integration-tests/suites/express/ignore-layers-type/instrument.mjs new file mode 100644 index 000000000000..d16ee8ded955 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/express/ignore-layers-type/instrument.mjs @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + // suppress the middleware layer that the cors module generates + integrations: [Sentry.expressIntegration({ ignoreLayersType: ['middleware'] })], +}); diff --git a/dev-packages/node-integration-tests/suites/express/late-init/scenario.mjs b/dev-packages/node-integration-tests/suites/express/ignore-layers-type/scenario.mjs similarity index 85% rename from dev-packages/node-integration-tests/suites/express/late-init/scenario.mjs rename to dev-packages/node-integration-tests/suites/express/ignore-layers-type/scenario.mjs index faea295143ef..bd775c17fcee 100644 --- a/dev-packages/node-integration-tests/suites/express/late-init/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/express/ignore-layers-type/scenario.mjs @@ -6,7 +6,7 @@ import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-int const app = express(); // cors() would normally create a 'middleware' type span, but the -// ignoreLayersType: ['middleware'] option set via Sentry.init() suppresses it. +// ignoreLayersType: ['middleware'] option suppresses it. app.use(cors()); app.get('/test/express', (_req, res) => { diff --git a/dev-packages/node-integration-tests/suites/express/late-init/test.ts b/dev-packages/node-integration-tests/suites/express/ignore-layers-type/test.ts similarity index 76% rename from dev-packages/node-integration-tests/suites/express/late-init/test.ts rename to dev-packages/node-integration-tests/suites/express/ignore-layers-type/test.ts index d7aeba94fd2b..f4dc8213e197 100644 --- a/dev-packages/node-integration-tests/suites/express/late-init/test.ts +++ b/dev-packages/node-integration-tests/suites/express/ignore-layers-type/test.ts @@ -2,13 +2,13 @@ import { afterAll, describe, expect } from 'vitest'; import { assertSentryTransaction } from '../../../utils/assertions'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; -describe('express late init', () => { +describe('express ignoreLayersType', () => { afterAll(() => { cleanupChildProcesses(); }); createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { - test('applies expressIntegration config set via Sentry.init() called after instrumentExpress()', async () => { + test('suppresses spans for layer types listed in ignoreLayersType', async () => { const runner = createRunner() .expect({ transaction: transaction => { @@ -21,8 +21,6 @@ describe('express late init', () => { }, }, }); - // request_handler span IS present - // confirms the express patch was applied. expect(transaction.spans).toContainEqual( expect.objectContaining({ data: expect.objectContaining({ @@ -30,8 +28,7 @@ describe('express late init', () => { }), }), ); - // Middleware spans NOT present, ignoreLayersType: ['middleware'] - // configured via the Sentry.init() AFTER instrumentExpress(). + // The cors() middleware span is suppressed by ignoreLayersType: ['middleware']. expect(transaction.spans).not.toContainEqual( expect.objectContaining({ data: expect.objectContaining({ diff --git a/dev-packages/node-integration-tests/suites/express/late-init/instrument.mjs b/dev-packages/node-integration-tests/suites/express/late-init/instrument.mjs deleted file mode 100644 index 05c979ec6585..000000000000 --- a/dev-packages/node-integration-tests/suites/express/late-init/instrument.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; - -// First: preload the express instrumentation without calling Sentry.init(). -// registers OTel module hook, patches the Express module with no config. -Sentry.preloadOpenTelemetry({ integrations: ['Express'] }); - -// call Sentry.init() with express integration config. -// instrumentExpress is already registered, so this calls setConfig() on the -// existing instrumentation to update its options. The lazy getOptions() -// in patchLayer ensures the updated options are read at request time. -Sentry.init({ - traceLifecycle: 'static', - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, - // suppress the middleware layer that the cors module generates - integrations: [Sentry.expressIntegration({ ignoreLayersType: ['middleware'] })], -}); diff --git a/dev-packages/node-integration-tests/suites/no-code/app.mjs b/dev-packages/node-integration-tests/suites/no-code/app.mjs deleted file mode 100644 index cb1937007297..000000000000 --- a/dev-packages/node-integration-tests/suites/no-code/app.mjs +++ /dev/null @@ -1,3 +0,0 @@ -setTimeout(() => { - throw new Error('Test error'); -}, 1000); diff --git a/dev-packages/node-integration-tests/suites/no-code/test.ts b/dev-packages/node-integration-tests/suites/no-code/test.ts deleted file mode 100644 index 629729373e10..000000000000 --- a/dev-packages/node-integration-tests/suites/no-code/test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { afterAll, describe, test } from 'vitest'; -import { cleanupChildProcesses, createRunner } from '../../utils/runner'; - -const EVENT = { - exception: { - values: [ - { - type: 'Error', - value: 'Test error', - }, - ], - }, -}; - -describe('no-code init', () => { - afterAll(() => { - cleanupChildProcesses(); - }); - - describe('--import', () => { - test('ESM', async () => { - await createRunner(__dirname, 'app.mjs') - .withFlags('--import=@sentry/node/init') - .withMockSentryServer() - .expect({ event: EVENT }) - .start() - .completed(); - }); - }); -}); diff --git a/dev-packages/node-integration-tests/utils/runner/createRunner.ts b/dev-packages/node-integration-tests/utils/runner/createRunner.ts index b3579005e0b2..bd237a36cae7 100644 --- a/dev-packages/node-integration-tests/utils/runner/createRunner.ts +++ b/dev-packages/node-integration-tests/utils/runner/createRunner.ts @@ -658,8 +658,7 @@ function log(...args: unknown[]): void { /** * Extracts the preloaded module paths from Node flags, accepting both the * two-element form (`--import foo`, e.g. `withInstrument`) and the single-element - * form (`--import=foo`, e.g. `withFlags('--import=@sentry/node/init')` in - * `suites/no-code/test.ts`). + * form (`--import=foo`, e.g. `withFlags('--import=./instrument.mjs')`). */ function getPreloadPaths(flags: readonly string[]): string[] { const paths: string[] = []; @@ -692,9 +691,8 @@ function getPreloadPaths(flags: readonly string[]): string[] { * * The flag name is no longer a usable signal, since CJS instrument files are preloaded * with `--import` too (`--require` re-runs the preload on Node's module loader thread). - * The extension is: `.mjs` and extensionless package specifiers such as - * `@sentry/node/init` resolve as ESM, while `.cjs`, `.js` and `.ts` are all CommonJS - * here because the test package sets no `"type"`. + * The extension is: `.mjs` and extensionless package specifiers resolve as ESM, while + * `.cjs`, `.js` and `.ts` are all CommonJS here because the test package sets no `"type"`. * * The CJS loader stays on `--require`: any `--import` makes Node resolve the entry point * through the ESM loader, which rejects the `.ts` scenarios that `tsx/cjs` handles. Unlike diff --git a/packages/node/package.json b/packages/node/package.json index dacb0b46da12..9a5cb451c0cb 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -32,22 +32,6 @@ "default": "./build/import-hook.mjs" } }, - "./init": { - "import": { - "default": "./build/esm/init.js" - }, - "require": { - "default": "./build/cjs/init.js" - } - }, - "./preload": { - "import": { - "default": "./build/esm/preload.js" - }, - "require": { - "default": "./build/cjs/preload.js" - } - }, "./vite": { "import": { "types": "./build/types/bundler-plugin/vite.d.ts", diff --git a/packages/node/rollup.npm.config.mjs b/packages/node/rollup.npm.config.mjs index ae6deb2ea370..45f7f66a6df4 100644 --- a/packages/node/rollup.npm.config.mjs +++ b/packages/node/rollup.npm.config.mjs @@ -23,8 +23,6 @@ export default [ makeBaseNPMConfig({ entrypoints: [ 'src/index.ts', - 'src/init.ts', - 'src/preload.ts', // Combined Sentry bundler plugins + orchestrion code transform, exposed // via the `@sentry/node/{vite,rollup,webpack,esbuild}` subpath exports. 'src/bundler-plugin/vite.ts', diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index b99de0e2295a..b541679d45d8 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -58,7 +58,7 @@ export { getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegrations, } from './sdk'; -export { initOpenTelemetry, preloadOpenTelemetry } from './sdk/initOtel'; +export { initOpenTelemetry } from './sdk/initOtel'; export { getAutoPerformanceIntegrations } from './integrations/tracing'; export type { NodeOptions, OpenTelemetryServerRuntimeOptions } from './types'; diff --git a/packages/node/src/init.ts b/packages/node/src/init.ts deleted file mode 100644 index acedff109ce1..000000000000 --- a/packages/node/src/init.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { init } from './sdk'; - -/** - * The @sentry/node/init export can be used with the node --import arg to initialize the SDK entirely via - * environment variables. - * - * > SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 SENTRY_TRACES_SAMPLE_RATE=1.0 node --import=@sentry/node/init app.mjs - */ -init(); diff --git a/packages/node/src/integrations/http/index.ts b/packages/node/src/integrations/http/index.ts index 610680874fa4..6f5bd9094181 100644 --- a/packages/node/src/integrations/http/index.ts +++ b/packages/node/src/integrations/http/index.ts @@ -154,10 +154,6 @@ interface HttpOptions { }; } -export const instrumentSentryHttp = Object.assign(instrumentHttpOutgoingRequests, { - id: `${INTEGRATION_NAME}.sentry`, -}); - /** * The http integration instruments Node's internal http and https modules. * It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span. diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 8f87b0e69d9b..7d03753d5ebe 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -24,7 +24,6 @@ import { tediousIntegration, vercelAiIntegration, } from '@sentry/server-utils/orchestrion'; -import { instrumentSentryHttp } from '../http'; import { fastifyIntegration } from './fastify'; import { redisIntegration } from './redis'; @@ -61,11 +60,3 @@ export function getAutoPerformanceIntegrations(): Integration[] { firebaseIntegration(), ]; } - -/** - * Get a list of methods to instrument OTEL, when preload instrumentation. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => void) & { id: string })[] { - return [instrumentSentryHttp]; -} diff --git a/packages/node/src/preload.ts b/packages/node/src/preload.ts deleted file mode 100644 index 575a0248ad76..000000000000 --- a/packages/node/src/preload.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { envToBool } from '@sentry/core'; -import { preloadOpenTelemetry } from './sdk/initOtel'; - -const debug = envToBool(process.env.SENTRY_DEBUG); -const integrationsStr = process.env.SENTRY_PRELOAD_INTEGRATIONS; - -const integrations = integrationsStr ? integrationsStr.split(',').map(integration => integration.trim()) : undefined; - -/** - * The @sentry/node/preload export can be used with the node --import arg to preload the OTEL - * instrumentation, without initializing the Sentry SDK. - * - * This is useful if you cannot initialize the SDK immediately, but still want to preload the instrumentation, - * e.g. if you have to load the DSN from somewhere else. - * - * You can configure this in two ways via environment variables: - * - `SENTRY_DEBUG` to enable debug logging - * - `SENTRY_PRELOAD_INTEGRATIONS` to preload specific integrations - e.g. `SENTRY_PRELOAD_INTEGRATIONS="Http,Express"` - */ -preloadOpenTelemetry({ debug, integrations }); diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 8a155189e09e..d1516c256592 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -10,7 +10,6 @@ import { SentryTracerProvider, } from '@sentry/opentelemetry'; import { DEBUG_BUILD } from '../debug-build'; -import { getOpenTelemetryInstrumentationToPreload } from '../integrations/tracing'; // The global registry of @opentelemetry/api 1.x, shared across all copies of the package const OTEL_API_GLOBAL_KEY = Symbol.for('opentelemetry.js.api.1'); @@ -79,47 +78,6 @@ export function initOpenTelemetry(client: NodeClient): void { client.traceProvider = provider; } -interface NodePreloadOptions { - debug?: boolean; - integrations?: string[]; -} - -/** - * Preload OpenTelemetry for Node. - * This can be used to preload instrumentation early, but set up Sentry later. - * By preloading the OTEL instrumentation wrapping still happens early enough that everything works. - */ -export function preloadOpenTelemetry(options: NodePreloadOptions = {}): void { - const { debug } = options; - - if (debug) { - coreDebug.enable(); - } - - // 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(); - - if (debug) { - coreDebug.log(`[Sentry] Preloaded ${fn.id} instrumentation`); - } - }); -} - -function getPreloadMethods(integrationNames?: string[]): ((() => void) & { id: string })[] { - const instruments = getOpenTelemetryInstrumentationToPreload(); - - if (!integrationNames) { - return instruments; - } - - // We match exact matches of instrumentation, but also match prefixes, e.g. "Fastify.v5" will match "Fastify" - return instruments.filter(instrumentation => { - const id = instrumentation.id; - return integrationNames.some(integrationName => id === integrationName || id.startsWith(`${integrationName}.`)); - }); -} - /** * Backfill Sentry span data (op, source, name, status) from OpenTelemetry semantic attributes. * diff --git a/packages/node/test/sdk/preload.test.ts b/packages/node/test/sdk/preload.test.ts deleted file mode 100644 index 65e61287bd33..000000000000 --- a/packages/node/test/sdk/preload.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { debug } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { resetGlobals } from '../helpers/mockSdkInit'; - -describe('preload', () => { - beforeEach(() => { - // Mock this to prevent conflicts with other tests - vi.mock('../../src/integrations/tracing', async (importOriginal: () => Promise>) => { - const actual = await importOriginal(); - return { - ...actual, - getOpenTelemetryInstrumentationToPreload: () => [ - Object.assign(vi.fn(), { id: 'Http.sentry' }), - Object.assign(vi.fn(), { id: 'Http' }), - Object.assign(vi.fn(), { id: 'Express' }), - Object.assign(vi.fn(), { id: 'Graphql' }), - ], - }; - }); - }); - - afterEach(() => { - debug.disable(); - resetGlobals(); - - delete process.env.SENTRY_DEBUG; - delete process.env.SENTRY_PRELOAD_INTEGRATIONS; - - vi.resetModules(); - }); - - it('works without env vars', async () => { - const logSpy = vi.spyOn(console, 'log'); - - await import('../../src/preload'); - - expect(logSpy).toHaveBeenCalledTimes(0); - }); - - it('works with SENTRY_DEBUG set', async () => { - const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - // We want to swallow these logs - vi.spyOn(console, 'debug').mockImplementation(() => {}); - - process.env.SENTRY_DEBUG = '1'; - - await import('../../src/preload'); - - expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http.sentry instrumentation'); - expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http instrumentation'); - expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Express instrumentation'); - expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Graphql instrumentation'); - }); - - it('works with SENTRY_DEBUG & SENTRY_PRELOAD_INTEGRATIONS set', async () => { - const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - // We want to swallow these logs - vi.spyOn(console, 'debug').mockImplementation(() => {}); - - process.env.SENTRY_DEBUG = '1'; - process.env.SENTRY_PRELOAD_INTEGRATIONS = 'Http,Express'; - - await import('../../src/preload'); - - expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http.sentry instrumentation'); - expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http instrumentation'); - expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Express instrumentation'); - expect(logSpy).not.toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Graphql instrumentation'); - }); -});