Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')) {
Expand Down
1 change: 1 addition & 0 deletions packages/node-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
45 changes: 45 additions & 0 deletions packages/node-core/src/otel/disabledIntegrations.ts
Original file line number Diff line number Diff line change
@@ -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<string>();

/**
* 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();
}
74 changes: 74 additions & 0 deletions packages/node-core/test/otel/disabledIntegrations.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
6 changes: 5 additions & 1 deletion packages/node/src/integrations/tracing/anthropic-ai/index.ts
Original file line number Diff line number Diff line change
@@ -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<AnthropicAiOptions>(
Expand All @@ -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);
},
};
Expand Down
6 changes: 5 additions & 1 deletion packages/node/src/integrations/tracing/google-genai/index.ts
Original file line number Diff line number Diff line change
@@ -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<GoogleGenAIOptions>(
Expand All @@ -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);
},
};
Expand Down
18 changes: 16 additions & 2 deletions packages/node/src/integrations/tracing/langchain/index.ts
Original file line number Diff line number Diff line change
@@ -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<LangChainOptions>(
Expand All @@ -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);
},
};
Expand All @@ -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';
Expand Down
6 changes: 5 additions & 1 deletion packages/node/src/integrations/tracing/openai/index.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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();
},
};
Expand Down
Loading