From 756a5aac8210abe7ea16875f1644b906a4e86626 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 14 May 2025 09:15:12 +0200 Subject: [PATCH] fix(node): Avoid creating breadcrumbs for suppressed requests --- .../requests/fetch-breadcrumbs/scenario.mjs | 2 + .../requests/fetch-breadcrumbs/test.ts | 2 +- .../requests/http-breadcrumbs/scenario.mjs | 2 + .../http/SentryHttpInstrumentation.ts | 134 +++++++++--------- .../SentryNodeFetchInstrumentation.ts | 42 ++++-- 5 files changed, 101 insertions(+), 81 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.mjs index 21694ba54e9d..779240ec755f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.mjs @@ -8,6 +8,8 @@ async function run() { await fetch(`${process.env.SERVER_URL}/api/v2`).then(res => res.text()); await fetch(`${process.env.SERVER_URL}/api/v3`).then(res => res.text()); + await Sentry.suppressTracing(() => fetch(`${process.env.SERVER_URL}/api/v4`).then(res => res.text())); + Sentry.captureException(new Error('foo')); } diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/test.ts index cab9c61a1b65..d3315ae86ece 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/test.ts @@ -4,7 +4,7 @@ import { createTestServer } from '../../../../utils/server'; describe('outgoing fetch', () => { createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { - test('outgoing fetch requests create breadcrumbs xxx', async () => { + test('outgoing fetch requests create breadcrumbs', async () => { const [SERVER_URL, closeTestServer] = await createTestServer().start(); await createRunner() diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-breadcrumbs/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/requests/http-breadcrumbs/scenario.mjs index 2ee57c8651e0..318b76858881 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/http-breadcrumbs/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-breadcrumbs/scenario.mjs @@ -9,6 +9,8 @@ async function run() { await makeHttpRequest(`${process.env.SERVER_URL}/api/v2`); await makeHttpRequest(`${process.env.SERVER_URL}/api/v3`); + await Sentry.suppressTracing(() => makeHttpRequest(`${process.env.SERVER_URL}/api/v4`)); + Sentry.captureException(new Error('foo')); } diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index 6b8f615479e4..a5c1ae523c2d 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -5,7 +5,7 @@ import type * as http from 'node:http'; import type * as https from 'node:https'; import type { EventEmitter } from 'node:stream'; import { context, propagation } from '@opentelemetry/api'; -import { VERSION } from '@opentelemetry/core'; +import { isTracingSuppressed, VERSION } from '@opentelemetry/core'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; import type { AggregationCounts, Client, SanitizedRequestData, Scope } from '@sentry/core'; @@ -116,11 +116,13 @@ const MAX_BODY_BYTE_LENGTH = 1024 * 1024; */ export class SentryHttpInstrumentation extends InstrumentationBase { private _propagationDecisionMap: LRUMap; + private _ignoreOutgoingRequestsMap: WeakMap; public constructor(config: SentryHttpInstrumentationOptions = {}) { super(INSTRUMENTATION_NAME, VERSION, config); this._propagationDecisionMap = new LRUMap(100); + this._ignoreOutgoingRequestsMap = new WeakMap(); } /** @inheritdoc */ @@ -149,6 +151,37 @@ export class SentryHttpInstrumentation extends InstrumentationBase(moduleExports: T): T => { + if (hasRegisteredHandlers) { + return moduleExports; + } + + hasRegisteredHandlers = true; + + subscribe('http.server.request.start', onHttpServerRequestStart); + subscribe('http.client.response.finish', onHttpClientResponseFinish); + + // When an error happens, we still want to have a breadcrumb + // In this case, `http.client.response.finish` is not triggered + subscribe('http.client.request.error', onHttpClientRequestError); + + // NOTE: This channel only exist since Node 22 + // Before that, outgoing requests are not patched + // and trace headers are not propagated, sadly. + if (this.getConfig().propagateTraceInOutgoingRequests) { + subscribe('http.client.request.created', onHttpClientRequestCreated); + } + + return moduleExports; + }; + + const unwrap = (): void => { + unsubscribe('http.server.request.start', onHttpServerRequestStart); + unsubscribe('http.client.response.finish', onHttpClientResponseFinish); + unsubscribe('http.client.request.error', onHttpClientRequestError); + unsubscribe('http.client.request.created', onHttpClientRequestCreated); + }; + /** * You may be wondering why we register these diagnostics-channel listeners * in such a convoluted way (as InstrumentationNodeModuleDefinition...)˝, @@ -158,64 +191,8 @@ export class SentryHttpInstrumentation extends InstrumentationBase { - if (hasRegisteredHandlers) { - return moduleExports; - } - - hasRegisteredHandlers = true; - - subscribe('http.server.request.start', onHttpServerRequestStart); - subscribe('http.client.response.finish', onHttpClientResponseFinish); - - // When an error happens, we still want to have a breadcrumb - // In this case, `http.client.response.finish` is not triggered - subscribe('http.client.request.error', onHttpClientRequestError); - - // NOTE: This channel only exist since Node 23 - // Before that, outgoing requests are not patched - // and trace headers are not propagated, sadly. - if (this.getConfig().propagateTraceInOutgoingRequests) { - subscribe('http.client.request.created', onHttpClientRequestCreated); - } - - return moduleExports; - }, - () => { - unsubscribe('http.server.request.start', onHttpServerRequestStart); - unsubscribe('http.client.response.finish', onHttpClientResponseFinish); - unsubscribe('http.client.request.error', onHttpClientRequestError); - unsubscribe('http.client.request.created', onHttpClientRequestCreated); - }, - ), - new InstrumentationNodeModuleDefinition( - 'https', - ['*'], - (moduleExports: Https): Https => { - if (hasRegisteredHandlers) { - return moduleExports; - } - - hasRegisteredHandlers = true; - - subscribe('http.server.request.start', onHttpServerRequestStart); - subscribe('http.client.response.finish', onHttpClientResponseFinish); - - // When an error happens, we still want to have a breadcrumb - // In this case, `http.client.response.finish` is not triggered - subscribe('http.client.request.error', onHttpClientRequestError); - - return moduleExports; - }, - () => { - unsubscribe('http.server.request.start', onHttpServerRequestStart); - unsubscribe('http.client.response.finish', onHttpClientResponseFinish); - unsubscribe('http.client.request.error', onHttpClientRequestError); - }, - ), + new InstrumentationNodeModuleDefinition('http', ['*'], wrap, unwrap), + new InstrumentationNodeModuleDefinition('https', ['*'], wrap, unwrap), ]; } @@ -228,13 +205,12 @@ export class SentryHttpInstrumentation extends InstrumentationBase; private _propagationDecisionMap: LRUMap; + private _ignoreOutgoingRequestsMap: WeakMap; public constructor(config: SentryNodeFetchInstrumentationOptions = {}) { super('@sentry/instrumentation-node-fetch', VERSION, config); this._channelSubs = []; this._propagationDecisionMap = new LRUMap(100); + this._ignoreOutgoingRequestsMap = new WeakMap(); } /** No need to instrument files/modules. */ @@ -118,15 +121,17 @@ export class SentryNodeFetchInstrumentation extends InstrumentationBase