From 31cce578263d76b2ba756f9001aee702ad40dca7 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 23 Oct 2025 11:13:15 +0200 Subject: [PATCH 1/5] update with disabling mechanisim --- .../tracing/langchain/instrument-with-pii.mjs | 2 - .../suites/tracing/langchain/instrument.mjs | 2 - packages/node-core/src/index.ts | 1 + .../src/otel/disabledIntegrations.ts | 45 +++++++++++ .../test/otel/disabledIntegrations.test.ts | 74 +++++++++++++++++++ .../tracing/anthropic-ai/index.ts | 6 +- .../tracing/google-genai/index.ts | 6 +- .../integrations/tracing/langchain/index.ts | 18 ++++- .../src/integrations/tracing/openai/index.ts | 6 +- 9 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 packages/node-core/src/otel/disabledIntegrations.ts create mode 100644 packages/node-core/test/otel/disabledIntegrations.test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs index 85b2a963d977..cb68a6f7683e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument-with-pii.mjs @@ -7,8 +7,6 @@ Sentry.init({ tracesSampleRate: 1.0, sendDefaultPii: true, transport: loggingTransport, - // Filter out Anthropic integration to avoid duplicate spans with LangChain - integrations: integrations => integrations.filter(integration => integration.name !== 'Anthropic_AI'), beforeSendTransaction: event => { // Filter out mock express server transactions if (event.transaction.includes('/v1/messages')) { diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs index 524d19f4b995..b4ce44f3e91a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/instrument.mjs @@ -7,8 +7,6 @@ Sentry.init({ tracesSampleRate: 1.0, sendDefaultPii: false, transport: loggingTransport, - // Filter out Anthropic integration to avoid duplicate spans with LangChain - integrations: integrations => integrations.filter(integration => integration.name !== 'Anthropic_AI'), beforeSendTransaction: event => { // Filter out mock express server transactions if (event.transaction.includes('/v1/messages')) { diff --git a/packages/node-core/src/index.ts b/packages/node-core/src/index.ts index 7557d73c74a2..bca7b727c699 100644 --- a/packages/node-core/src/index.ts +++ b/packages/node-core/src/index.ts @@ -32,6 +32,7 @@ export { pinoIntegration } from './integrations/pino'; export { SentryContextManager } from './otel/contextManager'; export { setupOpenTelemetryLogger } from './otel/logger'; export { generateInstrumentOnce, instrumentWhenWrapped, INSTRUMENTED } from './otel/instrument'; +export { disableIntegrations, isIntegrationDisabled, enableIntegration } from './otel/disabledIntegrations'; export { init, getDefaultIntegrations, initWithoutDefaultIntegrations, validateOpenTelemetrySetup } from './sdk'; export { setIsolationScope } from './sdk/scope'; diff --git a/packages/node-core/src/otel/disabledIntegrations.ts b/packages/node-core/src/otel/disabledIntegrations.ts new file mode 100644 index 000000000000..459846eabcae --- /dev/null +++ b/packages/node-core/src/otel/disabledIntegrations.ts @@ -0,0 +1,45 @@ +/** + * Registry to track disabled integrations. + * This is used to prevent duplicate instrumentation when higher-level integrations + * (like LangChain) already instrument the underlying libraries (like OpenAI, Anthropic, etc.) + */ + +const DISABLED_INTEGRATIONS = new Set(); + +/** + * Mark one or more integrations as disabled to prevent their instrumentation from being set up. + * @param integrationName The name(s) of the integration(s) to disable + */ +export function disableIntegrations(integrationName: string | string[]): void { + if (Array.isArray(integrationName)) { + integrationName.forEach(name => DISABLED_INTEGRATIONS.add(name)); + } else { + DISABLED_INTEGRATIONS.add(integrationName); + } +} + +/** + * Check if an integration has been disabled. + * @param integrationName The name of the integration to check + * @returns true if the integration is disabled + */ +export function isIntegrationDisabled(integrationName: string): boolean { + return DISABLED_INTEGRATIONS.has(integrationName); +} + +/** + * Remove one or more integrations from the disabled list. + * @param integrationName The name(s) of the integration(s) to enable + */ +export function enableIntegration(integrationName: string | string[]): void { + if (Array.isArray(integrationName)) { + integrationName.forEach(name => DISABLED_INTEGRATIONS.delete(name)); + } else { + DISABLED_INTEGRATIONS.delete(integrationName); + } +} + +/** Exported only for tests. */ +export function clearDisabledIntegrations(): void { + DISABLED_INTEGRATIONS.clear(); +} diff --git a/packages/node-core/test/otel/disabledIntegrations.test.ts b/packages/node-core/test/otel/disabledIntegrations.test.ts new file mode 100644 index 000000000000..bbe3b1116035 --- /dev/null +++ b/packages/node-core/test/otel/disabledIntegrations.test.ts @@ -0,0 +1,74 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { + clearDisabledIntegrations, + disableIntegrations, + enableIntegration, + isIntegrationDisabled, +} from '../../src/otel/disabledIntegrations'; + +describe('disabledIntegrations', () => { + beforeEach(() => { + clearDisabledIntegrations(); + }); + + it('should mark an integration as disabled', () => { + expect(isIntegrationDisabled('TestIntegration')).toBe(false); + disableIntegrations('TestIntegration'); + expect(isIntegrationDisabled('TestIntegration')).toBe(true); + }); + + it('should enable a disabled integration', () => { + disableIntegrations('TestIntegration'); + expect(isIntegrationDisabled('TestIntegration')).toBe(true); + enableIntegration('TestIntegration'); + expect(isIntegrationDisabled('TestIntegration')).toBe(false); + }); + + it('should handle multiple integrations', () => { + disableIntegrations('Integration1'); + disableIntegrations('Integration2'); + + expect(isIntegrationDisabled('Integration1')).toBe(true); + expect(isIntegrationDisabled('Integration2')).toBe(true); + expect(isIntegrationDisabled('Integration3')).toBe(false); + }); + + it('should clear all disabled integrations', () => { + disableIntegrations('Integration1'); + disableIntegrations('Integration2'); + + expect(isIntegrationDisabled('Integration1')).toBe(true); + expect(isIntegrationDisabled('Integration2')).toBe(true); + + clearDisabledIntegrations(); + + expect(isIntegrationDisabled('Integration1')).toBe(false); + expect(isIntegrationDisabled('Integration2')).toBe(false); + }); + + it('should disable multiple integrations at once using an array', () => { + expect(isIntegrationDisabled('Integration1')).toBe(false); + expect(isIntegrationDisabled('Integration2')).toBe(false); + expect(isIntegrationDisabled('Integration3')).toBe(false); + + disableIntegrations(['Integration1', 'Integration2', 'Integration3']); + + expect(isIntegrationDisabled('Integration1')).toBe(true); + expect(isIntegrationDisabled('Integration2')).toBe(true); + expect(isIntegrationDisabled('Integration3')).toBe(true); + }); + + it('should enable multiple integrations at once using an array', () => { + disableIntegrations(['Integration1', 'Integration2', 'Integration3']); + + expect(isIntegrationDisabled('Integration1')).toBe(true); + expect(isIntegrationDisabled('Integration2')).toBe(true); + expect(isIntegrationDisabled('Integration3')).toBe(true); + + enableIntegration(['Integration1', 'Integration2']); + + expect(isIntegrationDisabled('Integration1')).toBe(false); + expect(isIntegrationDisabled('Integration2')).toBe(false); + expect(isIntegrationDisabled('Integration3')).toBe(true); + }); +}); diff --git a/packages/node/src/integrations/tracing/anthropic-ai/index.ts b/packages/node/src/integrations/tracing/anthropic-ai/index.ts index 65b7d72a869a..a5ef985fada9 100644 --- a/packages/node/src/integrations/tracing/anthropic-ai/index.ts +++ b/packages/node/src/integrations/tracing/anthropic-ai/index.ts @@ -1,6 +1,6 @@ import type { AnthropicAiOptions, IntegrationFn } from '@sentry/core'; import { ANTHROPIC_AI_INTEGRATION_NAME, defineIntegration } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node-core'; +import { generateInstrumentOnce, isIntegrationDisabled } from '@sentry/node-core'; import { SentryAnthropicAiInstrumentation } from './instrumentation'; export const instrumentAnthropicAi = generateInstrumentOnce( @@ -13,6 +13,10 @@ const _anthropicAIIntegration = ((options: AnthropicAiOptions = {}) => { name: ANTHROPIC_AI_INTEGRATION_NAME, options, setupOnce() { + // Skip instrumentation if disabled (e.g., when LangChain integration is active) + if (isIntegrationDisabled(ANTHROPIC_AI_INTEGRATION_NAME)) { + return; + } instrumentAnthropicAi(options); }, }; diff --git a/packages/node/src/integrations/tracing/google-genai/index.ts b/packages/node/src/integrations/tracing/google-genai/index.ts index 5c1ad09d2fcd..8e3d61a6641a 100644 --- a/packages/node/src/integrations/tracing/google-genai/index.ts +++ b/packages/node/src/integrations/tracing/google-genai/index.ts @@ -1,6 +1,6 @@ import type { GoogleGenAIOptions, IntegrationFn } from '@sentry/core'; import { defineIntegration, GOOGLE_GENAI_INTEGRATION_NAME } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node-core'; +import { generateInstrumentOnce, isIntegrationDisabled } from '@sentry/node-core'; import { SentryGoogleGenAiInstrumentation } from './instrumentation'; export const instrumentGoogleGenAI = generateInstrumentOnce( @@ -12,6 +12,10 @@ const _googleGenAIIntegration = ((options: GoogleGenAIOptions = {}) => { return { name: GOOGLE_GENAI_INTEGRATION_NAME, setupOnce() { + // Skip instrumentation if disabled (e.g., when LangChain integration is active) + if (isIntegrationDisabled(GOOGLE_GENAI_INTEGRATION_NAME)) { + return; + } instrumentGoogleGenAI(options); }, }; diff --git a/packages/node/src/integrations/tracing/langchain/index.ts b/packages/node/src/integrations/tracing/langchain/index.ts index e575691b930f..df8eb95aaf61 100644 --- a/packages/node/src/integrations/tracing/langchain/index.ts +++ b/packages/node/src/integrations/tracing/langchain/index.ts @@ -1,6 +1,12 @@ import type { IntegrationFn, LangChainOptions } from '@sentry/core'; -import { defineIntegration, LANGCHAIN_INTEGRATION_NAME } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node-core'; +import { + ANTHROPIC_AI_INTEGRATION_NAME, + defineIntegration, + GOOGLE_GENAI_INTEGRATION_NAME, + LANGCHAIN_INTEGRATION_NAME, + OPENAI_INTEGRATION_NAME, +} from '@sentry/core'; +import { disableIntegrations, generateInstrumentOnce } from '@sentry/node-core'; import { SentryLangChainInstrumentation } from './instrumentation'; export const instrumentLangChain = generateInstrumentOnce( @@ -12,6 +18,10 @@ const _langChainIntegration = ((options: LangChainOptions = {}) => { return { name: LANGCHAIN_INTEGRATION_NAME, setupOnce() { + // Disable AI provider integrations to prevent duplicate spans + // LangChain integration handles instrumentation for all underlying AI providers + disableIntegrations([OPENAI_INTEGRATION_NAME, ANTHROPIC_AI_INTEGRATION_NAME, GOOGLE_GENAI_INTEGRATION_NAME]); + instrumentLangChain(options); }, }; @@ -25,6 +35,10 @@ const _langChainIntegration = ((options: LangChainOptions = {}) => { * When configured, this integration automatically instruments LangChain runnable instances * to capture telemetry data by injecting Sentry callback handlers into all LangChain calls. * + * **Important:** This integration automatically disables the OpenAI, Anthropic, and Google GenAI + * integrations to prevent duplicate spans when using LangChain with these providers. LangChain + * handles the instrumentation for all underlying AI providers. + * * @example * ```javascript * import * as Sentry from '@sentry/node'; diff --git a/packages/node/src/integrations/tracing/openai/index.ts b/packages/node/src/integrations/tracing/openai/index.ts index 0e88d2b315cc..38e724ed40e3 100644 --- a/packages/node/src/integrations/tracing/openai/index.ts +++ b/packages/node/src/integrations/tracing/openai/index.ts @@ -1,6 +1,6 @@ import type { IntegrationFn, OpenAiOptions } from '@sentry/core'; import { defineIntegration, OPENAI_INTEGRATION_NAME } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node-core'; +import { generateInstrumentOnce, isIntegrationDisabled } from '@sentry/node-core'; import { SentryOpenAiInstrumentation } from './instrumentation'; export const instrumentOpenAi = generateInstrumentOnce( @@ -13,6 +13,10 @@ const _openAiIntegration = ((options: OpenAiOptions = {}) => { name: OPENAI_INTEGRATION_NAME, options, setupOnce() { + // Skip instrumentation if disabled (e.g., when LangChain integration is active) + if (isIntegrationDisabled(OPENAI_INTEGRATION_NAME)) { + return; + } instrumentOpenAi(); }, }; From e34fa3d91167d2c5033767d5616df8012b1ce17c Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 23 Oct 2025 11:22:09 +0200 Subject: [PATCH 2/5] reorder integrations --- packages/node/src/integrations/tracing/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 2782d7907349..b586941d6530 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -51,13 +51,15 @@ export function getAutoPerformanceIntegrations(): Integration[] { kafkaIntegration(), amqplibIntegration(), lruMemoizerIntegration(), + // AI providers + // LangChain must come first to disable AI provider integrations before they instrument + langChainIntegration(), vercelAIIntegration(), openAIIntegration(), - postgresJsIntegration(), - firebaseIntegration(), anthropicAIIntegration(), googleGenAIIntegration(), - langChainIntegration(), + postgresJsIntegration(), + firebaseIntegration(), ]; } @@ -89,12 +91,12 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentTedious, instrumentGenericPool, instrumentAmqplib, + instrumentLangChain, instrumentVercelAi, instrumentOpenAi, instrumentPostgresJs, instrumentFirebase, instrumentAnthropicAi, instrumentGoogleGenAI, - instrumentLangChain, ]; } From d65dd6e01ec8ec7710141d11c2732e32ebecede2 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 23 Oct 2025 16:26:16 +0200 Subject: [PATCH 3/5] deisable integration --- packages/core/src/index.ts | 11 ++- packages/core/src/integration.ts | 66 ++++++++++++++++- packages/node-core/src/index.ts | 1 - .../src/otel/disabledIntegrations.ts | 45 ----------- packages/node-core/src/sdk/client.ts | 18 ++++- .../test/otel/disabledIntegrations.test.ts | 74 ------------------- .../tracing/anthropic-ai/index.ts | 6 +- .../tracing/google-genai/index.ts | 6 +- .../integrations/tracing/langchain/index.ts | 17 ++++- .../src/integrations/tracing/openai/index.ts | 6 +- 10 files changed, 109 insertions(+), 141 deletions(-) delete mode 100644 packages/node-core/src/otel/disabledIntegrations.ts delete mode 100644 packages/node-core/test/otel/disabledIntegrations.test.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f3b29009b9ce..e535dae6feb9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -55,7 +55,16 @@ export { initAndBind, setCurrentClient } from './sdk'; export { createTransport } from './transports/base'; export { makeOfflineTransport } from './transports/offline'; export { makeMultiplexedTransport } from './transports/multiplexed'; -export { getIntegrationsToSetup, addIntegration, defineIntegration } from './integration'; +export { + getIntegrationsToSetup, + addIntegration, + defineIntegration, + installedIntegrations, + disableIntegrations, + isIntegrationDisabled, + enableIntegration, + clearDisabledIntegrations, +} from './integration'; export { applyScopeDataToEvent, mergeScopeData } from './utils/applyScopeDataToEvent'; export { prepareEvent } from './utils/prepareEvent'; export { createCheckInEnvelope } from './checkin'; diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 5cba3ff3dfb8..28a88d9d5611 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -8,6 +8,65 @@ import { debug } from './utils/debug-logger'; export const installedIntegrations: string[] = []; +/** + * Registry to track disabled integrations. + * This is used to prevent duplicate instrumentation when higher-level integrations + * (like LangChain) already instrument the underlying libraries (like OpenAI, Anthropic, etc.) + */ +const DISABLED_INTEGRATIONS = new Set(); + +/** + * Mark one or more integrations as disabled to prevent their instrumentation from being set up. + * @param integrationName The name(s) of the integration(s) to disable + */ +export function disableIntegrations(integrationName: string | string[]): void { + if (Array.isArray(integrationName)) { + integrationName.forEach(name => DISABLED_INTEGRATIONS.add(name)); + } else { + DISABLED_INTEGRATIONS.add(integrationName); + } +} + +/** + * Check if an integration has been disabled. + * @param integrationName The name of the integration to check + * @returns true if the integration is disabled + */ +export function isIntegrationDisabled(integrationName: string): boolean { + return DISABLED_INTEGRATIONS.has(integrationName); +} + +/** + * Remove one or more integrations from the disabled list. + * @param integrationName The name(s) of the integration(s) to enable + */ +export function enableIntegration(integrationName: string | string[]): void { + if (Array.isArray(integrationName)) { + integrationName.forEach(name => DISABLED_INTEGRATIONS.delete(name)); + } else { + DISABLED_INTEGRATIONS.delete(integrationName); + } +} + +/** + * Clear all disabled integrations. + * This is automatically called during Sentry.init() to ensure a clean state. + * + * This also removes the disabled integrations from the global installedIntegrations list, + * allowing them to run setupOnce() again if they're included in a new client. + */ +export function clearDisabledIntegrations(): void { + // Remove disabled integrations from the installed list so they can setup again + DISABLED_INTEGRATIONS.forEach(integrationName => { + const index = installedIntegrations.indexOf(integrationName); + if (index !== -1) { + installedIntegrations.splice(index, 1); + } + }); + + DISABLED_INTEGRATIONS.clear(); +} + /** Map of integrations assigned to a client */ export type IntegrationIndex = { [key: string]: Integration; @@ -108,8 +167,11 @@ export function setupIntegration(client: Client, integration: Integration, integ // `setupOnce` is only called the first time if (installedIntegrations.indexOf(integration.name) === -1 && typeof integration.setupOnce === 'function') { - integration.setupOnce(); - installedIntegrations.push(integration.name); + // Skip setup if integration is disabled + if (!isIntegrationDisabled(integration.name)) { + integration.setupOnce(); + installedIntegrations.push(integration.name); + } } // `setup` is run for each client diff --git a/packages/node-core/src/index.ts b/packages/node-core/src/index.ts index bca7b727c699..7557d73c74a2 100644 --- a/packages/node-core/src/index.ts +++ b/packages/node-core/src/index.ts @@ -32,7 +32,6 @@ export { pinoIntegration } from './integrations/pino'; export { SentryContextManager } from './otel/contextManager'; export { setupOpenTelemetryLogger } from './otel/logger'; export { generateInstrumentOnce, instrumentWhenWrapped, INSTRUMENTED } from './otel/instrument'; -export { disableIntegrations, isIntegrationDisabled, enableIntegration } from './otel/disabledIntegrations'; export { init, getDefaultIntegrations, initWithoutDefaultIntegrations, validateOpenTelemetrySetup } from './sdk'; export { setIsolationScope } from './sdk/scope'; diff --git a/packages/node-core/src/otel/disabledIntegrations.ts b/packages/node-core/src/otel/disabledIntegrations.ts deleted file mode 100644 index 459846eabcae..000000000000 --- a/packages/node-core/src/otel/disabledIntegrations.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Registry to track disabled integrations. - * This is used to prevent duplicate instrumentation when higher-level integrations - * (like LangChain) already instrument the underlying libraries (like OpenAI, Anthropic, etc.) - */ - -const DISABLED_INTEGRATIONS = new Set(); - -/** - * Mark one or more integrations as disabled to prevent their instrumentation from being set up. - * @param integrationName The name(s) of the integration(s) to disable - */ -export function disableIntegrations(integrationName: string | string[]): void { - if (Array.isArray(integrationName)) { - integrationName.forEach(name => DISABLED_INTEGRATIONS.add(name)); - } else { - DISABLED_INTEGRATIONS.add(integrationName); - } -} - -/** - * Check if an integration has been disabled. - * @param integrationName The name of the integration to check - * @returns true if the integration is disabled - */ -export function isIntegrationDisabled(integrationName: string): boolean { - return DISABLED_INTEGRATIONS.has(integrationName); -} - -/** - * Remove one or more integrations from the disabled list. - * @param integrationName The name(s) of the integration(s) to enable - */ -export function enableIntegration(integrationName: string | string[]): void { - if (Array.isArray(integrationName)) { - integrationName.forEach(name => DISABLED_INTEGRATIONS.delete(name)); - } else { - DISABLED_INTEGRATIONS.delete(integrationName); - } -} - -/** Exported only for tests. */ -export function clearDisabledIntegrations(): void { - DISABLED_INTEGRATIONS.clear(); -} diff --git a/packages/node-core/src/sdk/client.ts b/packages/node-core/src/sdk/client.ts index e631508c7392..962cb2c197b4 100644 --- a/packages/node-core/src/sdk/client.ts +++ b/packages/node-core/src/sdk/client.ts @@ -4,7 +4,14 @@ import { trace } from '@opentelemetry/api'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { DynamicSamplingContext, Scope, ServerRuntimeClientOptions, TraceContext } from '@sentry/core'; -import { _INTERNAL_flushLogsBuffer, applySdkMetadata, debug, SDK_VERSION, ServerRuntimeClient } from '@sentry/core'; +import { + _INTERNAL_flushLogsBuffer, + applySdkMetadata, + clearDisabledIntegrations, + debug, + SDK_VERSION, + ServerRuntimeClient, +} from '@sentry/core'; import { getTraceContextForScope } from '@sentry/opentelemetry'; import { isMainThread, threadId } from 'worker_threads'; import { DEBUG_BUILD } from '../debug-build'; @@ -145,6 +152,15 @@ export class NodeClient extends ServerRuntimeClient { } } + /** @inheritDoc */ + protected _setupIntegrations(): void { + // Clear disabled integrations before setting up integrations + // This ensures that integrations work correctly when not all default integrations are used + // (e.g., when LangChain disables OpenAI, but a subsequent client doesn't use LangChain) + clearDisabledIntegrations(); + super._setupIntegrations(); + } + /** Custom implementation for OTEL, so we can handle scope-span linking. */ protected _getTraceInfoFromScope( scope: Scope | undefined, diff --git a/packages/node-core/test/otel/disabledIntegrations.test.ts b/packages/node-core/test/otel/disabledIntegrations.test.ts deleted file mode 100644 index bbe3b1116035..000000000000 --- a/packages/node-core/test/otel/disabledIntegrations.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { beforeEach, describe, expect, it } from 'vitest'; -import { - clearDisabledIntegrations, - disableIntegrations, - enableIntegration, - isIntegrationDisabled, -} from '../../src/otel/disabledIntegrations'; - -describe('disabledIntegrations', () => { - beforeEach(() => { - clearDisabledIntegrations(); - }); - - it('should mark an integration as disabled', () => { - expect(isIntegrationDisabled('TestIntegration')).toBe(false); - disableIntegrations('TestIntegration'); - expect(isIntegrationDisabled('TestIntegration')).toBe(true); - }); - - it('should enable a disabled integration', () => { - disableIntegrations('TestIntegration'); - expect(isIntegrationDisabled('TestIntegration')).toBe(true); - enableIntegration('TestIntegration'); - expect(isIntegrationDisabled('TestIntegration')).toBe(false); - }); - - it('should handle multiple integrations', () => { - disableIntegrations('Integration1'); - disableIntegrations('Integration2'); - - expect(isIntegrationDisabled('Integration1')).toBe(true); - expect(isIntegrationDisabled('Integration2')).toBe(true); - expect(isIntegrationDisabled('Integration3')).toBe(false); - }); - - it('should clear all disabled integrations', () => { - disableIntegrations('Integration1'); - disableIntegrations('Integration2'); - - expect(isIntegrationDisabled('Integration1')).toBe(true); - expect(isIntegrationDisabled('Integration2')).toBe(true); - - clearDisabledIntegrations(); - - expect(isIntegrationDisabled('Integration1')).toBe(false); - expect(isIntegrationDisabled('Integration2')).toBe(false); - }); - - it('should disable multiple integrations at once using an array', () => { - expect(isIntegrationDisabled('Integration1')).toBe(false); - expect(isIntegrationDisabled('Integration2')).toBe(false); - expect(isIntegrationDisabled('Integration3')).toBe(false); - - disableIntegrations(['Integration1', 'Integration2', 'Integration3']); - - expect(isIntegrationDisabled('Integration1')).toBe(true); - expect(isIntegrationDisabled('Integration2')).toBe(true); - expect(isIntegrationDisabled('Integration3')).toBe(true); - }); - - it('should enable multiple integrations at once using an array', () => { - disableIntegrations(['Integration1', 'Integration2', 'Integration3']); - - expect(isIntegrationDisabled('Integration1')).toBe(true); - expect(isIntegrationDisabled('Integration2')).toBe(true); - expect(isIntegrationDisabled('Integration3')).toBe(true); - - enableIntegration(['Integration1', 'Integration2']); - - expect(isIntegrationDisabled('Integration1')).toBe(false); - expect(isIntegrationDisabled('Integration2')).toBe(false); - expect(isIntegrationDisabled('Integration3')).toBe(true); - }); -}); diff --git a/packages/node/src/integrations/tracing/anthropic-ai/index.ts b/packages/node/src/integrations/tracing/anthropic-ai/index.ts index a5ef985fada9..65b7d72a869a 100644 --- a/packages/node/src/integrations/tracing/anthropic-ai/index.ts +++ b/packages/node/src/integrations/tracing/anthropic-ai/index.ts @@ -1,6 +1,6 @@ import type { AnthropicAiOptions, IntegrationFn } from '@sentry/core'; import { ANTHROPIC_AI_INTEGRATION_NAME, defineIntegration } from '@sentry/core'; -import { generateInstrumentOnce, isIntegrationDisabled } from '@sentry/node-core'; +import { generateInstrumentOnce } from '@sentry/node-core'; import { SentryAnthropicAiInstrumentation } from './instrumentation'; export const instrumentAnthropicAi = generateInstrumentOnce( @@ -13,10 +13,6 @@ const _anthropicAIIntegration = ((options: AnthropicAiOptions = {}) => { name: ANTHROPIC_AI_INTEGRATION_NAME, options, setupOnce() { - // Skip instrumentation if disabled (e.g., when LangChain integration is active) - if (isIntegrationDisabled(ANTHROPIC_AI_INTEGRATION_NAME)) { - return; - } instrumentAnthropicAi(options); }, }; diff --git a/packages/node/src/integrations/tracing/google-genai/index.ts b/packages/node/src/integrations/tracing/google-genai/index.ts index 8e3d61a6641a..5c1ad09d2fcd 100644 --- a/packages/node/src/integrations/tracing/google-genai/index.ts +++ b/packages/node/src/integrations/tracing/google-genai/index.ts @@ -1,6 +1,6 @@ import type { GoogleGenAIOptions, IntegrationFn } from '@sentry/core'; import { defineIntegration, GOOGLE_GENAI_INTEGRATION_NAME } from '@sentry/core'; -import { generateInstrumentOnce, isIntegrationDisabled } from '@sentry/node-core'; +import { generateInstrumentOnce } from '@sentry/node-core'; import { SentryGoogleGenAiInstrumentation } from './instrumentation'; export const instrumentGoogleGenAI = generateInstrumentOnce( @@ -12,10 +12,6 @@ const _googleGenAIIntegration = ((options: GoogleGenAIOptions = {}) => { return { name: GOOGLE_GENAI_INTEGRATION_NAME, setupOnce() { - // Skip instrumentation if disabled (e.g., when LangChain integration is active) - if (isIntegrationDisabled(GOOGLE_GENAI_INTEGRATION_NAME)) { - return; - } instrumentGoogleGenAI(options); }, }; diff --git a/packages/node/src/integrations/tracing/langchain/index.ts b/packages/node/src/integrations/tracing/langchain/index.ts index df8eb95aaf61..1c975a8f552f 100644 --- a/packages/node/src/integrations/tracing/langchain/index.ts +++ b/packages/node/src/integrations/tracing/langchain/index.ts @@ -2,6 +2,7 @@ import type { IntegrationFn, LangChainOptions } from '@sentry/core'; import { ANTHROPIC_AI_INTEGRATION_NAME, defineIntegration, + getClient, GOOGLE_GENAI_INTEGRATION_NAME, LANGCHAIN_INTEGRATION_NAME, OPENAI_INTEGRATION_NAME, @@ -18,9 +19,21 @@ const _langChainIntegration = ((options: LangChainOptions = {}) => { return { name: LANGCHAIN_INTEGRATION_NAME, setupOnce() { - // Disable AI provider integrations to prevent duplicate spans + // Only disable AI provider integrations if they weren't explicitly requested by the user // LangChain integration handles instrumentation for all underlying AI providers - disableIntegrations([OPENAI_INTEGRATION_NAME, ANTHROPIC_AI_INTEGRATION_NAME, GOOGLE_GENAI_INTEGRATION_NAME]); + const client = getClient(); + const clientIntegrations = client?.getOptions().integrations || []; + const explicitIntegrationNames = clientIntegrations.map(i => i.name); + + const integrationsToDisable = [ + OPENAI_INTEGRATION_NAME, + ANTHROPIC_AI_INTEGRATION_NAME, + GOOGLE_GENAI_INTEGRATION_NAME, + ].filter(name => !explicitIntegrationNames.includes(name)); + + if (integrationsToDisable.length > 0) { + disableIntegrations(integrationsToDisable); + } instrumentLangChain(options); }, diff --git a/packages/node/src/integrations/tracing/openai/index.ts b/packages/node/src/integrations/tracing/openai/index.ts index 38e724ed40e3..0e88d2b315cc 100644 --- a/packages/node/src/integrations/tracing/openai/index.ts +++ b/packages/node/src/integrations/tracing/openai/index.ts @@ -1,6 +1,6 @@ import type { IntegrationFn, OpenAiOptions } from '@sentry/core'; import { defineIntegration, OPENAI_INTEGRATION_NAME } from '@sentry/core'; -import { generateInstrumentOnce, isIntegrationDisabled } from '@sentry/node-core'; +import { generateInstrumentOnce } from '@sentry/node-core'; import { SentryOpenAiInstrumentation } from './instrumentation'; export const instrumentOpenAi = generateInstrumentOnce( @@ -13,10 +13,6 @@ const _openAiIntegration = ((options: OpenAiOptions = {}) => { name: OPENAI_INTEGRATION_NAME, options, setupOnce() { - // Skip instrumentation if disabled (e.g., when LangChain integration is active) - if (isIntegrationDisabled(OPENAI_INTEGRATION_NAME)) { - return; - } instrumentOpenAi(); }, }; From 3fba71ef9365a69acd80a202cfc4893a2936cadb Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Thu, 30 Oct 2025 09:49:12 +0100 Subject: [PATCH 4/5] refactor --- .../integrations/tracing/langchain/index.ts | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/packages/node/src/integrations/tracing/langchain/index.ts b/packages/node/src/integrations/tracing/langchain/index.ts index 1c975a8f552f..9d8ff0694189 100644 --- a/packages/node/src/integrations/tracing/langchain/index.ts +++ b/packages/node/src/integrations/tracing/langchain/index.ts @@ -2,12 +2,12 @@ import type { IntegrationFn, LangChainOptions } from '@sentry/core'; import { ANTHROPIC_AI_INTEGRATION_NAME, defineIntegration, - getClient, + disableIntegrations, GOOGLE_GENAI_INTEGRATION_NAME, LANGCHAIN_INTEGRATION_NAME, OPENAI_INTEGRATION_NAME, } from '@sentry/core'; -import { disableIntegrations, generateInstrumentOnce } from '@sentry/node-core'; +import { generateInstrumentOnce } from '@sentry/node-core'; import { SentryLangChainInstrumentation } from './instrumentation'; export const instrumentLangChain = generateInstrumentOnce( @@ -19,21 +19,9 @@ const _langChainIntegration = ((options: LangChainOptions = {}) => { return { name: LANGCHAIN_INTEGRATION_NAME, setupOnce() { - // Only disable AI provider integrations if they weren't explicitly requested by the user + // Disable AI provider integrations to prevent duplicate spans // LangChain integration handles instrumentation for all underlying AI providers - const client = getClient(); - const clientIntegrations = client?.getOptions().integrations || []; - const explicitIntegrationNames = clientIntegrations.map(i => i.name); - - const integrationsToDisable = [ - OPENAI_INTEGRATION_NAME, - ANTHROPIC_AI_INTEGRATION_NAME, - GOOGLE_GENAI_INTEGRATION_NAME, - ].filter(name => !explicitIntegrationNames.includes(name)); - - if (integrationsToDisable.length > 0) { - disableIntegrations(integrationsToDisable); - } + disableIntegrations([OPENAI_INTEGRATION_NAME, ANTHROPIC_AI_INTEGRATION_NAME, GOOGLE_GENAI_INTEGRATION_NAME]); instrumentLangChain(options); }, From df823f425b477dc58bc4e73f885b0af87c2df913 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Tue, 4 Nov 2025 16:41:12 +0100 Subject: [PATCH 5/5] add missing import --- packages/node-core/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node-core/src/index.ts b/packages/node-core/src/index.ts index 7557d73c74a2..c242386fa2e7 100644 --- a/packages/node-core/src/index.ts +++ b/packages/node-core/src/index.ts @@ -113,6 +113,7 @@ export { captureSession, endSession, addIntegration, + disableIntegrations, startSpan, startSpanManual, startInactiveSpan,