Skip to content

Commit 7db0b7f

Browse files
authored
feat(otel): Disable node tracing auto instrumentation when using otel (#6144)
1 parent 351be06 commit 7db0b7f

File tree

18 files changed

+205
-6
lines changed

18 files changed

+205
-6
lines changed

packages/node/src/handlers.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ export function tracingHandler(): (
3737
const hub = getCurrentHub();
3838
const options = hub.getClient()?.getOptions();
3939

40-
if (!options || req.method?.toUpperCase() === 'OPTIONS' || req.method?.toUpperCase() === 'HEAD') {
40+
if (
41+
!options ||
42+
options.instrumenter !== 'sentry' ||
43+
req.method?.toUpperCase() === 'OPTIONS' ||
44+
req.method?.toUpperCase() === 'HEAD'
45+
) {
4146
return next();
4247
}
4348

packages/node/src/integrations/http.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ export class Http implements Integration {
6969
}
7070

7171
const clientOptions = setupOnceGetCurrentHub().getClient<NodeClient>()?.getOptions();
72+
73+
// Do not auto-instrument for other instrumenter
74+
if (clientOptions && clientOptions.instrumenter !== 'sentry') {
75+
__DEBUG_BUILD__ && logger.log('HTTP Integration is skipped because of instrumenter configuration.');
76+
return;
77+
}
78+
7279
const wrappedHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, this._tracing, clientOptions);
7380

7481
// eslint-disable-next-line @typescript-eslint/no-var-requires

packages/node/test/helper/node-client-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export function getDefaultNodeClientOptions(options: Partial<NodeClientOptions>
88
integrations: [],
99
transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
1010
stackParser: () => [],
11+
instrumenter: 'sentry',
1112
...options,
1213
};
1314
}

packages/node/test/integrations/http.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as sentryCore from '@sentry/core';
22
import { Hub } from '@sentry/core';
33
import { addExtensionMethods, Span, TRACEPARENT_REGEXP, Transaction } from '@sentry/tracing';
44
import { TransactionContext } from '@sentry/types';
5-
import { parseSemver } from '@sentry/utils';
5+
import { logger, parseSemver } from '@sentry/utils';
66
import * as http from 'http';
77
import * as https from 'https';
88
import * as HttpsProxyAgent from 'https-proxy-agent';
@@ -188,6 +188,28 @@ describe('tracing', () => {
188188
expect(transaction.metadata.propagations).toBe(2);
189189
});
190190

191+
it("doesn't attach when using otel instrumenter", () => {
192+
const loggerLogSpy = jest.spyOn(logger, 'log');
193+
194+
const options = getDefaultNodeClientOptions({
195+
dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012',
196+
tracesSampleRate: 1.0,
197+
integrations: [new HttpIntegration({ tracing: true })],
198+
release: '1.0.0',
199+
environment: 'production',
200+
instrumenter: 'otel',
201+
});
202+
const hub = new Hub(new NodeClient(options));
203+
204+
const integration = new HttpIntegration();
205+
integration.setupOnce(
206+
() => {},
207+
() => hub,
208+
);
209+
210+
expect(loggerLogSpy).toBeCalledWith('HTTP Integration is skipped because of instrumenter configuration.');
211+
});
212+
191213
describe('tracePropagationTargets option', () => {
192214
beforeEach(() => {
193215
// hacky way of restoring monkey patched functions

packages/tracing/src/integrations/node/apollo.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Hub } from '@sentry/core';
22
import { EventProcessor, Integration } from '@sentry/types';
33
import { arrayify, fill, isThenable, loadModule, logger } from '@sentry/utils';
44

5+
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
6+
57
type ApolloResolverGroup = {
68
[key: string]: () => unknown;
79
};
@@ -39,6 +41,11 @@ export class Apollo implements Integration {
3941
return;
4042
}
4143

44+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
45+
__DEBUG_BUILD__ && logger.log('Apollo Integration is skipped because of instrumenter configuration.');
46+
return;
47+
}
48+
4249
/**
4350
* Iterate over resolvers of the ApolloServer instance before schemas are constructed.
4451
*/

packages/tracing/src/integrations/node/express.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/* eslint-disable max-lines */
2-
import { Integration, PolymorphicRequest, Transaction } from '@sentry/types';
2+
import { Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types';
33
import { extractPathForTransaction, getNumberOfUrlSegments, isRegExp, logger } from '@sentry/utils';
44

5+
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
6+
57
type Method =
68
| 'all'
79
| 'get'
@@ -105,11 +107,17 @@ export class Express implements Integration {
105107
/**
106108
* @inheritDoc
107109
*/
108-
public setupOnce(): void {
110+
public setupOnce(_: unknown, getCurrentHub: () => Hub): void {
109111
if (!this._router) {
110112
__DEBUG_BUILD__ && logger.error('ExpressIntegration is missing an Express instance');
111113
return;
112114
}
115+
116+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
117+
__DEBUG_BUILD__ && logger.log('Express Integration is skipped because of instrumenter configuration.');
118+
return;
119+
}
120+
113121
instrumentMiddlewares(this._router, this._methods);
114122
instrumentRouter(this._router as ExpressRouter);
115123
}

packages/tracing/src/integrations/node/graphql.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Hub } from '@sentry/core';
22
import { EventProcessor, Integration } from '@sentry/types';
33
import { fill, isThenable, loadModule, logger } from '@sentry/utils';
44

5+
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
6+
57
/** Tracing integration for graphql package */
68
export class GraphQL implements Integration {
79
/**
@@ -27,6 +29,11 @@ export class GraphQL implements Integration {
2729
return;
2830
}
2931

32+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
33+
__DEBUG_BUILD__ && logger.log('GraphQL Integration is skipped because of instrumenter configuration.');
34+
return;
35+
}
36+
3037
fill(pkg, 'execute', function (orig: () => void | Promise<unknown>) {
3138
return function (this: unknown, ...args: unknown[]) {
3239
const scope = getCurrentHub().getScope();

packages/tracing/src/integrations/node/mongo.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Hub } from '@sentry/core';
22
import { EventProcessor, Integration, SpanContext } from '@sentry/types';
33
import { fill, isThenable, loadModule, logger } from '@sentry/utils';
44

5+
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
6+
57
// This allows us to use the same array for both defaults options and the type itself.
68
// (note `as const` at the end to make it a union of string literal types (i.e. "a" | "b" | ... )
79
// and not just a string[])
@@ -125,6 +127,11 @@ export class Mongo implements Integration {
125127
return;
126128
}
127129

130+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
131+
__DEBUG_BUILD__ && logger.log('Mongo Integration is skipped because of instrumenter configuration.');
132+
return;
133+
}
134+
128135
this._instrumentOperations(pkg.Collection, this._operations, getCurrentHub);
129136
}
130137

packages/tracing/src/integrations/node/mysql.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Hub } from '@sentry/core';
22
import { EventProcessor, Integration } from '@sentry/types';
33
import { fill, loadModule, logger } from '@sentry/utils';
44

5+
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
6+
57
interface MysqlConnection {
68
createQuery: () => void;
79
}
@@ -29,6 +31,11 @@ export class Mysql implements Integration {
2931
return;
3032
}
3133

34+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
35+
__DEBUG_BUILD__ && logger.log('Mysql Integration is skipped because of instrumenter configuration.');
36+
return;
37+
}
38+
3239
// The original function will have one of these signatures:
3340
// function (callback) => void
3441
// function (options, callback) => void

packages/tracing/src/integrations/node/postgres.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Hub } from '@sentry/core';
22
import { EventProcessor, Integration } from '@sentry/types';
33
import { fill, isThenable, loadModule, logger } from '@sentry/utils';
44

5+
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
6+
57
interface PgClient {
68
prototype: {
79
query: () => void | Promise<unknown>;
@@ -41,6 +43,11 @@ export class Postgres implements Integration {
4143
return;
4244
}
4345

46+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
47+
__DEBUG_BUILD__ && logger.log('Postgres Integration is skipped because of instrumenter configuration.');
48+
return;
49+
}
50+
4451
if (this._usePgNative && !pkg.native?.Client) {
4552
__DEBUG_BUILD__ && logger.error("Postgres Integration was unable to access 'pg-native' bindings.");
4653
return;

packages/tracing/src/integrations/node/prisma.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Hub } from '@sentry/core';
22
import { EventProcessor, Integration } from '@sentry/types';
33
import { isThenable, logger } from '@sentry/utils';
44

5+
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
6+
57
type PrismaAction =
68
| 'findUnique'
79
| 'findMany'
@@ -80,6 +82,11 @@ export class Prisma implements Integration {
8082
return;
8183
}
8284

85+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
86+
__DEBUG_BUILD__ && logger.log('Prisma Integration is skipped because of instrumenter configuration.');
87+
return;
88+
}
89+
8390
this._client.$use((params, next: (params: PrismaMiddlewareParams) => Promise<unknown>) => {
8491
const scope = getCurrentHub().getScope();
8592
const parentSpan = scope?.getSpan();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Hub } from '@sentry/types';
2+
3+
/**
4+
* Check if Sentry auto-instrumentation should be disabled.
5+
*
6+
* @param getCurrentHub A method to fetch the current hub
7+
* @returns boolean
8+
*/
9+
export function shouldDisableAutoInstrumentation(getCurrentHub: () => Hub): boolean {
10+
const clientOptions = getCurrentHub().getClient()?.getOptions();
11+
const instrumenter = clientOptions?.instrumenter || 'sentry';
12+
13+
return instrumenter !== 'sentry';
14+
}

packages/tracing/test/integrations/apollo.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/* eslint-disable @typescript-eslint/unbound-method */
22
import { Hub, Scope } from '@sentry/core';
3+
import { logger } from '@sentry/utils';
34

45
import { Apollo } from '../../src/integrations/node/apollo';
56
import { Span } from '../../src/span';
7+
import { getTestClient } from '../testutils';
68

79
type ApolloResolverGroup = {
810
[key: string]: () => any;
@@ -100,4 +102,19 @@ describe('setupOnce', () => {
100102
});
101103
expect(childSpan.finish).toBeCalled();
102104
});
105+
106+
it("doesn't attach when using otel instrumenter", () => {
107+
const loggerLogSpy = jest.spyOn(logger, 'log');
108+
109+
const client = getTestClient({ instrumenter: 'otel' });
110+
const hub = new Hub(client);
111+
112+
const integration = new Apollo();
113+
integration.setupOnce(
114+
() => {},
115+
() => hub,
116+
);
117+
118+
expect(loggerLogSpy).toBeCalledWith('Apollo Integration is skipped because of instrumenter configuration.');
119+
});
103120
});

packages/tracing/test/integrations/graphql.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/* eslint-disable @typescript-eslint/unbound-method */
22
import { Hub, Scope } from '@sentry/core';
3+
import { logger } from '@sentry/utils';
34

45
import { GraphQL } from '../../src/integrations/node/graphql';
56
import { Span } from '../../src/span';
7+
import { getTestClient } from '../testutils';
68

79
const GQLExecute = {
810
execute() {
@@ -53,4 +55,19 @@ describe('setupOnce', () => {
5355
expect(childSpan.finish).toBeCalled();
5456
expect(scope.setSpan).toHaveBeenCalledTimes(2);
5557
});
58+
59+
it("doesn't attach when using otel instrumenter", () => {
60+
const loggerLogSpy = jest.spyOn(logger, 'log');
61+
62+
const client = getTestClient({ instrumenter: 'otel' });
63+
const hub = new Hub(client);
64+
65+
const integration = new GraphQL();
66+
integration.setupOnce(
67+
() => {},
68+
() => hub,
69+
);
70+
71+
expect(loggerLogSpy).toBeCalledWith('GraphQL Integration is skipped because of instrumenter configuration.');
72+
});
5673
});

packages/tracing/test/integrations/node/mongo.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/* eslint-disable @typescript-eslint/unbound-method */
22
import { Hub, Scope } from '@sentry/core';
3+
import { logger } from '@sentry/utils';
34

45
import { Mongo } from '../../../src/integrations/node/mongo';
56
import { Span } from '../../../src/span';
7+
import { getTestClient } from '../../testutils';
68

79
class Collection {
810
public collectionName: string = 'mockedCollectionName';
@@ -111,4 +113,19 @@ describe('patchOperation()', () => {
111113
});
112114
expect(childSpan.finish).toBeCalled();
113115
});
116+
117+
it("doesn't attach when using otel instrumenter", () => {
118+
const loggerLogSpy = jest.spyOn(logger, 'log');
119+
120+
const client = getTestClient({ instrumenter: 'otel' });
121+
const hub = new Hub(client);
122+
123+
const integration = new Mongo();
124+
integration.setupOnce(
125+
() => {},
126+
() => hub,
127+
);
128+
129+
expect(loggerLogSpy).toBeCalledWith('Mongo Integration is skipped because of instrumenter configuration.');
130+
});
114131
});

packages/tracing/test/integrations/node/postgres.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
/* eslint-disable @typescript-eslint/unbound-method */
22
import { Hub, Scope } from '@sentry/core';
3+
import { logger } from '@sentry/utils';
34

45
import { Postgres } from '../../../src/integrations/node/postgres';
56
import { Span } from '../../../src/span';
7+
import { getTestClient } from '../../testutils';
68

79
class PgClient {
810
// https://node-postgres.com/api/client#clientquery
@@ -94,4 +96,19 @@ describe('setupOnce', () => {
9496
expect(childSpan.finish).toBeCalled();
9597
});
9698
});
99+
100+
it("doesn't attach when using otel instrumenter", () => {
101+
const loggerLogSpy = jest.spyOn(logger, 'log');
102+
103+
const client = getTestClient({ instrumenter: 'otel' });
104+
const hub = new Hub(client);
105+
106+
const integration = new Postgres();
107+
integration.setupOnce(
108+
() => {},
109+
() => hub,
110+
);
111+
112+
expect(loggerLogSpy).toBeCalledWith('Postgres Integration is skipped because of instrumenter configuration.');
113+
});
97114
});

0 commit comments

Comments
 (0)