diff --git a/MIGRATION.md b/MIGRATION.md index af7724fa7dbd..001133cfd3b1 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -319,6 +319,7 @@ Affected SDKs: `@sentry/cloudflare`. - The internal, deprecated `addAutoIpAddressToUser` export was removed. - The `createSpanEnvelope` function and the `SpanEnvelope` / `SpanItem` types were removed. They existed only to send standalone (v1) spans as their own segment envelope, which the SDK no longer does. Standalone spans are gone; spans are sent either on their transaction or, with span streaming, as streamed spans (`StreamedSpanEnvelope`). +- The `disableInstrumentationWarnings` option and the `MissingInstrumentationContext` type were removed. Now that instrumentation is channel-based, the SDK can no longer detect the "you imported a framework before `Sentry.init()`" case, so the warning it gated and the context it attached no longer exist. - The deprecated `sendDefaultPii` option was removed. Use [`dataCollection`](#senddefaultpii-is-replaced-by-datacollection) instead. - The `_experiments.enableMetrics` and `_experiments.beforeSendMetric` options were removed, use the top-level `enableMetrics` and `beforeSendMetric` options instead. @@ -411,6 +412,8 @@ Sentry.init({ - The deprecated `prismaInstrumentation` option was removed. It was no longer used, as Prisma works out of the box. - 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/loader` entry point was removed. Use `node --import @sentry/node/import` instead. - (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`. diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/.gitignore b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/.gitignore deleted file mode 100644 index 1521c8b7652b..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json deleted file mode 100644 index 84afe281c642..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "node-express-incorrect-instrumentation", - "version": "1.0.0", - "private": true, - "scripts": { - "build": "tsc", - "start": "node dist/app.js", - "test": "playwright test", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && pnpm build", - "test:assert": "pnpm test" - }, - "dependencies": { - "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@trpc/server": "10.45.4", - "@trpc/client": "10.45.4", - "@types/express": "4.17.17", - "@types/node": "^18.19.1", - "express": "^4.21.2", - "typescript": "~5.0.0", - "zod": "~3.22.4" - }, - "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-incorrect-instrumentation/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/playwright.config.mjs deleted file mode 100644 index 31f2b913b58b..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/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-incorrect-instrumentation/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/src/app.ts deleted file mode 100644 index 166f13822a1b..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/src/app.ts +++ /dev/null @@ -1,40 +0,0 @@ -declare global { - namespace globalThis { - var transactionIds: string[]; - } -} - -import express from 'express'; - -const app = express(); -const port = 3030; - -// import and init sentry last for missing instrumentation -import * as Sentry from '@sentry/node'; -Sentry.init({ - traceLifecycle: 'static', - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.E2E_TEST_DSN, - includeLocalVariables: true, - debug: !!process.env.DEBUG, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1, -}); - -app.get('/test-exception/:id', function (req, _res) { - throw new Error(`This is an exception with id ${req.params.id}`); -}); - -Sentry.setupExpressErrorHandler(app); - -// @ts-ignore -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'); -}); - -app.listen(port, () => { - console.log(`Example app listening on port ${port}`); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/start-event-proxy.mjs deleted file mode 100644 index 3276781a442a..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'node-express', -}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/tests/instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/tests/instrumentation.test.ts deleted file mode 100644 index f562cb3a5837..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/tests/instrumentation.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; - -test('Sends correct context when instrumentation was set up incorrectly', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-express', event => { - return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; - }); - - await fetch(`${baseURL}/test-exception/123`); - - const errorEvent = await errorEventPromise; - - expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123'); - - expect(errorEvent.contexts?.missing_instrumentation).toEqual({ - package: 'express', - 'javascript.is_cjs': true, - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/tsconfig.json deleted file mode 100644 index 2887ec11a81d..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "types": ["node"], - "esModuleInterop": true, - "lib": ["es2018"], - "strict": true, - "outDir": "dist", - "skipLibCheck": true - }, - "include": ["src/**/*.ts"] -} diff --git a/dev-packages/rollup-utils/code/importHookTemplate.js b/dev-packages/rollup-utils/code/importHookTemplate.js new file mode 100644 index 000000000000..590a81734c10 --- /dev/null +++ b/dev-packages/rollup-utils/code/importHookTemplate.js @@ -0,0 +1 @@ +import '@sentry/server-utils/orchestrion/import-hook'; diff --git a/dev-packages/rollup-utils/npmHelpers.mjs b/dev-packages/rollup-utils/npmHelpers.mjs index be2bfa675886..41d6cbc2a354 100644 --- a/dev-packages/rollup-utils/npmHelpers.mjs +++ b/dev-packages/rollup-utils/npmHelpers.mjs @@ -282,3 +282,53 @@ export function makeOtelLoaders(outputFolder, hookVariant, options = {}) { }, ]); } + +/** + * Emits the `@sentry//import` entry (`build/import-hook.mjs`) as part of the rollup build, + * used as `node --import @sentry//import app.js`. The generated hook imports + * `@sentry/server-utils/orchestrion/import-hook`, which registers the orchestrion + * diagnostics-channel injection, so the consuming package must declare `@sentry/server-utils` as a + * dependency. + * + * @param {string} outputFolder Build output folder. + */ +export function makeOrchestrionLoader(outputFolder) { + const expectedImportHookLocation = `${outputFolder}/import-hook.mjs`; + const foundImportHookExport = Object.keys(packageDotJSON.exports ?? {}).some(key => { + return packageDotJSON?.exports?.[key]?.import?.default === expectedImportHookLocation; + }); + if (!foundImportHookExport) { + throw new Error( + `You used the makeOrchestrionLoader() rollup utility without specifying the import hook inside \`exports[something].import.default\`. Please add "${expectedImportHookLocation}" as a value there (maybe check for typos - it needs to be "${expectedImportHookLocation}" exactly).`, + ); + } + + const requiredDep = '@sentry/server-utils'; + const foundRequiredDep = + Object.keys(packageDotJSON.dependencies ?? {}).some(key => { + return key === requiredDep; + }) || + Object.keys(packageDotJSON.devDependencies ?? {}).some(key => { + return key === requiredDep; + }); + + if (!foundRequiredDep) { + throw new Error( + `You used the makeOrchestrionLoader() rollup utility but didn't specify the "${requiredDep}" dependency in ${path.resolve( + process.cwd(), + 'package.json', + )}. Please add it to the dependencies.`, + ); + } + + return defineConfig([ + { + input: path.join(__dirname, 'code', 'importHookTemplate.js'), + external: /.*/, + output: { + format: 'esm', + file: path.join(outputFolder, 'import-hook.mjs'), + }, + }, + ]); +} diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index cb157ec9007e..245c0993459d 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -45,7 +45,6 @@ export { flush, fsIntegration, functionToStringIntegration, - generateInstrumentOnce, genericPoolIntegration, getActiveSpan, getAutoPerformanceIntegrations, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 30f597757b40..faa62b15b6d8 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -15,7 +15,6 @@ export { getClient, isInitialized, isEnabled, - generateInstrumentOnce, getCurrentScope, getGlobalScope, getIsolationScope, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 4a6c25eed27d..224a61251110 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -38,7 +38,6 @@ export { getClient, isInitialized, isEnabled, - generateInstrumentOnce, getCurrentScope, getGlobalScope, getIsolationScope, diff --git a/packages/core/src/shared-exports.ts b/packages/core/src/shared-exports.ts index 700e65d2a974..2d75b93b311e 100644 --- a/packages/core/src/shared-exports.ts +++ b/packages/core/src/shared-exports.ts @@ -351,7 +351,6 @@ export type { CultureContext, TraceContext, CloudResourceContext, - MissingInstrumentationContext, } from './types/context'; export type { DataCategory } from './types/datacategory'; export type { DsnComponents, DsnLike, DsnProtocol } from './types/dsn'; diff --git a/packages/core/src/tracing/spans/scopeContextAttributes.ts b/packages/core/src/tracing/spans/scopeContextAttributes.ts index c7267625b8bc..63b468fea922 100644 --- a/packages/core/src/tracing/spans/scopeContextAttributes.ts +++ b/packages/core/src/tracing/spans/scopeContextAttributes.ts @@ -3,7 +3,7 @@ import type { Contexts } from '../../types/context'; /** * Convert known scope contexts set by SDK integrations to span attributes. * Only maps context keys that are relevant to browser SDKs. - * Server-only contexts (aws, gcp, missing_instrumentation, trpc) are handled + * Server-only contexts (aws, gcp, trpc) are handled * by processSegmentSpan hooks in their respective packages. */ export function scopeContextsToSpanAttributes(contexts: Contexts): Record { diff --git a/packages/core/src/types/context.ts b/packages/core/src/types/context.ts index 9e52ff9d6834..d09f4a6a5d6f 100644 --- a/packages/core/src/types/context.ts +++ b/packages/core/src/types/context.ts @@ -125,11 +125,6 @@ export interface ProfileContext extends Record { profile_id: string; } -export interface MissingInstrumentationContext extends Record { - package: string; - ['javascript.is_cjs']?: boolean; -} - /** * Used to buffer flag evaluation data on the current scope and attach it to * error events. `values` should be initialized as empty ([]), and modifying diff --git a/packages/core/src/types/options.ts b/packages/core/src/types/options.ts index e5f938082509..63f2a3c1e09f 100644 --- a/packages/core/src/types/options.ts +++ b/packages/core/src/types/options.ts @@ -65,12 +65,6 @@ export interface ServerRuntimeOptions { */ includeServerName?: boolean; - /** - * By default, the SDK will try to identify problems with your instrumentation setup and warn you about it. - * If you want to disable these warnings, set this to `true`. - */ - disableInstrumentationWarnings?: boolean; - /** * Controls how many milliseconds to wait before shutting down. The default is 2 seconds. Setting this too low can cause * problems for sending events from command line applications. Setting it too diff --git a/packages/core/src/utils/worldwide.ts b/packages/core/src/utils/worldwide.ts index 396c98d952a2..45607ac4bd31 100644 --- a/packages/core/src/utils/worldwide.ts +++ b/packages/core/src/utils/worldwide.ts @@ -54,7 +54,6 @@ export type InternalGlobal = { * Keys are `error.stack` strings, values are the metadata. */ _sentryModuleMetadata?: Record; - _sentryEsmLoaderHookRegistered?: boolean; _sentryWrappedDepth?: number; /** * Orchestrion bundler and runtime detection. diff --git a/packages/elysia/src/index.ts b/packages/elysia/src/index.ts index 715c2484296e..32dbcb9f5957 100644 --- a/packages/elysia/src/index.ts +++ b/packages/elysia/src/index.ts @@ -16,7 +16,6 @@ export { getClient, isInitialized, isEnabled, - generateInstrumentOnce, getCurrentScope, getGlobalScope, getIsolationScope, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index d07490563212..3afeb1b9dc7d 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -15,7 +15,6 @@ export { getClient, isInitialized, isEnabled, - generateInstrumentOnce, getCurrentScope, getGlobalScope, getIsolationScope, diff --git a/packages/node/package.json b/packages/node/package.json index bcee960ba043..eb5fba284ec7 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -32,11 +32,6 @@ "default": "./build/import-hook.mjs" } }, - "./loader": { - "import": { - "default": "./build/loader-hook.mjs" - } - }, "./init": { "import": { "default": "./build/esm/init.js" @@ -100,7 +95,6 @@ "dependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.9.0", - "@opentelemetry/instrumentation": "^0.220.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.67.0", "@sentry/opentelemetry": "10.67.0", @@ -141,8 +135,7 @@ "{projectRoot}/build/cjs", "{projectRoot}/build/npm/esm", "{projectRoot}/build/npm/cjs", - "{projectRoot}/build/import-hook.mjs", - "{projectRoot}/build/loader-hook.mjs" + "{projectRoot}/build/import-hook.mjs" ] } } diff --git a/packages/node/rollup.npm.config.mjs b/packages/node/rollup.npm.config.mjs index 650a875fe6ad..ae6deb2ea370 100644 --- a/packages/node/rollup.npm.config.mjs +++ b/packages/node/rollup.npm.config.mjs @@ -1,5 +1,5 @@ import replace from '@rollup/plugin-replace'; -import { makeBaseNPMConfig, makeNPMConfigVariants, makeOtelLoaders } from '@sentry-internal/rollup-utils'; +import { makeBaseNPMConfig, makeNPMConfigVariants, makeOrchestrionLoader } from '@sentry-internal/rollup-utils'; import { createWorkerCodeBuilder } from './rollup.anr-worker.config.mjs'; const [anrWorkerConfig, getAnrBase64Code] = createWorkerCodeBuilder( @@ -13,12 +13,9 @@ const [localVariablesWorkerConfig, getLocalVariablesBase64Code] = createWorkerCo ); export default [ - // `injectDiagnosticsChannel` makes the generated `@sentry/node/import` hook - // also register the diagnostics-channel injection, so `node --import - // @sentry/node/import app.js` injects the channels unconditionally (they are - // only subscribed to when the app opts in via - // `experimentalUseDiagnosticsChannelInjection()`). - ...makeOtelLoaders('./build', 'otel', { injectDiagnosticsChannel: true }), + // The `@sentry/node/import` entry (`node --import @sentry/node/import app.js`), which registers + // the orchestrion diagnostics-channel injection before the app loads. + ...makeOrchestrionLoader('./build'), // The workers need to be built first since their output is copied into the main bundle. anrWorkerConfig, localVariablesWorkerConfig, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index bf8dd5d7533e..c120aca98393 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -203,7 +203,6 @@ export { createGetModuleFromFilename } from './utils/module'; export { httpServerIntegration } from './integrations/http/httpServerIntegration'; export { httpServerSpansIntegration } from './integrations/http/httpServerSpansIntegration'; export { processSessionIntegration } from './integrations/processSession'; -export { generateInstrumentOnce } from './otel/instrument'; export { NodeClient } from './sdk/client'; // eslint-disable-next-line typescript/no-deprecated export { anrIntegration, disableAnrDetectionForCallback } from './integrations/anr'; diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index 438d95128e2f..e3d3b2ea6768 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -1,7 +1,6 @@ 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 type { ClientRequest, IncomingMessage, ServerResponse } from 'node:http'; import type { HttpClientRequest, HttpIncomingMessage, HttpInstrumentationOptions, Span } from '@sentry/core'; import { @@ -75,65 +74,64 @@ interface OutgoingHttpRequestInstrumentationOptions { ) => void; } -export type SentryHttpInstrumentationOptions = InstrumentationConfig & - OutgoingHttpRequestInstrumentationOptions & { - // All options below do not do anything anymore in this instrumentation, and will be removed in the future. - // They are only kept here for backwards compatibility - the respective functionality is now handled by the httpServerIntegration/httpServerSpansIntegration. - - /** - * @deprecated This no longer does anything. - */ - extractIncomingTraceFromHeader?: boolean; - - /** - * @deprecated This no longer does anything. - */ - ignoreStaticAssets?: boolean; - - /** - * @deprecated This no longer does anything. - */ - disableIncomingRequestSpans?: boolean; - - /** - * @deprecated This no longer does anything. - */ - ignoreSpansForIncomingRequests?: (urlPath: string, request: IncomingMessage) => boolean; - - /** - * @deprecated This no longer does anything. - */ - ignoreIncomingRequestBody?: (url: string, request: http.RequestOptions) => boolean; - - /** - * @deprecated This no longer does anything. - */ - maxIncomingRequestBodySize?: 'none' | 'small' | 'medium' | 'always'; - - /** - * @deprecated This no longer does anything. - */ - trackIncomingRequestsAsSessions?: boolean; - - /** - * @deprecated This no longer does anything. - */ - instrumentation?: { - requestHook?: (span: Span, req: ClientRequest | IncomingMessage) => void; - responseHook?: (span: Span, response: IncomingMessage | ServerResponse) => void; - applyCustomAttributesOnSpan?: ( - span: Span, - request: ClientRequest | IncomingMessage, - response: IncomingMessage | ServerResponse, - ) => void; - }; - - /** - * @deprecated This no longer does anything. - */ - sessionFlushingDelayMS?: number; +export type SentryHttpInstrumentationOptions = OutgoingHttpRequestInstrumentationOptions & { + // All options below do not do anything anymore in this instrumentation, and will be removed in the future. + // They are only kept here for backwards compatibility - the respective functionality is now handled by the httpServerIntegration/httpServerSpansIntegration. + + /** + * @deprecated This no longer does anything. + */ + extractIncomingTraceFromHeader?: boolean; + + /** + * @deprecated This no longer does anything. + */ + ignoreStaticAssets?: boolean; + + /** + * @deprecated This no longer does anything. + */ + disableIncomingRequestSpans?: boolean; + + /** + * @deprecated This no longer does anything. + */ + ignoreSpansForIncomingRequests?: (urlPath: string, request: IncomingMessage) => boolean; + + /** + * @deprecated This no longer does anything. + */ + ignoreIncomingRequestBody?: (url: string, request: http.RequestOptions) => boolean; + + /** + * @deprecated This no longer does anything. + */ + maxIncomingRequestBodySize?: 'none' | 'small' | 'medium' | 'always'; + + /** + * @deprecated This no longer does anything. + */ + trackIncomingRequestsAsSessions?: boolean; + + /** + * @deprecated This no longer does anything. + */ + instrumentation?: { + requestHook?: (span: Span, req: ClientRequest | IncomingMessage) => void; + responseHook?: (span: Span, response: IncomingMessage | ServerResponse) => void; + applyCustomAttributesOnSpan?: ( + span: Span, + request: ClientRequest | IncomingMessage, + response: IncomingMessage | ServerResponse, + ) => void; }; + /** + * @deprecated This no longer does anything. + */ + sessionFlushingDelayMS?: number; +}; + /** * This instruments the http modules for outgoing requests. * It uses the diagnostics channel if available, otherwise it falls back to monkey-patching. diff --git a/packages/node/src/integrations/tracing/express.ts b/packages/node/src/integrations/tracing/express.ts index d7212ac5b009..7590c8c6bfd6 100644 --- a/packages/node/src/integrations/tracing/express.ts +++ b/packages/node/src/integrations/tracing/express.ts @@ -1,4 +1,3 @@ -import { ensureIsWrapped } from '../../utils/ensureIsWrapped'; import { setupExpressErrorHandler as coreSetupExpressErrorHandler, type ExpressHandlerOptions } from '@sentry/core'; export { expressErrorHandler } from '@sentry/core'; @@ -8,5 +7,4 @@ export function setupExpressErrorHandler( options?: ExpressHandlerOptions, ): void { coreSetupExpressErrorHandler(app, options); - ensureIsWrapped(app.use, 'express'); } diff --git a/packages/node/src/integrations/tracing/hapi/index.ts b/packages/node/src/integrations/tracing/hapi/index.ts index 214a70ee74c9..6de0344f27d8 100644 --- a/packages/node/src/integrations/tracing/hapi/index.ts +++ b/packages/node/src/integrations/tracing/hapi/index.ts @@ -1,5 +1,4 @@ import { captureException, debug, getDefaultIsolationScope, getIsolationScope, SDK_VERSION } from '@sentry/core'; -import { ensureIsWrapped } from '../../../utils/ensureIsWrapped'; import { DEBUG_BUILD } from '../../../debug-build'; import type { Request, RequestEvent, Server } from './types'; @@ -64,5 +63,4 @@ export const hapiErrorPlugin = { */ export async function setupHapiErrorHandler(server: Server): Promise { await server.register(hapiErrorPlugin); - ensureIsWrapped(server.register, 'hapi'); } diff --git a/packages/node/src/integrations/tracing/koa/index.ts b/packages/node/src/integrations/tracing/koa/index.ts index 7c467df9b6f8..88532ffe331b 100644 --- a/packages/node/src/integrations/tracing/koa/index.ts +++ b/packages/node/src/integrations/tracing/koa/index.ts @@ -1,5 +1,4 @@ import { captureException } from '@sentry/core'; -import { ensureIsWrapped } from '../../../utils/ensureIsWrapped'; /** * Add an Koa error handler to capture errors to Sentry. @@ -38,6 +37,4 @@ export const setupKoaErrorHandler = (app: { use: (arg0: (ctx: any, next: any) => throw error; } }); - - ensureIsWrapped(app.use, 'koa'); }; diff --git a/packages/node/src/integrations/tracing/redis/vendored/types.ts b/packages/node/src/integrations/tracing/redis/vendored/types.ts index 571c090fb31a..167cc448d374 100644 --- a/packages/node/src/integrations/tracing/redis/vendored/types.ts +++ b/packages/node/src/integrations/tracing/redis/vendored/types.ts @@ -4,29 +4,12 @@ * * NOTICE from the Sentry authors: * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/instrumentation-redis-v0.62.0/packages/instrumentation-redis - * - Upstream version: @opentelemetry/instrumentation-redis@0.62.0 and @opentelemetry/instrumentation-ioredis@0.62.0 + * - Upstream version: @opentelemetry/instrumentation-ioredis@0.62.0 * - Minor TypeScript adjustments for this repository's compiler settings */ /* eslint-disable -- vendored @opentelemetry/instrumentation-redis */ import type { Span } from '@sentry/core'; -import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; - -// ---- redis types ---- - -/** - * Function that can be used to add custom attributes to span on response from redis server - */ -export interface RedisResponseCustomAttributeFunction { - (span: Span, cmdName: string, cmdArgs: Array, response: unknown): void; -} - -export interface RedisInstrumentationConfig extends InstrumentationConfig { - /** Function for adding custom attributes on db response */ - responseHook?: RedisResponseCustomAttributeFunction; -} - -// ---- ioredis types ---- export type CommandArgs = Array; @@ -36,8 +19,3 @@ export type CommandArgs = Array; export interface IORedisResponseCustomAttributeFunction { (span: Span, cmdName: string, cmdArgs: CommandArgs, response: unknown): void; } - -export interface IORedisInstrumentationConfig extends InstrumentationConfig { - /** Function for adding custom attributes on db response */ - responseHook?: IORedisResponseCustomAttributeFunction; -} diff --git a/packages/node/src/otel/instrument.ts b/packages/node/src/otel/instrument.ts deleted file mode 100644 index 6f19190afe91..000000000000 --- a/packages/node/src/otel/instrument.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { type Instrumentation, registerInstrumentations } from '@opentelemetry/instrumentation'; - -/** Exported only for tests. */ -export const INSTRUMENTED: Record = {}; - -export function generateInstrumentOnce< - Options, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - InstrumentationClass extends new (...args: any[]) => Instrumentation, ->( - name: string, - instrumentationClass: InstrumentationClass, - optionsCallback: (options: Options) => ConstructorParameters[0], -): ((options: Options) => InstanceType) & { id: string }; -export function generateInstrumentOnce< - Options = unknown, - InstrumentationInstance extends Instrumentation = Instrumentation, ->( - name: string, - creator: (options?: Options) => InstrumentationInstance, -): ((options?: Options) => InstrumentationInstance) & { id: string }; -/** - * Instrument an OpenTelemetry instrumentation once. - * This will skip running instrumentation again if it was already instrumented. - */ -export function generateInstrumentOnce( - name: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - creatorOrClass: (new (...args: any[]) => Instrumentation) | ((options?: Options) => Instrumentation), - optionsCallback?: (options: Options) => unknown, -): ((options: Options) => Instrumentation) & { id: string } { - if (optionsCallback) { - return _generateInstrumentOnceWithOptions( - name, - creatorOrClass as new (...args: unknown[]) => Instrumentation, - optionsCallback, - ); - } - - return _generateInstrumentOnce(name, creatorOrClass as (options?: Options) => Instrumentation); -} - -// The plain version without handling of options -// Should not be used with custom options that are mutated in the creator! -function _generateInstrumentOnce( - name: string, - creator: (options?: Options) => InstrumentationInstance, -): ((options?: Options) => InstrumentationInstance) & { id: string } { - return Object.assign( - (options?: Options) => { - const instrumented = INSTRUMENTED[name] as InstrumentationInstance | undefined; - if (instrumented) { - // If options are provided, ensure we update them - if (options) { - instrumented.setConfig(options); - } - return instrumented; - } - - const instrumentation = creator(options); - INSTRUMENTED[name] = instrumentation; - - registerInstrumentations({ - instrumentations: [instrumentation], - }); - - return instrumentation; - }, - { id: name }, - ); -} - -// This version handles options properly -function _generateInstrumentOnceWithOptions< - Options, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - InstrumentationClass extends new (...args: any[]) => Instrumentation, ->( - name: string, - instrumentationClass: InstrumentationClass, - optionsCallback: (options: Options) => ConstructorParameters[0], -): ((options: Options) => InstanceType) & { id: string } { - return Object.assign( - (_options: Options) => { - const options = optionsCallback(_options); - - const instrumented = INSTRUMENTED[name] as InstanceType | undefined; - if (instrumented) { - // Ensure we update options - instrumented.setConfig(options); - return instrumented; - } - - const instrumentation = new instrumentationClass(options) as InstanceType; - INSTRUMENTED[name] = instrumentation; - - registerInstrumentations({ - instrumentations: [instrumentation], - }); - - return instrumentation; - }, - { id: name }, - ); -} - -/** - * Ensure a given callback is called when the instrumentation is actually wrapping something. - * This can be used to ensure some logic is only called when the instrumentation is actually active. - * - * This function returns a function that can be invoked with a callback. - * This callback will either be invoked immediately - * (e.g. if the instrumentation was already wrapped, or if _wrap could not be patched), - * or once the instrumentation is actually wrapping something. - * - * Make sure to call this function right after adding the instrumentation, otherwise it may be too late! - * The returned callback can be used any time, and also multiple times. - */ -export function instrumentWhenWrapped(instrumentation: T): (callback: () => void) => void { - let isWrapped = false; - let callbacks: (() => void)[] = []; - - if (!hasWrap(instrumentation)) { - isWrapped = true; - } else { - const originalWrap = instrumentation['_wrap']; - - instrumentation['_wrap'] = (...args: Parameters) => { - isWrapped = true; - callbacks.forEach(callback => callback()); - callbacks = []; - return originalWrap(...args); - }; - } - - const registerCallback = (callback: () => void): void => { - if (isWrapped) { - callback(); - } else { - callbacks.push(callback); - } - }; - - return registerCallback; -} - -function hasWrap( - instrumentation: T, -): instrumentation is T & { _wrap: (...args: unknown[]) => unknown } { - return typeof (instrumentation as T & { _wrap?: (...args: unknown[]) => unknown })['_wrap'] === 'function'; -} diff --git a/packages/node/src/utils/createMissingInstrumentationContext.ts b/packages/node/src/utils/createMissingInstrumentationContext.ts deleted file mode 100644 index 7e9b9c69db68..000000000000 --- a/packages/node/src/utils/createMissingInstrumentationContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { MissingInstrumentationContext } from '@sentry/core'; - -export const createMissingInstrumentationContext = (pkg: string): MissingInstrumentationContext => { - let isCjs: boolean; - /*! rollup-include-cjs-only */ - isCjs = true; - /*! rollup-include-cjs-only-end */ - /*! rollup-include-esm-only */ - isCjs = false; - /*! rollup-include-esm-only-end */ - - return { - package: pkg, - 'javascript.is_cjs': isCjs, - }; -}; diff --git a/packages/node/src/utils/ensureIsWrapped.ts b/packages/node/src/utils/ensureIsWrapped.ts deleted file mode 100644 index 2de941805162..000000000000 --- a/packages/node/src/utils/ensureIsWrapped.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { isWrapped } from '@opentelemetry/instrumentation'; -import { - consoleSandbox, - getClient, - getOriginalFunction, - getGlobalScope, - hasSpansEnabled, - isEnabled, - type WrappedFunction, -} from '@sentry/core'; -import type { NodeClient } from '../sdk/client'; -import { createMissingInstrumentationContext } from './createMissingInstrumentationContext'; - -/** - * Checks and warns if a framework isn't wrapped by opentelemetry. - */ -export function ensureIsWrapped( - maybeWrappedFunction: unknown, - name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa', -): void { - const clientOptions = getClient()?.getOptions(); - if ( - !clientOptions?.disableInstrumentationWarnings && - !( - isWrapped(maybeWrappedFunction) || - typeof getOriginalFunction(maybeWrappedFunction as WrappedFunction) === 'function' - ) && - isEnabled() && - hasSpansEnabled(clientOptions) - ) { - consoleSandbox(() => { - /*! rollup-include-cjs-only */ - // eslint-disable-next-line no-console - console.warn( - `[Sentry] ${name} is not instrumented. This is likely because you required/imported ${name} before calling \`Sentry.init()\`.`, - ); - /*! rollup-include-cjs-only-end */ - - /*! rollup-include-esm-only */ - // eslint-disable-next-line no-console - console.warn( - `[Sentry] ${name} is not instrumented. Please make sure to initialize Sentry in a separate file that you \`--import\` when running node, see: https://docs.sentry.io/platforms/javascript/guides/${name}/install/esm/.`, - ); - /*! rollup-include-esm-only-end */ - }); - - getGlobalScope().setContext('missing_instrumentation', createMissingInstrumentationContext(name)); - } -} diff --git a/packages/node/test/utils/ensureIsWrapped.test.ts b/packages/node/test/utils/ensureIsWrapped.test.ts deleted file mode 100644 index c5d884329d99..000000000000 --- a/packages/node/test/utils/ensureIsWrapped.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { ensureIsWrapped } from '../../src/utils/ensureIsWrapped'; -import { cleanupOtel, mockSdkInit, resetGlobals } from '../helpers/mockSdkInit'; -import { markFunctionWrapped } from '@sentry/core'; - -const unwrappedFunction = () => {}; - -// We simulate a wrapped function -const wrappedfunction = Object.assign(() => {}, { - __wrapped: true, - __original: () => {}, - __unwrap: () => {}, -}); - -describe('ensureIsWrapped', () => { - afterEach(() => { - vi.restoreAllMocks(); - cleanupOtel(); - resetGlobals(); - }); - - it('warns when the method is unwrapped', () => { - const spyWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - mockSdkInit({ tracesSampleRate: 1 }); - - ensureIsWrapped(unwrappedFunction, 'express'); - - expect(spyWarn).toHaveBeenCalledTimes(1); - expect(spyWarn).toHaveBeenCalledWith( - '[Sentry] express is not instrumented. Please make sure to initialize Sentry in a separate file that you `--import` when running node, see: https://docs.sentry.io/platforms/javascript/guides/express/install/esm/.', - ); - }); - - it('does not warn when the method is wrapped', () => { - const spyWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - mockSdkInit({ tracesSampleRate: 1 }); - - ensureIsWrapped(wrappedfunction, 'express'); - - expect(spyWarn).toHaveBeenCalledTimes(0); - }); - - it('does not warn without a client', () => { - const spyWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - resetGlobals(); - - ensureIsWrapped(wrappedfunction, 'express'); - - expect(spyWarn).toHaveBeenCalledTimes(0); - }); - - it('does not warn without tracing', () => { - const spyWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - mockSdkInit({}); - - ensureIsWrapped(unwrappedFunction, 'express'); - - expect(spyWarn).toHaveBeenCalledTimes(0); - }); - - it('does not warn if disableInstrumentationWarnings=true', () => { - const spyWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - mockSdkInit({ tracesSampleRate: 1, disableInstrumentationWarnings: true }); - - ensureIsWrapped(unwrappedFunction, 'express'); - - expect(spyWarn).toHaveBeenCalledTimes(0); - }); - - it('does not warn when the method is wrapped by @sentry/core', () => { - const spyWarn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - - mockSdkInit({ tracesSampleRate: 1 }); - - function original() { - return 'i am original'; - } - - function sentryWrapped() { - return original(); - } - - markFunctionWrapped(sentryWrapped, original); - - ensureIsWrapped(sentryWrapped, 'express'); - - expect(spyWarn).toHaveBeenCalledTimes(0); - }); -}); diff --git a/packages/node/test/utils/instrument.test.ts b/packages/node/test/utils/instrument.test.ts deleted file mode 100644 index 0b9e1b6c727a..000000000000 --- a/packages/node/test/utils/instrument.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { describe, expect, test, vi } from 'vitest'; -import { instrumentWhenWrapped } from '../../src/otel/instrument'; - -describe('instrumentWhenWrapped', () => { - test('calls callback immediately when instrumentation has no _wrap method', () => { - const callback = vi.fn(); - const instrumentation = {} as any; - - const registerCallback = instrumentWhenWrapped(instrumentation); - registerCallback(callback); - - expect(callback).toHaveBeenCalledTimes(1); - }); - - test('calls callback when _wrap is called', () => { - const callback = vi.fn(); - const originalWrap = vi.fn(); - const instrumentation = { - _wrap: originalWrap, - } as any; - - const registerCallback = instrumentWhenWrapped(instrumentation); - registerCallback(callback); - - // Callback should not be called yet - expect(callback).not.toHaveBeenCalled(); - - // Call _wrap - instrumentation._wrap(); - - // Callback should be called once - expect(callback).toHaveBeenCalledTimes(1); - expect(originalWrap).toHaveBeenCalled(); - }); - - test('calls multiple callbacks when _wrap is called', () => { - const callback1 = vi.fn(); - const callback2 = vi.fn(); - const originalWrap = vi.fn(); - const instrumentation = { - _wrap: originalWrap, - } as any; - - const registerCallback = instrumentWhenWrapped(instrumentation); - registerCallback(callback1); - registerCallback(callback2); - - // Callbacks should not be called yet - expect(callback1).not.toHaveBeenCalled(); - expect(callback2).not.toHaveBeenCalled(); - - // Call _wrap - instrumentation._wrap(); - - // Both callbacks should be called once - expect(callback1).toHaveBeenCalledTimes(1); - expect(callback2).toHaveBeenCalledTimes(1); - expect(originalWrap).toHaveBeenCalled(); - }); - - test('calls callback immediately if already wrapped', () => { - const callback = vi.fn(); - const originalWrap = vi.fn(); - const instrumentation = { - _wrap: originalWrap, - } as any; - - const registerCallback = instrumentWhenWrapped(instrumentation); - - // Call _wrap first - instrumentation._wrap(); - - registerCallback(callback); - - // Callback should be called immediately - expect(callback).toHaveBeenCalledTimes(1); - expect(originalWrap).toHaveBeenCalled(); - }); - - test('passes through arguments to original _wrap', () => { - const callback = vi.fn(); - const originalWrap = vi.fn(); - const instrumentation = { - _wrap: originalWrap, - } as any; - - const registerCallback = instrumentWhenWrapped(instrumentation); - registerCallback(callback); - - // Call _wrap with arguments - const args = ['arg1', 'arg2']; - instrumentation._wrap(...args); - - expect(originalWrap).toHaveBeenCalledWith(...args); - }); -}); diff --git a/packages/remix/src/server/index.ts b/packages/remix/src/server/index.ts index 4fd2331b34fa..a1555930bad4 100644 --- a/packages/remix/src/server/index.ts +++ b/packages/remix/src/server/index.ts @@ -35,7 +35,6 @@ export { fastifyIntegration, flush, functionToStringIntegration, - generateInstrumentOnce, genericPoolIntegration, getActiveSpan, getAutoPerformanceIntegrations, diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index fd5fbb2e0e39..6fa2a75fcfdc 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -38,7 +38,6 @@ export { fastifyIntegration, flush, functionToStringIntegration, - generateInstrumentOnce, genericPoolIntegration, getActiveSpan, getAutoPerformanceIntegrations, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 5ebd934e9e88..d72e610185a3 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -37,7 +37,6 @@ export { flush, functionToStringIntegration, genericPoolIntegration, - generateInstrumentOnce, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index cfc23c721eb5..6d8db6463cdd 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -81,7 +81,6 @@ import type { Measurements as Measurements_imported, MeasurementUnit as MeasurementUnit_imported, Mechanism as Mechanism_imported, - MissingInstrumentationContext as MissingInstrumentationContext_imported, MonitorConfig as MonitorConfig_imported, NoneUnit as NoneUnit_imported, Options as Options_imported, @@ -214,8 +213,6 @@ export type TraceContext = TraceContext_imported; /** @deprecated This type has been moved to `@sentry/core`. */ export type CloudResourceContext = CloudResourceContext_imported; /** @deprecated This type has been moved to `@sentry/core`. */ -export type MissingInstrumentationContext = MissingInstrumentationContext_imported; -/** @deprecated This type has been moved to `@sentry/core`. */ export type DataCategory = DataCategory_imported; /** @deprecated This type has been moved to `@sentry/core`. */ export type DsnComponents = DsnComponents_imported; diff --git a/yarn.lock b/yarn.lock index 9406e8a9d981..4807a2541717 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6177,7 +6177,7 @@ "@opentelemetry/semantic-conventions" "^1.29.0" forwarded-parse "2.1.2" -"@opentelemetry/instrumentation@0.220.0", "@opentelemetry/instrumentation@^0.220.0": +"@opentelemetry/instrumentation@0.220.0": version "0.220.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz#542b36bf4871dd83dc84e1373ac4bbcd35a8bfb8" integrity sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==