Skip to content

feat(node): Auto discovery only returns integrations where dependency loads #7603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/node/src/tracing/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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());
}
1 change: 1 addition & 0 deletions packages/tracing-internal/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {
Prisma,
lazyLoadedNodePerformanceMonitoringIntegrations,
} from './node';
export type { LazyLoadedIntegration } from './node';

export {
BrowserTracing,
Expand Down
50 changes: 34 additions & 16 deletions packages/tracing-internal/src/node/integrations/apollo.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<GraphQLModule & ApolloModule> {
/**
* @inheritDoc
*/
Expand All @@ -30,6 +47,8 @@ export class Apollo implements Integration {

private readonly _useNest: boolean;

private _module?: GraphQLModule & ApolloModule;

/**
* @inheritDoc
*/
Expand All @@ -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
*/
Expand All @@ -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.');
Expand Down Expand Up @@ -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.');
Expand Down
20 changes: 15 additions & 5 deletions packages/tracing-internal/src/node/integrations/graphql.ts
Original file line number Diff line number Diff line change
@@ -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<GraphQLModule> {
/**
* @inheritDoc
*/
Expand All @@ -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
*/
Expand All @@ -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.');
Expand Down
25 changes: 17 additions & 8 deletions packages/tracing-internal/src/node/integrations/lazy.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
import type { Integration, IntegrationClass } from '@sentry/types';
import { dynamicRequire } from '@sentry/utils';

export const lazyLoadedNodePerformanceMonitoringIntegrations: (() => Integration)[] = [
export interface LazyLoadedIntegration<T = object> 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<Integration>;
Apollo: IntegrationClass<LazyLoadedIntegration>;
};
return new integration.Apollo();
},
() => {
const integration = dynamicRequire(module, './apollo') as {
Apollo: IntegrationClass<Integration>;
Apollo: IntegrationClass<LazyLoadedIntegration>;
};
return new integration.Apollo({ useNestjs: true });
},
() => {
const integration = dynamicRequire(module, './graphql') as {
GraphQL: IntegrationClass<Integration>;
GraphQL: IntegrationClass<LazyLoadedIntegration>;
};
return new integration.GraphQL();
},
() => {
const integration = dynamicRequire(module, './mongo') as {
Mongo: IntegrationClass<Integration>;
Mongo: IntegrationClass<LazyLoadedIntegration>;
};
return new integration.Mongo();
},
() => {
const integration = dynamicRequire(module, './mongo') as {
Mongo: IntegrationClass<Integration>;
Mongo: IntegrationClass<LazyLoadedIntegration>;
};
return new integration.Mongo({ mongoose: true });
},
() => {
const integration = dynamicRequire(module, './mysql') as {
Mysql: IntegrationClass<Integration>;
Mysql: IntegrationClass<LazyLoadedIntegration>;
};
return new integration.Mysql();
},
() => {
const integration = dynamicRequire(module, './postgres') as {
Postgres: IntegrationClass<Integration>;
Postgres: IntegrationClass<LazyLoadedIntegration>;
};
return new integration.Postgres();
},
Expand Down
19 changes: 15 additions & 4 deletions packages/tracing-internal/src/node/integrations/mongo.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<MongoModule> {
/**
* @inheritDoc
*/
Expand All @@ -114,6 +117,8 @@ export class Mongo implements Integration {
private _describeOperations?: boolean | Operation[];
private _useMongoose: boolean;

private _module?: MongoModule;

/**
* @inheritDoc
*/
Expand All @@ -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
*/
Expand All @@ -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;
}
Expand Down
14 changes: 11 additions & 3 deletions packages/tracing-internal/src/node/integrations/mysql.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
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 {
createQuery: () => void;
}

/** Tracing integration for node-mysql package */
export class Mysql implements Integration {
export class Mysql implements LazyLoadedIntegration<MysqlConnection> {
/**
* @inheritDoc
*/
Expand All @@ -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
*/
Expand All @@ -29,7 +37,7 @@ export class Mysql implements Integration {
return;
}

const pkg = loadModule<MysqlConnection>('mysql/lib/Connection.js');
const pkg = this.loadDependency();

if (!pkg) {
__DEBUG_BUILD__ && logger.error('Mysql Integration was unable to require `mysql` package.');
Expand Down
16 changes: 13 additions & 3 deletions packages/tracing-internal/src/node/integrations/postgres.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<PGModule> {
/**
* @inheritDoc
*/
Expand All @@ -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
*/
Expand All @@ -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.');
Expand Down