From 9d342e69b68a8f5212802180c4e99e62603a9e04 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 10:53:13 +0000 Subject: [PATCH 1/4] feat(node): Add `prismaInstrumentation` option to Prisma integration as escape hatch for all Prisma versions --- docs/migration/v8-to-v9.md | 35 ++++- .../node/src/integrations/tracing/prisma.ts | 120 ++++++++++++------ 2 files changed, 110 insertions(+), 45 deletions(-) diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index 665ae287d9b0..234964e0ae48 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -107,12 +107,35 @@ Older Typescript versions _may_ still work, but we will not test them anymore an - The `childProcessIntegration`'s (previously `processThreadBreadcrumbIntegration`) `name` value has been changed from `"ProcessAndThreadBreadcrumbs"` to `"ChildProcess"`. This is relevant if you were filtering integrations by name. -- The Prisma integration no longer supports Prisma v5. As per Prisma v6, the `previewFeatures = ["tracing"]` client generator option in your Prisma Schema is no longer required to use tracing with the Prisma integration. - - For performance instrumentation using Prisma v5: - - 1. Install the `@prisma/instrumentation` package on version 5 - 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the the `Sentry.init()`'s `openTelemetryInstrumentations` option. +- The Prisma integration no longer supports Prisma v5 and supports Prisma v6 by default. As per Prisma v6, the `previewFeatures = ["tracing"]` client generator option in your Prisma Schema is no longer required to use tracing with the Prisma integration. + + For performance instrumentation using other/older 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: + + ```js + import { PrismaInstrumentation } from '@prisma/instrumentation'; + 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. + + 1. Depending on your Prisma version (prior to Prisma version 6), add `previewFeatures = ["tracing"]` to the client generator block of your Prisma schema: + + ``` + generator client { + provider = "prisma-client-js" + previewFeatures = ["tracing"] + } + ``` ### `@sentry/browser` diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index bf1013d405ae..1b25e3cdb2f7 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -1,55 +1,97 @@ +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'); - } - - // 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); - } - }); - }, - }; -}) satisfies IntegrationFn; + return new EsmInteropPrismaInstrumentation({}); + }, +); /** * Adds Sentry tracing instrumentation for the [prisma](https://www.npmjs.com/package/prisma) library. - * * For more information, see the [`prismaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/). * - * @example - * ```javascript - * const Sentry = require('@sentry/node'); + * NOTE: By default, this integration works with Prisma version 6. + * 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: + * + * ```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. + * 1. Depending on your Prisma version (prior to version 6), add `previewFeatures = ["tracing"]` to the client generator block of your Prisma schema: + * + * ``` + * generator client { + * provider = "prisma-client-js" + * previewFeatures = ["tracing"] + * } + * ``` */ -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 v6 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 35bed5593446b1ba3e731ca53c4fef7584f4b7c1 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 11:23:15 +0000 Subject: [PATCH 2/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 1b25e3cdb2f7..53c29b93a377 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 8192392a0e50ac9ede153dde3dd46d45d8068d7e Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 11:28:09 +0000 Subject: [PATCH 3/4] . --- 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 53c29b93a377..c5bf6869b279 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -81,7 +81,7 @@ export const prismaIntegration = defineIntegration( } // 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']) { + if (spanJSON.description === 'prisma:engine:db_query' && spanJSON.data['db.query.text']) { span.updateName(spanJSON.data['db.query.text'] as string); } From 6ee2fded1d498fd491580685da9da1d084698729 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 22 Jan 2025 11:36:46 +0000 Subject: [PATCH 4/4] bless the 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 c5bf6869b279..ec9bc149410d 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -67,7 +67,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() {