From 6e7175d92792dffd44a50d1ad2b9d5a73198aac8 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 10:59:47 +0000 Subject: [PATCH 1/4] feat(node/v8): Add `prismaInstrumentation` option to Prisma integration as escape hatch for all Prisma versions --- .../node/src/integrations/tracing/prisma.ts | 119 +++++++++++------- 1 file changed, 74 insertions(+), 45 deletions(-) diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index a139de3a78fb..03f3a0504661 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -1,53 +1,33 @@ +import { Instrumentation } from '@opentelemetry/instrumentation'; // When importing CJS modules into an ESM module, we cannot import the named exports directly. import * as prismaInstrumentation from '@prisma/instrumentation'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/core'; import { generateInstrumentOnce } from '../../otel/instrument'; const INTEGRATION_NAME = 'Prisma'; -export const instrumentPrisma = generateInstrumentOnce(INTEGRATION_NAME, () => { - const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation = - // @ts-expect-error We need to do the following for interop reasons - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation; +export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: Instrumentation }>( + INTEGRATION_NAME, + options => { + // Use a passed instrumentation instance to support older Prisma versions + if (options?.prismaInstrumentation) { + return options.prismaInstrumentation; + } - return new EsmInteropPrismaInstrumentation({}); -}); + const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation = + // @ts-expect-error We need to do the following for interop reasons + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation; -const _prismaIntegration = (() => { - return { - name: INTEGRATION_NAME, - setupOnce() { - instrumentPrisma(); - }, - - setup(client) { - client.on('spanStart', span => { - const spanJSON = spanToJSON(span); - if (spanJSON.description?.startsWith('prisma:')) { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.prisma'); - } - - // In Prisma v5.22+, the `db.system` attribute is automatically set - // On older versions, this is missing, so we add it here - if (spanJSON.description === 'prisma:engine:db_query' && !spanJSON.data?.['db.system']) { - span.setAttribute('db.system', 'prisma'); - } - }); - }, - }; -}) satisfies IntegrationFn; + return new EsmInteropPrismaInstrumentation({}); + }, +); /** - * Adds Sentry tracing instrumentation for the [prisma](https://www.npmjs.com/package/prisma) library. - * + * Adds Sentry tracing instrumentation for the [Prisma](https://www.npmjs.com/package/prisma) ORM. * For more information, see the [`prismaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/). * - * @example - * - * Make sure `previewFeatures = ["tracing"]` is set in the prisma client generator block. See the - * [prisma docs](https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing) for more details. + * Make sure `previewFeatures = ["tracing"]` is added to the generator block in of your Prisma schema. * * ```prisma * generator client { @@ -56,14 +36,63 @@ const _prismaIntegration = (() => { * } * ``` * - * Then you can use the integration like this: + * NOTE: By default, this integration works with Prisma version 5. + * To get performance instrumentation for other Prisma versions, + * 1. Install the `@prisma/instrumentation` package with the desired version. + * 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the `prismaInstrumentation` option of this integration: * - * ```javascript - * const Sentry = require('@sentry/node'); + * ```js + * import { PrismaInstrumentation } from '@prisma/instrumentation' * - * Sentry.init({ - * integrations: [Sentry.prismaIntegration()], - * }); - * ``` + * Sentry.init({ + * integrations: [ + * prismaIntegration({ + * // Override the default instrumentation that Sentry uses + * prismaInstrumentation: new PrismaInstrumentation() + * }) + * ] + * }) + * ``` + * + * The passed instrumentation instance will override the default instrumentation instance the integration would use, while the `prismaIntegration` will still ensure data compatibility for the various Prisma versions. */ -export const prismaIntegration = defineIntegration(_prismaIntegration); +export const prismaIntegration = defineIntegration( + ({ + prismaInstrumentation, + }: { + /** + * Overrides the instrumentation used by the Sentry SDK with the passed in instrumentation instance. + * + * NOTE: By default, the Sentry SDK uses the Prisma v5 instrumentation. Use this option if you need performance instrumentation different Prisma versions. + * + * For more information refer to the documentation of `prismaIntegration()` or see https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/ + */ + prismaInstrumentation?: Instrumentation; + }) => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentPrisma({ prismaInstrumentation }); + }, + setup(client) { + client.on('spanStart', span => { + const spanJSON = spanToJSON(span); + if (spanJSON.description?.startsWith('prisma:')) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.prisma'); + } + + // Make sure we use the query text as the span name, for ex. SELECT * FROM "User" WHERE "id" = $1 + if (spanJSON.description === 'prisma:engine:db_query' && spanJSON.data?.['db.query.text']) { + span.updateName(spanJSON.data['db.query.text'] as string); + } + + // In Prisma v5.22+, the `db.system` attribute is automatically set + // On older versions, this is missing, so we add it here + if (spanJSON.description === 'prisma:engine:db_query' && !spanJSON.data['db.system']) { + span.setAttribute('db.system', 'prisma'); + } + }); + }, + }; + }, +); From 120deeb11658457c5a9baed541c5272b4258df49 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 11:19:27 +0000 Subject: [PATCH 2/4] fix types --- packages/node/src/integrations/tracing/prisma.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index 03f3a0504661..c03700425358 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -88,7 +88,7 @@ export const prismaIntegration = defineIntegration( // In Prisma v5.22+, the `db.system` attribute is automatically set // On older versions, this is missing, so we add it here - if (spanJSON.description === 'prisma:engine:db_query' && !spanJSON.data['db.system']) { + if (spanJSON.description === 'prisma:engine:db_query' && !spanJSON.data?.['db.system']) { span.setAttribute('db.system', 'prisma'); } }); From e042627e1b3fec82b82e66d16be17ab632c5b9ec Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 11:22:49 +0000 Subject: [PATCH 3/4] lint --- packages/node/src/integrations/tracing/prisma.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index c03700425358..1a41f1886395 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -1,4 +1,4 @@ -import { Instrumentation } from '@opentelemetry/instrumentation'; +import type { Instrumentation } from '@opentelemetry/instrumentation'; // When importing CJS modules into an ESM module, we cannot import the named exports directly. import * as prismaInstrumentation from '@prisma/instrumentation'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; From eccb507f1163005d38d93b4e625e76ede79891ea Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 11:37:04 +0000 Subject: [PATCH 4/4] Bless tests --- packages/node/src/integrations/tracing/prisma.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index 1a41f1886395..33a51384f0ce 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -68,7 +68,7 @@ export const prismaIntegration = defineIntegration( * For more information refer to the documentation of `prismaIntegration()` or see https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/ */ prismaInstrumentation?: Instrumentation; - }) => { + } = {}) => { return { name: INTEGRATION_NAME, setupOnce() {