Skip to content

Commit 846b445

Browse files
committed
update with tests
1 parent 461b8ed commit 846b445

File tree

3 files changed

+149
-18
lines changed

3 files changed

+149
-18
lines changed

packages/core/test/lib/integration.test.ts

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest';
22
import { getCurrentScope } from '../../src/currentScopes';
3-
import { addIntegration, getIntegrationsToSetup, installedIntegrations, setupIntegration } from '../../src/integration';
3+
import {
4+
_clearDisabledIntegrationsMarks,
5+
_isIntegrationMarkedDisabled,
6+
_markIntegrationsDisabled,
7+
addIntegration,
8+
getIntegrationsToSetup,
9+
installedIntegrations,
10+
setupIntegration,
11+
} from '../../src/integration';
412
import { setCurrentClient } from '../../src/sdk';
513
import type { Integration } from '../../src/types-hoist/integration';
614
import type { Options } from '../../src/types-hoist/options';
@@ -683,3 +691,127 @@ describe('addIntegration', () => {
683691
expect(logs).toHaveBeenCalledWith('Integration skipped because it was already installed: test');
684692
});
685693
});
694+
695+
describe('Integration marking system', () => {
696+
beforeEach(() => {
697+
// Clear marks before each test
698+
_clearDisabledIntegrationsMarks();
699+
// Reset installed integrations
700+
installedIntegrations.splice(0, installedIntegrations.length);
701+
});
702+
703+
afterEach(() => {
704+
// Clean up after tests
705+
_clearDisabledIntegrationsMarks();
706+
});
707+
708+
describe('_markIntegrationsDisabled', () => {
709+
it('marks a single integration as disabled', () => {
710+
_markIntegrationsDisabled('TestIntegration');
711+
expect(_isIntegrationMarkedDisabled('TestIntegration')).toBe(true);
712+
});
713+
714+
it('marks multiple integrations as disabled using array', () => {
715+
_markIntegrationsDisabled(['Integration1', 'Integration2', 'Integration3']);
716+
expect(_isIntegrationMarkedDisabled('Integration1')).toBe(true);
717+
expect(_isIntegrationMarkedDisabled('Integration2')).toBe(true);
718+
expect(_isIntegrationMarkedDisabled('Integration3')).toBe(true);
719+
});
720+
721+
it('does not affect unmarked integrations', () => {
722+
_markIntegrationsDisabled('Integration1');
723+
expect(_isIntegrationMarkedDisabled('Integration1')).toBe(true);
724+
expect(_isIntegrationMarkedDisabled('Integration2')).toBe(false);
725+
});
726+
});
727+
728+
describe('_isIntegrationMarkedDisabled', () => {
729+
it('returns false for integrations that are not marked', () => {
730+
expect(_isIntegrationMarkedDisabled('SomeIntegration')).toBe(false);
731+
});
732+
733+
it('returns true for integrations that are marked', () => {
734+
_markIntegrationsDisabled('MarkedIntegration');
735+
expect(_isIntegrationMarkedDisabled('MarkedIntegration')).toBe(true);
736+
});
737+
});
738+
739+
describe('_clearDisabledIntegrationsMarks', () => {
740+
it('clears all marks', () => {
741+
_markIntegrationsDisabled(['Integration1', 'Integration2']);
742+
expect(_isIntegrationMarkedDisabled('Integration1')).toBe(true);
743+
expect(_isIntegrationMarkedDisabled('Integration2')).toBe(true);
744+
745+
_clearDisabledIntegrationsMarks();
746+
747+
expect(_isIntegrationMarkedDisabled('Integration1')).toBe(false);
748+
expect(_isIntegrationMarkedDisabled('Integration2')).toBe(false);
749+
});
750+
751+
it('removes marked integrations from installedIntegrations list', () => {
752+
// Simulate integrations being installed
753+
installedIntegrations.push('Integration1', 'Integration2', 'Integration3');
754+
755+
// Mark some as disabled
756+
_markIntegrationsDisabled(['Integration1', 'Integration3']);
757+
758+
// Clear should remove marked ones from installed list
759+
_clearDisabledIntegrationsMarks();
760+
761+
expect(installedIntegrations).toEqual(['Integration2']);
762+
});
763+
764+
it('preserves integrations that are not marked when clearing', () => {
765+
installedIntegrations.push('Integration1', 'Integration2', 'Integration3');
766+
_markIntegrationsDisabled('Integration2');
767+
768+
_clearDisabledIntegrationsMarks();
769+
770+
expect(installedIntegrations).toEqual(['Integration1', 'Integration3']);
771+
});
772+
});
773+
774+
describe('setupIntegration with marked integrations', () => {
775+
it('skips setupOnce for marked integrations', () => {
776+
const client = getTestClient();
777+
const integration = new MockIntegration('MarkedIntegration');
778+
779+
// Mark the integration as disabled before setup
780+
_markIntegrationsDisabled('MarkedIntegration');
781+
782+
setupIntegration(client, integration, {});
783+
784+
// setupOnce should not be called
785+
expect(integration.setupOnce).not.toHaveBeenCalled();
786+
// Integration should not be in installed list
787+
expect(installedIntegrations).not.toContain('MarkedIntegration');
788+
});
789+
790+
it('calls setupOnce for non-marked integrations', () => {
791+
const client = getTestClient();
792+
const integration = new MockIntegration('NormalIntegration');
793+
794+
setupIntegration(client, integration, {});
795+
796+
// setupOnce should be called
797+
expect(integration.setupOnce).toHaveBeenCalledTimes(1);
798+
// Integration should be in installed list
799+
expect(installedIntegrations).toContain('NormalIntegration');
800+
});
801+
802+
it('allows setup after clearing marks', () => {
803+
const client = getTestClient();
804+
const integration = new MockIntegration('TestIntegration');
805+
806+
// Mark, clear, then setup
807+
_markIntegrationsDisabled('TestIntegration');
808+
_clearDisabledIntegrationsMarks();
809+
810+
setupIntegration(client, integration, {});
811+
812+
// setupOnce should be called after marks are cleared
813+
expect(integration.setupOnce).toHaveBeenCalledTimes(1);
814+
expect(installedIntegrations).toContain('TestIntegration');
815+
});
816+
});
817+
});

