diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 0f7465a91be4..7433ad8720dd 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -37,7 +37,12 @@ export function tracingHandler(): ( const hub = getCurrentHub(); const options = hub.getClient()?.getOptions(); - if (!options || req.method?.toUpperCase() === 'OPTIONS' || req.method?.toUpperCase() === 'HEAD') { + if ( + !options || + options.instrumenter !== 'sentry' || + req.method?.toUpperCase() === 'OPTIONS' || + req.method?.toUpperCase() === 'HEAD' + ) { return next(); } diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 96f6c3f9dbd7..4cdd58f7ee25 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -69,6 +69,13 @@ export class Http implements Integration { } const clientOptions = setupOnceGetCurrentHub().getClient()?.getOptions(); + + // Do not auto-instrument for other instrumenter + if (clientOptions && clientOptions.instrumenter !== 'sentry') { + __DEBUG_BUILD__ && logger.log('HTTP Integration is skipped because of instrumenter configuration.'); + return; + } + const wrappedHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, this._tracing, clientOptions); // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/packages/node/test/helper/node-client-options.ts b/packages/node/test/helper/node-client-options.ts index 8831af6a2896..d6973e4a437d 100644 --- a/packages/node/test/helper/node-client-options.ts +++ b/packages/node/test/helper/node-client-options.ts @@ -8,6 +8,7 @@ export function getDefaultNodeClientOptions(options: Partial integrations: [], transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), stackParser: () => [], + instrumenter: 'sentry', ...options, }; } diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index 4f5205f0b596..6bf8a7117327 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -2,7 +2,7 @@ import * as sentryCore from '@sentry/core'; import { Hub } from '@sentry/core'; import { addExtensionMethods, Span, TRACEPARENT_REGEXP, Transaction } from '@sentry/tracing'; import { TransactionContext } from '@sentry/types'; -import { parseSemver } from '@sentry/utils'; +import { logger, parseSemver } from '@sentry/utils'; import * as http from 'http'; import * as https from 'https'; import * as HttpsProxyAgent from 'https-proxy-agent'; @@ -188,6 +188,28 @@ describe('tracing', () => { expect(transaction.metadata.propagations).toBe(2); }); + it("doesn't attach when using otel instrumenter", () => { + const loggerLogSpy = jest.spyOn(logger, 'log'); + + const options = getDefaultNodeClientOptions({ + dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012', + tracesSampleRate: 1.0, + integrations: [new HttpIntegration({ tracing: true })], + release: '1.0.0', + environment: 'production', + instrumenter: 'otel', + }); + const hub = new Hub(new NodeClient(options)); + + const integration = new HttpIntegration(); + integration.setupOnce( + () => {}, + () => hub, + ); + + expect(loggerLogSpy).toBeCalledWith('HTTP Integration is skipped because of instrumenter configuration.'); + }); + describe('tracePropagationTargets option', () => { beforeEach(() => { // hacky way of restoring monkey patched functions diff --git a/packages/tracing/src/integrations/node/apollo.ts b/packages/tracing/src/integrations/node/apollo.ts index 8202164a0fec..e505899df9f2 100644 --- a/packages/tracing/src/integrations/node/apollo.ts +++ b/packages/tracing/src/integrations/node/apollo.ts @@ -2,6 +2,8 @@ import { Hub } from '@sentry/core'; import { EventProcessor, Integration } from '@sentry/types'; import { arrayify, fill, isThenable, loadModule, logger } from '@sentry/utils'; +import { shouldDisableAutoInstrumentation } from './utils/node-utils'; + type ApolloResolverGroup = { [key: string]: () => unknown; }; @@ -39,6 +41,11 @@ export class Apollo implements Integration { return; } + if (shouldDisableAutoInstrumentation(getCurrentHub)) { + __DEBUG_BUILD__ && logger.log('Apollo Integration is skipped because of instrumenter configuration.'); + return; + } + /** * Iterate over resolvers of the ApolloServer instance before schemas are constructed. */ diff --git a/packages/tracing/src/integrations/node/express.ts b/packages/tracing/src/integrations/node/express.ts index 495832a8334b..24260f627c0f 100644 --- a/packages/tracing/src/integrations/node/express.ts +++ b/packages/tracing/src/integrations/node/express.ts @@ -1,7 +1,9 @@ /* eslint-disable max-lines */ -import { Integration, PolymorphicRequest, Transaction } from '@sentry/types'; +import { Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types'; import { extractPathForTransaction, getNumberOfUrlSegments, isRegExp, logger } from '@sentry/utils'; +import { shouldDisableAutoInstrumentation } from './utils/node-utils'; + type Method = | 'all' | 'get' @@ -105,11 +107,17 @@ export class Express implements Integration { /** * @inheritDoc */ - public setupOnce(): void { + public setupOnce(_: unknown, getCurrentHub: () => Hub): void { if (!this._router) { __DEBUG_BUILD__ && logger.error('ExpressIntegration is missing an Express instance'); return; } + + if (shouldDisableAutoInstrumentation(getCurrentHub)) { + __DEBUG_BUILD__ && logger.log('Express Integration is skipped because of instrumenter configuration.'); + return; + } + instrumentMiddlewares(this._router, this._methods); instrumentRouter(this._router as ExpressRouter); } diff --git a/packages/tracing/src/integrations/node/graphql.ts b/packages/tracing/src/integrations/node/graphql.ts index cdb073779516..4e777459cd80 100644 --- a/packages/tracing/src/integrations/node/graphql.ts +++ b/packages/tracing/src/integrations/node/graphql.ts @@ -2,6 +2,8 @@ import { Hub } from '@sentry/core'; import { EventProcessor, Integration } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; +import { shouldDisableAutoInstrumentation } from './utils/node-utils'; + /** Tracing integration for graphql package */ export class GraphQL implements Integration { /** @@ -27,6 +29,11 @@ export class GraphQL implements Integration { return; } + if (shouldDisableAutoInstrumentation(getCurrentHub)) { + __DEBUG_BUILD__ && logger.log('GraphQL Integration is skipped because of instrumenter configuration.'); + return; + } + fill(pkg, 'execute', function (orig: () => void | Promise) { return function (this: unknown, ...args: unknown[]) { const scope = getCurrentHub().getScope(); diff --git a/packages/tracing/src/integrations/node/mongo.ts b/packages/tracing/src/integrations/node/mongo.ts index b580fc33bd1e..e19711d12460 100644 --- a/packages/tracing/src/integrations/node/mongo.ts +++ b/packages/tracing/src/integrations/node/mongo.ts @@ -2,6 +2,8 @@ import { Hub } from '@sentry/core'; import { EventProcessor, Integration, SpanContext } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; +import { shouldDisableAutoInstrumentation } from './utils/node-utils'; + // This allows us to use the same array for both defaults options and the type itself. // (note `as const` at the end to make it a union of string literal types (i.e. "a" | "b" | ... ) // and not just a string[]) @@ -125,6 +127,11 @@ export class Mongo implements Integration { return; } + if (shouldDisableAutoInstrumentation(getCurrentHub)) { + __DEBUG_BUILD__ && logger.log('Mongo Integration is skipped because of instrumenter configuration.'); + return; + } + this._instrumentOperations(pkg.Collection, this._operations, getCurrentHub); } diff --git a/packages/tracing/src/integrations/node/mysql.ts b/packages/tracing/src/integrations/node/mysql.ts index deacfedc8209..a9e38cd1c0e7 100644 --- a/packages/tracing/src/integrations/node/mysql.ts +++ b/packages/tracing/src/integrations/node/mysql.ts @@ -2,6 +2,8 @@ import { Hub } from '@sentry/core'; import { EventProcessor, Integration } from '@sentry/types'; import { fill, loadModule, logger } from '@sentry/utils'; +import { shouldDisableAutoInstrumentation } from './utils/node-utils'; + interface MysqlConnection { createQuery: () => void; } @@ -29,6 +31,11 @@ export class Mysql implements Integration { return; } + if (shouldDisableAutoInstrumentation(getCurrentHub)) { + __DEBUG_BUILD__ && logger.log('Mysql Integration is skipped because of instrumenter configuration.'); + return; + } + // The original function will have one of these signatures: // function (callback) => void // function (options, callback) => void diff --git a/packages/tracing/src/integrations/node/postgres.ts b/packages/tracing/src/integrations/node/postgres.ts index 516c9dbe1ea7..6ed0f46c5dd7 100644 --- a/packages/tracing/src/integrations/node/postgres.ts +++ b/packages/tracing/src/integrations/node/postgres.ts @@ -2,6 +2,8 @@ import { Hub } from '@sentry/core'; import { EventProcessor, Integration } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; +import { shouldDisableAutoInstrumentation } from './utils/node-utils'; + interface PgClient { prototype: { query: () => void | Promise; @@ -41,6 +43,11 @@ export class Postgres implements Integration { return; } + if (shouldDisableAutoInstrumentation(getCurrentHub)) { + __DEBUG_BUILD__ && logger.log('Postgres Integration is skipped because of instrumenter configuration.'); + return; + } + if (this._usePgNative && !pkg.native?.Client) { __DEBUG_BUILD__ && logger.error("Postgres Integration was unable to access 'pg-native' bindings."); return; diff --git a/packages/tracing/src/integrations/node/prisma.ts b/packages/tracing/src/integrations/node/prisma.ts index 9f743ca0f66e..371052217911 100644 --- a/packages/tracing/src/integrations/node/prisma.ts +++ b/packages/tracing/src/integrations/node/prisma.ts @@ -2,6 +2,8 @@ import { Hub } from '@sentry/core'; import { EventProcessor, Integration } from '@sentry/types'; import { isThenable, logger } from '@sentry/utils'; +import { shouldDisableAutoInstrumentation } from './utils/node-utils'; + type PrismaAction = | 'findUnique' | 'findMany' @@ -80,6 +82,11 @@ export class Prisma implements Integration { return; } + if (shouldDisableAutoInstrumentation(getCurrentHub)) { + __DEBUG_BUILD__ && logger.log('Prisma Integration is skipped because of instrumenter configuration.'); + return; + } + this._client.$use((params, next: (params: PrismaMiddlewareParams) => Promise) => { const scope = getCurrentHub().getScope(); const parentSpan = scope?.getSpan(); diff --git a/packages/tracing/src/integrations/node/utils/node-utils.ts b/packages/tracing/src/integrations/node/utils/node-utils.ts new file mode 100644 index 000000000000..9cb6970c9ebc --- /dev/null +++ b/packages/tracing/src/integrations/node/utils/node-utils.ts @@ -0,0 +1,14 @@ +import { Hub } from '@sentry/types'; + +/** + * Check if Sentry auto-instrumentation should be disabled. + * + * @param getCurrentHub A method to fetch the current hub + * @returns boolean + */ +export function shouldDisableAutoInstrumentation(getCurrentHub: () => Hub): boolean { + const clientOptions = getCurrentHub().getClient()?.getOptions(); + const instrumenter = clientOptions?.instrumenter || 'sentry'; + + return instrumenter !== 'sentry'; +} diff --git a/packages/tracing/test/integrations/apollo.test.ts b/packages/tracing/test/integrations/apollo.test.ts index 2f8e5c843d07..8b9946ba4a70 100644 --- a/packages/tracing/test/integrations/apollo.test.ts +++ b/packages/tracing/test/integrations/apollo.test.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { Hub, Scope } from '@sentry/core'; +import { logger } from '@sentry/utils'; import { Apollo } from '../../src/integrations/node/apollo'; import { Span } from '../../src/span'; +import { getTestClient } from '../testutils'; type ApolloResolverGroup = { [key: string]: () => any; @@ -100,4 +102,19 @@ describe('setupOnce', () => { }); expect(childSpan.finish).toBeCalled(); }); + + it("doesn't attach when using otel instrumenter", () => { + const loggerLogSpy = jest.spyOn(logger, 'log'); + + const client = getTestClient({ instrumenter: 'otel' }); + const hub = new Hub(client); + + const integration = new Apollo(); + integration.setupOnce( + () => {}, + () => hub, + ); + + expect(loggerLogSpy).toBeCalledWith('Apollo Integration is skipped because of instrumenter configuration.'); + }); }); diff --git a/packages/tracing/test/integrations/graphql.test.ts b/packages/tracing/test/integrations/graphql.test.ts index e074dfc602f8..265bbe0fb0d8 100644 --- a/packages/tracing/test/integrations/graphql.test.ts +++ b/packages/tracing/test/integrations/graphql.test.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { Hub, Scope } from '@sentry/core'; +import { logger } from '@sentry/utils'; import { GraphQL } from '../../src/integrations/node/graphql'; import { Span } from '../../src/span'; +import { getTestClient } from '../testutils'; const GQLExecute = { execute() { @@ -53,4 +55,19 @@ describe('setupOnce', () => { expect(childSpan.finish).toBeCalled(); expect(scope.setSpan).toHaveBeenCalledTimes(2); }); + + it("doesn't attach when using otel instrumenter", () => { + const loggerLogSpy = jest.spyOn(logger, 'log'); + + const client = getTestClient({ instrumenter: 'otel' }); + const hub = new Hub(client); + + const integration = new GraphQL(); + integration.setupOnce( + () => {}, + () => hub, + ); + + expect(loggerLogSpy).toBeCalledWith('GraphQL Integration is skipped because of instrumenter configuration.'); + }); }); diff --git a/packages/tracing/test/integrations/node/mongo.test.ts b/packages/tracing/test/integrations/node/mongo.test.ts index b7417a6ae9b2..e362db6f0c47 100644 --- a/packages/tracing/test/integrations/node/mongo.test.ts +++ b/packages/tracing/test/integrations/node/mongo.test.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { Hub, Scope } from '@sentry/core'; +import { logger } from '@sentry/utils'; import { Mongo } from '../../../src/integrations/node/mongo'; import { Span } from '../../../src/span'; +import { getTestClient } from '../../testutils'; class Collection { public collectionName: string = 'mockedCollectionName'; @@ -111,4 +113,19 @@ describe('patchOperation()', () => { }); expect(childSpan.finish).toBeCalled(); }); + + it("doesn't attach when using otel instrumenter", () => { + const loggerLogSpy = jest.spyOn(logger, 'log'); + + const client = getTestClient({ instrumenter: 'otel' }); + const hub = new Hub(client); + + const integration = new Mongo(); + integration.setupOnce( + () => {}, + () => hub, + ); + + expect(loggerLogSpy).toBeCalledWith('Mongo Integration is skipped because of instrumenter configuration.'); + }); }); diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts index 4b6c23db41e6..2ef3754b28ef 100644 --- a/packages/tracing/test/integrations/node/postgres.test.ts +++ b/packages/tracing/test/integrations/node/postgres.test.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { Hub, Scope } from '@sentry/core'; +import { logger } from '@sentry/utils'; import { Postgres } from '../../../src/integrations/node/postgres'; import { Span } from '../../../src/span'; +import { getTestClient } from '../../testutils'; class PgClient { // https://node-postgres.com/api/client#clientquery @@ -94,4 +96,19 @@ describe('setupOnce', () => { expect(childSpan.finish).toBeCalled(); }); }); + + it("doesn't attach when using otel instrumenter", () => { + const loggerLogSpy = jest.spyOn(logger, 'log'); + + const client = getTestClient({ instrumenter: 'otel' }); + const hub = new Hub(client); + + const integration = new Postgres(); + integration.setupOnce( + () => {}, + () => hub, + ); + + expect(loggerLogSpy).toBeCalledWith('Postgres Integration is skipped because of instrumenter configuration.'); + }); }); diff --git a/packages/tracing/test/integrations/node/prisma.test.ts b/packages/tracing/test/integrations/node/prisma.test.ts index 3c088ac25834..d2adb685fc9d 100644 --- a/packages/tracing/test/integrations/node/prisma.test.ts +++ b/packages/tracing/test/integrations/node/prisma.test.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { Hub, Scope } from '@sentry/core'; +import { logger } from '@sentry/utils'; import { Prisma } from '../../../src/integrations/node/prisma'; import { Span } from '../../../src/span'; +import { getTestClient } from '../../testutils'; type PrismaMiddleware = (params: unknown, next: (params?: unknown) => Promise) => Promise; @@ -30,7 +32,6 @@ describe('setupOnce', function () { let childSpan: Span; beforeAll(() => { - // @ts-ignore, not to export PrismaClient types from integration source new Prisma({ client: Client }).setupOnce( () => undefined, () => new Hub(undefined, scope), @@ -58,4 +59,19 @@ describe('setupOnce', function () { done(); }); }); + + it("doesn't attach when using otel instrumenter", () => { + const loggerLogSpy = jest.spyOn(logger, 'log'); + + const client = getTestClient({ instrumenter: 'otel' }); + const hub = new Hub(client); + + const integration = new Prisma({ client: Client }); + integration.setupOnce( + () => {}, + () => hub, + ); + + expect(loggerLogSpy).toBeCalledWith('Prisma Integration is skipped because of instrumenter configuration.'); + }); }); diff --git a/packages/tracing/test/testutils.ts b/packages/tracing/test/testutils.ts index 474eb57846f4..4ddd042a1240 100644 --- a/packages/tracing/test/testutils.ts +++ b/packages/tracing/test/testutils.ts @@ -1,5 +1,5 @@ import { createTransport } from '@sentry/browser'; -import { ClientOptions } from '@sentry/types'; +import { Client, ClientOptions } from '@sentry/types'; import { GLOBAL_OBJ, resolvedSyncPromise } from '@sentry/utils'; import { JSDOM } from 'jsdom'; @@ -66,3 +66,19 @@ export function getDefaultBrowserClientOptions(options: Partial = ...options, }; } + +export function getTestClient(options: Partial): Client { + class TestClient { + _options: Partial; + + constructor(options: Partial) { + this._options = options; + } + + getOptions(): Partial { + return this._options; + } + } + + return new TestClient(options) as unknown as Client; +}