Skip to content

Commit e1af1e6

Browse files
timfishAbhiPrasad
andauthored
feat(node): Auto discovery only returns integrations where dependency loads (#7603)
Co-authored-by: Abhijeet Prasad <[email protected]>
1 parent 5abe629 commit e1af1e6

File tree

8 files changed

+110
-41
lines changed

8 files changed

+110
-41
lines changed

packages/node/src/tracing/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { LazyLoadedIntegration } from '@sentry-internal/tracing';
12
import { lazyLoadedNodePerformanceMonitoringIntegrations } from '@sentry-internal/tracing';
23
import type { Integration } from '@sentry/types';
34
import { logger } from '@sentry/utils';
@@ -14,11 +15,12 @@ export function autoDiscoverNodePerformanceMonitoringIntegrations(): Integration
1415
return undefined;
1516
}
1617
})
17-
.filter(integration => !!integration) as Integration[];
18+
.filter(integration => !!integration) as LazyLoadedIntegration[];
1819

1920
if (loadedIntegrations.length === 0) {
2021
logger.warn('Performance monitoring integrations could not be automatically loaded.');
2122
}
2223

23-
return loadedIntegrations;
24+
// Only return integrations where their dependencies loaded successfully.
25+
return loadedIntegrations.filter(integration => !!integration.loadDependency());
2426
}

packages/tracing-internal/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
Prisma,
1111
lazyLoadedNodePerformanceMonitoringIntegrations,
1212
} from './node';
13+
export type { LazyLoadedIntegration } from './node';
1314

