From e8681aef3ddff4660cd0fc0e17d3445a52a163c9 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 1 Aug 2023 10:48:58 +0200 Subject: [PATCH 1/2] fix(node-experimental): Improve auto-loading of integrations --- ...{lazy.ts => NodePerformanceIntegration.ts} | 23 ++++- .../src/integrations/express.ts | 2 +- .../src/integrations/fastify.ts | 2 +- .../getAutoPerformanceIntegrations.ts | 92 ++++++++----------- .../src/integrations/graphql.ts | 2 +- .../src/integrations/mongo.ts | 2 +- .../src/integrations/mongoose.ts | 2 +- .../src/integrations/mysql.ts | 2 +- .../src/integrations/mysql2.ts | 2 +- .../src/integrations/nest.ts | 2 +- .../src/integrations/postgres.ts | 2 +- .../src/integrations/prisma.ts | 2 +- 12 files changed, 67 insertions(+), 68 deletions(-) rename packages/node-experimental/src/integrations/{lazy.ts => NodePerformanceIntegration.ts} (60%) diff --git a/packages/node-experimental/src/integrations/lazy.ts b/packages/node-experimental/src/integrations/NodePerformanceIntegration.ts similarity index 60% rename from packages/node-experimental/src/integrations/lazy.ts rename to packages/node-experimental/src/integrations/NodePerformanceIntegration.ts index 85545992eb41..536b9ed55daa 100644 --- a/packages/node-experimental/src/integrations/lazy.ts +++ b/packages/node-experimental/src/integrations/NodePerformanceIntegration.ts @@ -1,20 +1,39 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; -/** TODO */ +/** + * The base node performance integration. + */ export abstract class NodePerformanceIntegration { protected _options: IntegrationOptions; protected _unload?: () => void; + protected _instrumentations?: Instrumentation[] | undefined; + + public abstract name: string; public constructor(options: IntegrationOptions) { this._options = options; } + /** + * Load the instrumentation(s) for this integration. + * Returns `true` if the instrumentations were loaded, else false. + */ + public loadInstrumentations(): boolean { + try { + this._instrumentations = this.setupInstrumentation(this._options) || undefined; + } catch (error) { + return false; + } + + return true; + } + /** * @inheritDoc */ public setupOnce(): void { - const instrumentations = this.setupInstrumentation(this._options); + const instrumentations = this._instrumentations || this.setupInstrumentation(this._options); if (!instrumentations) { return; diff --git a/packages/node-experimental/src/integrations/express.ts b/packages/node-experimental/src/integrations/express.ts index f51860364378..33eed0182107 100644 --- a/packages/node-experimental/src/integrations/express.ts +++ b/packages/node-experimental/src/integrations/express.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * Express integration diff --git a/packages/node-experimental/src/integrations/fastify.ts b/packages/node-experimental/src/integrations/fastify.ts index 70969c40be46..17bae20d0ed3 100644 --- a/packages/node-experimental/src/integrations/fastify.ts +++ b/packages/node-experimental/src/integrations/fastify.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * Express integration diff --git a/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts b/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts index d770d50a9a30..7bd0a4225acb 100644 --- a/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts +++ b/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts @@ -1,87 +1,67 @@ -import type { Integration, IntegrationClass } from '@sentry/types'; -import { dynamicRequire } from '@sentry/utils'; +import type { Integration } from '@sentry/types'; -import type { Express } from './express'; -import type { Fastify } from './fastify'; -import type { GraphQL } from './graphql'; -import type { Mongo } from './mongo'; -import type { Mongoose } from './mongoose'; -import type { Mysql } from './mysql'; -import type { Mysql2 } from './mysql2'; -import type { Nest } from './nest'; -import type { Postgres } from './postgres'; -import type { Prisma } from './prisma'; +import { Express } from './express'; +import { Fastify } from './fastify'; +import { GraphQL } from './graphql'; +import { Mongo } from './mongo'; +import { Mongoose } from './mongoose'; +import { Mysql } from './mysql'; +import { Mysql2 } from './mysql2'; +import { Nest } from './nest'; +import type { NodePerformanceIntegration } from './NodePerformanceIntegration'; +import { Postgres } from './postgres'; +import { Prisma } from './prisma'; -const INTEGRATIONS = [ +const INTEGRATIONS: (() => NodePerformanceIntegration)[] = [ () => { - const integration = dynamicRequire(module, './express') as { - Express: IntegrationClass; - }; - return new integration.Express(); + return new Express(); }, () => { - const integration = dynamicRequire(module, './fastify') as { - Fastify: IntegrationClass; - }; - return new integration.Fastify(); + return new Fastify(); }, () => { - const integration = dynamicRequire(module, './graphql') as { - GraphQL: IntegrationClass; - }; - return new integration.GraphQL(); + return new GraphQL(); }, () => { - const integration = dynamicRequire(module, './mongo') as { - Mongo: IntegrationClass; - }; - return new integration.Mongo(); + return new Mongo(); }, () => { - const integration = dynamicRequire(module, './mongoose') as { - Mongoose: IntegrationClass; - }; - return new integration.Mongoose(); + return new Mongoose(); }, () => { - const integration = dynamicRequire(module, './mysql') as { - Mysql: IntegrationClass; - }; - return new integration.Mysql(); + return new Mysql(); }, () => { - const integration = dynamicRequire(module, './mysql2') as { - Mysql2: IntegrationClass; - }; - return new integration.Mysql2(); + return new Mysql2(); }, () => { - const integration = dynamicRequire(module, './postgres') as { - Postgres: IntegrationClass; - }; - return new integration.Postgres(); + return new Postgres(); }, () => { - const integration = dynamicRequire(module, './prisma') as { - Prisma: IntegrationClass; - }; - return new integration.Prisma(); + return new Prisma(); }, () => { - const integration = dynamicRequire(module, './nest') as { - Nest: IntegrationClass; - }; - return new integration.Nest(); + return new Nest(); }, ]; -/** TODO */ +/** + * Get auto-dsicovered performance integrations. + * Note that due to the way OpenTelemetry instrumentation works, this will generally still return Integrations + * for stuff that may not be installed. This is because Otel only instruments when the module is imported/required, + * so if the package is not required at all it will not be patched, and thus not instrumented. + * But the _Sentry_ Integration will still be added. + * This _may_ be a bit confusing because it shows all integrations as being installed in the debug logs, but this is + * technically not wrong because we install it (it just doesn't do anything). + */ export function getAutoPerformanceIntegrations(): Integration[] { const loadedIntegrations = INTEGRATIONS.map(tryLoad => { try { - return tryLoad(); + const integration = tryLoad(); + const isLoaded = integration.loadInstrumentations(); + return isLoaded ? integration : false; } catch (_) { - return undefined; + return false; } }).filter(integration => !!integration) as Integration[]; diff --git a/packages/node-experimental/src/integrations/graphql.ts b/packages/node-experimental/src/integrations/graphql.ts index c606f1b6f126..dad877cdbb1a 100644 --- a/packages/node-experimental/src/integrations/graphql.ts +++ b/packages/node-experimental/src/integrations/graphql.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * GraphQL integration diff --git a/packages/node-experimental/src/integrations/mongo.ts b/packages/node-experimental/src/integrations/mongo.ts index 109251986af6..befa3bbf6365 100644 --- a/packages/node-experimental/src/integrations/mongo.ts +++ b/packages/node-experimental/src/integrations/mongo.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * MongoDB integration diff --git a/packages/node-experimental/src/integrations/mongoose.ts b/packages/node-experimental/src/integrations/mongoose.ts index 5f85ae2bb771..0b48f4bd2e6c 100644 --- a/packages/node-experimental/src/integrations/mongoose.ts +++ b/packages/node-experimental/src/integrations/mongoose.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * Mongoose integration diff --git a/packages/node-experimental/src/integrations/mysql.ts b/packages/node-experimental/src/integrations/mysql.ts index bae4e81f21a2..cbfe46c6aadb 100644 --- a/packages/node-experimental/src/integrations/mysql.ts +++ b/packages/node-experimental/src/integrations/mysql.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * MySQL integration diff --git a/packages/node-experimental/src/integrations/mysql2.ts b/packages/node-experimental/src/integrations/mysql2.ts index 0620e7ad0b63..30b47d298716 100644 --- a/packages/node-experimental/src/integrations/mysql2.ts +++ b/packages/node-experimental/src/integrations/mysql2.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * MySQL2 integration diff --git a/packages/node-experimental/src/integrations/nest.ts b/packages/node-experimental/src/integrations/nest.ts index 1845e4c3b9c6..f847f3201771 100644 --- a/packages/node-experimental/src/integrations/nest.ts +++ b/packages/node-experimental/src/integrations/nest.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * Nest framework integration diff --git a/packages/node-experimental/src/integrations/postgres.ts b/packages/node-experimental/src/integrations/postgres.ts index b061b209c8c8..ab8c17ea61e7 100644 --- a/packages/node-experimental/src/integrations/postgres.ts +++ b/packages/node-experimental/src/integrations/postgres.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * Postgres integration diff --git a/packages/node-experimental/src/integrations/prisma.ts b/packages/node-experimental/src/integrations/prisma.ts index 8fe0b0deb131..4609c98ae71f 100644 --- a/packages/node-experimental/src/integrations/prisma.ts +++ b/packages/node-experimental/src/integrations/prisma.ts @@ -2,7 +2,7 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { PrismaInstrumentation } from '@prisma/instrumentation'; import type { Integration } from '@sentry/types'; -import { NodePerformanceIntegration } from './lazy'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; /** * Prisma integration From 1f2498f246a4372f126cb27a75b0cd8a9966ed7b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 1 Aug 2023 12:38:29 +0200 Subject: [PATCH 2/2] add docs --- packages/node-experimental/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/node-experimental/README.md b/packages/node-experimental/README.md index c7147a82243f..2b091a3379b0 100644 --- a/packages/node-experimental/README.md +++ b/packages/node-experimental/README.md @@ -50,10 +50,26 @@ Currently, this SDK: * Will capture errors (same as @sentry/node) * Auto-instrument for performance - see below for which performance integrations are available. +### Manual Instrumentation + **Manual instrumentation is not supported!** This is because the current Sentry-Performance-APIs like `Sentry.startTransaction()` are not compatible with the OpenTelemetry tracing model. We may add manual tracing capabilities in a later version. +### ESM Support + +Due to the way OpenTelemetry handles instrumentation, this only works out of the box for CommonJS (`require`) applications. + + +There is experimental support for running OpenTelemetry with ESM (`"type": "module"`): + +```bash +node --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./app.js +``` + +See [OpenTelemetry Instrumentation Docs](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation#instrumentation-for-es-modules-in-nodejs-experimental) for details on this - +but note that this is a) experimental, and b) does not work with all integrations. + ## Available (Performance) Integrations * Http