packages/node/src/integrations/tracing/langchain/index.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import type { IntegrationFn, LangChainOptions } from '@sentry/core';
2-
import {
3-
_markIntegrationsDisabled,
4-
ANTHROPIC_AI_INTEGRATION_NAME,
5-
defineIntegration,
6-
GOOGLE_GENAI_INTEGRATION_NAME,
7-
LANGCHAIN_INTEGRATION_NAME,
8-
OPENAI_INTEGRATION_NAME,
9-
} from '@sentry/core';
2+
import { defineIntegration, LANGCHAIN_INTEGRATION_NAME } from '@sentry/core';
103
import { generateInstrumentOnce } from '@sentry/node-core';
114
import { SentryLangChainInstrumentation } from './instrumentation';
125

@@ -19,14 +12,8 @@ const _langChainIntegration = ((options: LangChainOptions = {}) => {
1912
return {
2013
name: LANGCHAIN_INTEGRATION_NAME,
2114
setupOnce() {
22-
// Mark AI provider integrations as disabled to prevent duplicate spans
23-
// LangChain integration handles instrumentation for all underlying AI providers
24-
_markIntegrationsDisabled([
25-
OPENAI_INTEGRATION_NAME,
26-
ANTHROPIC_AI_INTEGRATION_NAME,
27-
GOOGLE_GENAI_INTEGRATION_NAME,
28-
]);
29-
15+
// Register the instrumentation
16+
// The instrumentation will mark AI providers as disabled when LangChain modules are actually loaded
3017
instrumentLangChain(options);
3118
},
3219
};

packages/node/src/integrations/tracing/langchain/instrumentation.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ import {
66
InstrumentationNodeModuleFile,
77
} from '@opentelemetry/instrumentation';
88
import type { LangChainOptions } from '@sentry/core';
9-
import { createLangChainCallbackHandler, getClient, SDK_VERSION } from '@sentry/core';
9+
import {
10+
_markIntegrationsDisabled,
11+
ANTHROPIC_AI_INTEGRATION_NAME,
12+
createLangChainCallbackHandler,
13+
getClient,
14+
GOOGLE_GENAI_INTEGRATION_NAME,
15+
OPENAI_INTEGRATION_NAME,
16+
SDK_VERSION,
17+
} from '@sentry/core';
1018

1119
const supportedVersions = ['>=0.1.0 <1.0.0'];
1220

@@ -143,6 +151,10 @@ export class SentryLangChainInstrumentation extends InstrumentationBase<LangChai
143151
* This is called when a LangChain provider package is loaded
144152
*/
145153
private _patch(exports: PatchedLangChainExports): PatchedLangChainExports | void {
154+
// Mark AI provider integrations as disabled now that LangChain is actually being used
155+
// This prevents duplicate spans from Anthropic/OpenAI/GoogleGenAI standalone integrations
156+
_markIntegrationsDisabled([OPENAI_INTEGRATION_NAME, ANTHROPIC_AI_INTEGRATION_NAME, GOOGLE_GENAI_INTEGRATION_NAME]);
157+
146158
const client = getClient();
147159
const defaultPii = Boolean(client?.getOptions().sendDefaultPii);
148160

0 commit comments

Comments
 (0)