1415
export {
1516
BrowserTracing,

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

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

5+
import type { LazyLoadedIntegration } from './lazy';
56
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
67

78
interface ApolloOptions {
@@ -16,8 +17,24 @@ type ApolloModelResolvers = {
1617
[key: string]: ApolloResolverGroup;
1718
};
1819

20+
type GraphQLModule = {
21+
GraphQLFactory: {
22+
prototype: {
23+
create: (resolvers: ApolloModelResolvers[]) => unknown;
24+
};
25+
};
26+
};
27+
28+
type ApolloModule = {
29+
ApolloServerBase: {
30+
prototype: {
31+
constructSchema: (config: unknown) => unknown;
32+
};
33+
};
34+
};
35+
1936
/** Tracing integration for Apollo */
20-
export class Apollo implements Integration {
37+
export class Apollo implements LazyLoadedIntegration<GraphQLModule & ApolloModule> {
2138
/**
2239
* @inheritDoc
2340
*/
@@ -30,6 +47,8 @@ export class Apollo implements Integration {
3047

3148
private readonly _useNest: boolean;
3249

50+
private _module?: GraphQLModule & ApolloModule;
51+
3352
/**
3453
* @inheritDoc
3554
*/
@@ -41,6 +60,17 @@ export class Apollo implements Integration {
4160
this._useNest = !!options.useNestjs;
4261
}
4362

63+
/** @inheritdoc */
64+
public loadDependency(): (GraphQLModule & ApolloModule) | undefined {
65+
if (this._useNest) {
66+
this._module = this._module || loadModule('@nestjs/graphql');
67+
} else {
68+
this._module = this._module || loadModule('apollo-server-core');
69+
}
70+
71+
return this._module;
72+
}
73+
4474
/**
4575
* @inheritDoc
4676
*/
@@ -51,13 +81,7 @@ export class Apollo implements Integration {
5181
}
5282

5383
if (this._useNest) {
54-
const pkg = loadModule<{
55-
GraphQLFactory: {
56-
prototype: {
57-
create: (resolvers: ApolloModelResolvers[]) => unknown;
58-
};
59-
};
60-
}>('@nestjs/graphql');
84+
const pkg = this.loadDependency();
6185

6286
if (!pkg) {
6387
__DEBUG_BUILD__ && logger.error('Apollo-NestJS Integration was unable to require @nestjs/graphql package.');
@@ -90,13 +114,7 @@ export class Apollo implements Integration {
90114
},
91115
);
92116
} else {
93-
const pkg = loadModule<{
94-
ApolloServerBase: {
95-
prototype: {
96-
constructSchema: (config: unknown) => unknown;
97-
};
98-
};
99-
}>('apollo-server-core');
117+
const pkg = this.loadDependency();
100118

101119
if (!pkg) {
102120
__DEBUG_BUILD__ && logger.error('Apollo Integration was unable to require apollo-server-core package.');

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import type { Hub } from '@sentry/core';
2-
import type { EventProcessor, Integration } from '@sentry/types';
2+
import type { EventProcessor } from '@sentry/types';
33
import { fill, isThenable, loadModule, logger } from '@sentry/utils';
44

5+
import type { LazyLoadedIntegration } from './lazy';
56
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
67

8+
type GraphQLModule = {
9+
[method: string]: (...args: unknown[]) => unknown;
10+
};
11+
712
/** Tracing integration for graphql package */
8-
export class GraphQL implements Integration {
13+
export class GraphQL implements LazyLoadedIntegration<GraphQLModule> {
914
/**
1015
* @inheritDoc
1116
*/
@@ -16,6 +21,13 @@ export class GraphQL implements Integration {
1621
*/
1722
public name: string = GraphQL.id;
1823

24+
private _module?: GraphQLModule;
25+
26+
/** @inheritdoc */
27+
public loadDependency(): GraphQLModule | undefined {
28+
return (this._module = this._module || loadModule('graphql/execution/execute.js'));
29+
}
30+
1931
/**
2032
* @inheritDoc
2133
*/
@@ -25,9 +37,7 @@ export class GraphQL implements Integration {
2537
return;
2638
}
2739

28-
const pkg = loadModule<{
29-
[method: string]: (...args: unknown[]) => unknown;
30-
}>('graphql/execution/execute.js');
40+
const pkg = this.loadDependency();
3141

3242
if (!pkg) {
3343
__DEBUG_BUILD__ && logger.error('GraphQL Integration was unable to require graphql/execution package.');

packages/tracing-internal/src/node/integrations/lazy.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,55 @@
11
import type { Integration, IntegrationClass } from '@sentry/types';
22
import { dynamicRequire } from '@sentry/utils';
33

4-
export const lazyLoadedNodePerformanceMonitoringIntegrations: (() => Integration)[] = [
4+
export interface LazyLoadedIntegration<T = object> extends Integration {
5+
/**
6+
* Loads the integration's dependency and caches it so it doesn't have to be loaded again.
7+
*
8+
* If this returns undefined, the dependency could not be loaded.
9+
*/
10+
loadDependency(): T | undefined;
11+
}
12+
13+
export const lazyLoadedNodePerformanceMonitoringIntegrations: (() => LazyLoadedIntegration)[] = [
514
() => {
615
const integration = dynamicRequire(module, './apollo') as {
7-
Apollo: IntegrationClass<Integration>;
16+
Apollo: IntegrationClass<LazyLoadedIntegration>;
817
};
918
return new integration.Apollo();
1019
},
1120
() => {
1221
const integration = dynamicRequire(module, './apollo') as {
13-
Apollo: IntegrationClass<Integration>;
22+
Apollo: IntegrationClass<LazyLoadedIntegration>;
1423
};
1524
return new integration.Apollo({ useNestjs: true });
1625
},
1726
() => {
1827
const integration = dynamicRequire(module, './graphql') as {
19-
GraphQL: IntegrationClass<Integration>;
28+
GraphQL: IntegrationClass<LazyLoadedIntegration>;
2029
};
2130
return new integration.GraphQL();
2231
},
2332
() => {
2433
const integration = dynamicRequire(module, './mongo') as {
25-
Mongo: IntegrationClass<Integration>;
34+
Mongo: IntegrationClass<LazyLoadedIntegration>;
2635
};
2736
return new integration.Mongo();
2837
},
2938
() => {
3039
const integration = dynamicRequire(module, './mongo') as {
31-
Mongo: IntegrationClass<Integration>;
40+
Mongo: IntegrationClass<LazyLoadedIntegration>;
3241
};
3342
return new integration.Mongo({ mongoose: true });
3443
},
3544
() => {
3645
const integration = dynamicRequire(module, './mysql') as {
37-
Mysql: IntegrationClass<Integration>;
46+
Mysql: IntegrationClass<LazyLoadedIntegration>;
3847
};
3948
return new integration.Mysql();
4049
},
4150
() => {
4251
const integration = dynamicRequire(module, './postgres') as {
43-
Postgres: IntegrationClass<Integration>;
52+
Postgres: IntegrationClass<LazyLoadedIntegration>;
4453
};
4554
return new integration.Postgres();
4655
},

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

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

5+
import type { LazyLoadedIntegration } from './lazy';
56
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
67

78
// 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 {
9899
return maybeCursor && typeof maybeCursor === 'object' && maybeCursor.once && typeof maybeCursor.once === 'function';
99100
}
100101

102+
type MongoModule = { Collection: MongoCollection };
103+
101104
/** Tracing integration for mongo package */
102-
export class Mongo implements Integration {
105+
export class Mongo implements LazyLoadedIntegration<MongoModule> {
103106
/**
104107
* @inheritDoc
105108
*/
@@ -114,6 +117,8 @@ export class Mongo implements Integration {
114117
private _describeOperations?: boolean | Operation[];
115118
private _useMongoose: boolean;
116119

120+
private _module?: MongoModule;
121+
117122
/**
118123
* @inheritDoc
119124
*/
@@ -123,6 +128,12 @@ export class Mongo implements Integration {
123128
this._useMongoose = !!options.useMongoose;
124129
}
125130

131+
/** @inheritdoc */
132+
public loadDependency(): MongoModule | undefined {
133+
const moduleName = this._useMongoose ? 'mongoose' : 'mongodb';
134+
return (this._module = this._module || loadModule(moduleName));
135+
}
136+
126137
/**
127138
* @inheritDoc
128139
*/
@@ -132,10 +143,10 @@ export class Mongo implements Integration {
132143
return;
133144
}
134145

135-
const moduleName = this._useMongoose ? 'mongoose' : 'mongodb';
136-
const pkg = loadModule<{ Collection: MongoCollection }>(moduleName);
146+
const pkg = this.loadDependency();
137147

138148
if (!pkg) {
149+
const moduleName = this._useMongoose ? 'mongoose' : 'mongodb';
139150
__DEBUG_BUILD__ && logger.error(`Mongo Integration was unable to require \`${moduleName}\` package.`);
140151
return;
141152
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import type { Hub } from '@sentry/core';
2-
import type { EventProcessor, Integration } from '@sentry/types';
2+
import type { EventProcessor } from '@sentry/types';
33
import { fill, loadModule, logger } from '@sentry/utils';
44

5+
import type { LazyLoadedIntegration } from './lazy';
56
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
67

78
interface MysqlConnection {
89
createQuery: () => void;
910
}
1011

1112
/** Tracing integration for node-mysql package */
12-
export class Mysql implements Integration {
13+
export class Mysql implements LazyLoadedIntegration<MysqlConnection> {
1314
/**
1415
* @inheritDoc
1516
*/
@@ -20,6 +21,13 @@ export class Mysql implements Integration {
2021
*/
2122
public name: string = Mysql.id;
2223

24+
private _module?: MysqlConnection;
25+
26+
/** @inheritdoc */
27+
public loadDependency(): MysqlConnection | undefined {
28+
return (this._module = this._module || loadModule('mysql/lib/Connection.js'));
29+
}
30+
2331
/**
2432
* @inheritDoc
2533
*/
@@ -29,7 +37,7 @@ export class Mysql implements Integration {
2937
return;
3038
}
3139

32-
const pkg = loadModule<MysqlConnection>('mysql/lib/Connection.js');
40+
const pkg = this.loadDependency();
3341

3442
if (!pkg) {
3543
__DEBUG_BUILD__ && logger.error('Mysql Integration was unable to require `mysql` package.');

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

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

5+
import type { LazyLoadedIntegration } from './lazy';
56
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
67

78
interface PgClient {
@@ -14,8 +15,10 @@ interface PgOptions {
1415
usePgNative?: boolean;
1516
}
1617

18+
type PGModule = { Client: PgClient; native: { Client: PgClient } };
19+
1720
/** Tracing integration for node-postgres package */
18-
export class Postgres implements Integration {
21+
export class Postgres implements LazyLoadedIntegration<PGModule> {
1922
/**
2023
* @inheritDoc
2124
*/
@@ -28,10 +31,17 @@ export class Postgres implements Integration {
2831

2932
private _usePgNative: boolean;
3033

34+
private _module?: PGModule;
35+
3136
public constructor(options: PgOptions = {}) {
3237
this._usePgNative = !!options.usePgNative;
3338
}
3439

40+
/** @inheritdoc */
41+
public loadDependency(): PGModule | undefined {
42+
return (this._module = this._module || loadModule('pg'));
43+
}
44+
3545
/**
3646
* @inheritDoc
3747
*/
@@ -41,7 +51,7 @@ export class Postgres implements Integration {
4151
return;
4252
}
4353

44-
const pkg = loadModule<{ Client: PgClient; native: { Client: PgClient } }>('pg');
54+
const pkg = this.loadDependency();
4555

4656
if (!pkg) {
4757
__DEBUG_BUILD__ && logger.error('Postgres Integration was unable to require `pg` package.');

0 commit comments

Comments
 (0)