diff --git a/packages/node/src/tracing/index.ts b/packages/node/src/tracing/index.ts index 15c4e2889b3f..2b4be9d41e70 100644 --- a/packages/node/src/tracing/index.ts +++ b/packages/node/src/tracing/index.ts @@ -1,3 +1,4 @@ +import type { LazyLoadedIntegration } from '@sentry-internal/tracing'; import { lazyLoadedNodePerformanceMonitoringIntegrations } from '@sentry-internal/tracing'; import type { Integration } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -14,11 +15,12 @@ export function autoDiscoverNodePerformanceMonitoringIntegrations(): Integration return undefined; } }) - .filter(integration => !!integration) as Integration[]; + .filter(integration => !!integration) as LazyLoadedIntegration[]; if (loadedIntegrations.length === 0) { logger.warn('Performance monitoring integrations could not be automatically loaded.'); } - return loadedIntegrations; + // Only return integrations where their dependencies loaded successfully. + return loadedIntegrations.filter(integration => !!integration.loadDependency()); } diff --git a/packages/tracing-internal/src/index.ts b/packages/tracing-internal/src/index.ts index c4e17c25294f..da9b94aac666 100644 --- a/packages/tracing-internal/src/index.ts +++ b/packages/tracing-internal/src/index.ts @@ -10,6 +10,7 @@ export { Prisma, lazyLoadedNodePerformanceMonitoringIntegrations, } from './node'; +export type { LazyLoadedIntegration } from './node'; export { BrowserTracing, diff --git a/packages/tracing-internal/src/node/integrations/apollo.ts b/packages/tracing-internal/src/node/integrations/apollo.ts index 3a076444af5e..5a9c0219a00c 100644 --- a/packages/tracing-internal/src/node/integrations/apollo.ts +++ b/packages/tracing-internal/src/node/integrations/apollo.ts @@ -1,7 +1,8 @@ import type { Hub } from '@sentry/core'; -import type { EventProcessor, Integration } from '@sentry/types'; +import type { EventProcessor } from '@sentry/types'; import { arrayify, fill, isThenable, loadModule, logger } from '@sentry/utils'; +import type { LazyLoadedIntegration } from './lazy'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; interface ApolloOptions { @@ -16,8 +17,24 @@ type ApolloModelResolvers = { [key: string]: ApolloResolverGroup; }; +type GraphQLModule = { + GraphQLFactory: { + prototype: { + create: (resolvers: ApolloModelResolvers[]) => unknown; + }; + }; +}; + +type ApolloModule = { + ApolloServerBase: { + prototype: { + constructSchema: (config: unknown) => unknown; + }; + }; +}; + /** Tracing integration for Apollo */ -export class Apollo implements Integration { +export class Apollo implements LazyLoadedIntegration { /** * @inheritDoc */ @@ -30,6 +47,8 @@ export class Apollo implements Integration { private readonly _useNest: boolean; + private _module?: GraphQLModule & ApolloModule; + /** * @inheritDoc */ @@ -41,6 +60,17 @@ export class Apollo implements Integration { this._useNest = !!options.useNestjs; } + /** @inheritdoc */ + public loadDependency(): (GraphQLModule & ApolloModule) | undefined { + if (this._useNest) { + this._module = this._module || loadModule('@nestjs/graphql'); + } else { + this._module = this._module || loadModule('apollo-server-core'); + } + + return this._module; + } + /** * @inheritDoc */ @@ -51,13 +81,7 @@ export class Apollo implements Integration { } if (this._useNest) { - const pkg = loadModule<{ - GraphQLFactory: { - prototype: { - create: (resolvers: ApolloModelResolvers[]) => unknown; - }; - }; - }>('@nestjs/graphql'); + const pkg = this.loadDependency(); if (!pkg) { __DEBUG_BUILD__ && logger.error('Apollo-NestJS Integration was unable to require @nestjs/graphql package.'); @@ -90,13 +114,7 @@ export class Apollo implements Integration { }, ); } else { - const pkg = loadModule<{ - ApolloServerBase: { - prototype: { - constructSchema: (config: unknown) => unknown; - }; - }; - }>('apollo-server-core'); + const pkg = this.loadDependency(); if (!pkg) { __DEBUG_BUILD__ && logger.error('Apollo Integration was unable to require apollo-server-core package.'); diff --git a/packages/tracing-internal/src/node/integrations/graphql.ts b/packages/tracing-internal/src/node/integrations/graphql.ts index 12f04b7c1e57..027419ae8ce8 100644 --- a/packages/tracing-internal/src/node/integrations/graphql.ts +++ b/packages/tracing-internal/src/node/integrations/graphql.ts @@ -1,11 +1,16 @@ import type { Hub } from '@sentry/core'; -import type { EventProcessor, Integration } from '@sentry/types'; +import type { EventProcessor } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; +import type { LazyLoadedIntegration } from './lazy'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; +type GraphQLModule = { + [method: string]: (...args: unknown[]) => unknown; +}; + /** Tracing integration for graphql package */ -export class GraphQL implements Integration { +export class GraphQL implements LazyLoadedIntegration { /** * @inheritDoc */ @@ -16,6 +21,13 @@ export class GraphQL implements Integration { */ public name: string = GraphQL.id; + private _module?: GraphQLModule; + + /** @inheritdoc */ + public loadDependency(): GraphQLModule | undefined { + return (this._module = this._module || loadModule('graphql/execution/execute.js')); + } + /** * @inheritDoc */ @@ -25,9 +37,7 @@ export class GraphQL implements Integration { return; } - const pkg = loadModule<{ - [method: string]: (...args: unknown[]) => unknown; - }>('graphql/execution/execute.js'); + const pkg = this.loadDependency(); if (!pkg) { __DEBUG_BUILD__ && logger.error('GraphQL Integration was unable to require graphql/execution package.'); diff --git a/packages/tracing-internal/src/node/integrations/lazy.ts b/packages/tracing-internal/src/node/integrations/lazy.ts index f53ff756cd48..635f5b082bee 100644 --- a/packages/tracing-internal/src/node/integrations/lazy.ts +++ b/packages/tracing-internal/src/node/integrations/lazy.ts @@ -1,46 +1,55 @@ import type { Integration, IntegrationClass } from '@sentry/types'; import { dynamicRequire } from '@sentry/utils'; -export const lazyLoadedNodePerformanceMonitoringIntegrations: (() => Integration)[] = [ +export interface LazyLoadedIntegration extends Integration { + /** + * Loads the integration's dependency and caches it so it doesn't have to be loaded again. + * + * If this returns undefined, the dependency could not be loaded. + */ + loadDependency(): T | undefined; +} + +export const lazyLoadedNodePerformanceMonitoringIntegrations: (() => LazyLoadedIntegration)[] = [ () => { const integration = dynamicRequire(module, './apollo') as { - Apollo: IntegrationClass; + Apollo: IntegrationClass; }; return new integration.Apollo(); }, () => { const integration = dynamicRequire(module, './apollo') as { - Apollo: IntegrationClass; + Apollo: IntegrationClass; }; return new integration.Apollo({ useNestjs: true }); }, () => { const integration = dynamicRequire(module, './graphql') as { - GraphQL: IntegrationClass; + GraphQL: IntegrationClass; }; return new integration.GraphQL(); }, () => { const integration = dynamicRequire(module, './mongo') as { - Mongo: IntegrationClass; + Mongo: IntegrationClass; }; return new integration.Mongo(); }, () => { const integration = dynamicRequire(module, './mongo') as { - Mongo: IntegrationClass; + Mongo: IntegrationClass; }; return new integration.Mongo({ mongoose: true }); }, () => { const integration = dynamicRequire(module, './mysql') as { - Mysql: IntegrationClass; + Mysql: IntegrationClass; }; return new integration.Mysql(); }, () => { const integration = dynamicRequire(module, './postgres') as { - Postgres: IntegrationClass; + Postgres: IntegrationClass; }; return new integration.Postgres(); }, diff --git a/packages/tracing-internal/src/node/integrations/mongo.ts b/packages/tracing-internal/src/node/integrations/mongo.ts index 37335358c82e..5364f234df8d 100644 --- a/packages/tracing-internal/src/node/integrations/mongo.ts +++ b/packages/tracing-internal/src/node/integrations/mongo.ts @@ -1,7 +1,8 @@ import type { Hub } from '@sentry/core'; -import type { EventProcessor, Integration, SpanContext } from '@sentry/types'; +import type { EventProcessor, SpanContext } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; +import type { LazyLoadedIntegration } from './lazy'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; // This allows us to use the same array for both defaults options and the type itself. @@ -98,8 +99,10 @@ function isCursor(maybeCursor: MongoCursor): maybeCursor is MongoCursor { return maybeCursor && typeof maybeCursor === 'object' && maybeCursor.once && typeof maybeCursor.once === 'function'; } +type MongoModule = { Collection: MongoCollection }; + /** Tracing integration for mongo package */ -export class Mongo implements Integration { +export class Mongo implements LazyLoadedIntegration { /** * @inheritDoc */ @@ -114,6 +117,8 @@ export class Mongo implements Integration { private _describeOperations?: boolean | Operation[]; private _useMongoose: boolean; + private _module?: MongoModule; + /** * @inheritDoc */ @@ -123,6 +128,12 @@ export class Mongo implements Integration { this._useMongoose = !!options.useMongoose; } + /** @inheritdoc */ + public loadDependency(): MongoModule | undefined { + const moduleName = this._useMongoose ? 'mongoose' : 'mongodb'; + return (this._module = this._module || loadModule(moduleName)); + } + /** * @inheritDoc */ @@ -132,10 +143,10 @@ export class Mongo implements Integration { return; } - const moduleName = this._useMongoose ? 'mongoose' : 'mongodb'; - const pkg = loadModule<{ Collection: MongoCollection }>(moduleName); + const pkg = this.loadDependency(); if (!pkg) { + const moduleName = this._useMongoose ? 'mongoose' : 'mongodb'; __DEBUG_BUILD__ && logger.error(`Mongo Integration was unable to require \`${moduleName}\` package.`); return; } diff --git a/packages/tracing-internal/src/node/integrations/mysql.ts b/packages/tracing-internal/src/node/integrations/mysql.ts index 6e6a80ac59a6..529e3b859bc2 100644 --- a/packages/tracing-internal/src/node/integrations/mysql.ts +++ b/packages/tracing-internal/src/node/integrations/mysql.ts @@ -1,7 +1,8 @@ import type { Hub } from '@sentry/core'; -import type { EventProcessor, Integration } from '@sentry/types'; +import type { EventProcessor } from '@sentry/types'; import { fill, loadModule, logger } from '@sentry/utils'; +import type { LazyLoadedIntegration } from './lazy'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; interface MysqlConnection { @@ -9,7 +10,7 @@ interface MysqlConnection { } /** Tracing integration for node-mysql package */ -export class Mysql implements Integration { +export class Mysql implements LazyLoadedIntegration { /** * @inheritDoc */ @@ -20,6 +21,13 @@ export class Mysql implements Integration { */ public name: string = Mysql.id; + private _module?: MysqlConnection; + + /** @inheritdoc */ + public loadDependency(): MysqlConnection | undefined { + return (this._module = this._module || loadModule('mysql/lib/Connection.js')); + } + /** * @inheritDoc */ @@ -29,7 +37,7 @@ export class Mysql implements Integration { return; } - const pkg = loadModule('mysql/lib/Connection.js'); + const pkg = this.loadDependency(); if (!pkg) { __DEBUG_BUILD__ && logger.error('Mysql Integration was unable to require `mysql` package.'); diff --git a/packages/tracing-internal/src/node/integrations/postgres.ts b/packages/tracing-internal/src/node/integrations/postgres.ts index 41ad31b20660..be7e299cf6aa 100644 --- a/packages/tracing-internal/src/node/integrations/postgres.ts +++ b/packages/tracing-internal/src/node/integrations/postgres.ts @@ -1,7 +1,8 @@ import type { Hub } from '@sentry/core'; -import type { EventProcessor, Integration } from '@sentry/types'; +import type { EventProcessor } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; +import type { LazyLoadedIntegration } from './lazy'; import { shouldDisableAutoInstrumentation } from './utils/node-utils'; interface PgClient { @@ -14,8 +15,10 @@ interface PgOptions { usePgNative?: boolean; } +type PGModule = { Client: PgClient; native: { Client: PgClient } }; + /** Tracing integration for node-postgres package */ -export class Postgres implements Integration { +export class Postgres implements LazyLoadedIntegration { /** * @inheritDoc */ @@ -28,10 +31,17 @@ export class Postgres implements Integration { private _usePgNative: boolean; + private _module?: PGModule; + public constructor(options: PgOptions = {}) { this._usePgNative = !!options.usePgNative; } + /** @inheritdoc */ + public loadDependency(): PGModule | undefined { + return (this._module = this._module || loadModule('pg')); + } + /** * @inheritDoc */ @@ -41,7 +51,7 @@ export class Postgres implements Integration { return; } - const pkg = loadModule<{ Client: PgClient; native: { Client: PgClient } }>('pg'); + const pkg = this.loadDependency(); if (!pkg) { __DEBUG_BUILD__ && logger.error('Postgres Integration was unable to require `pg` package.');