Skip to content

feat(node): Add prismaInstrumentation option to Prisma integration as escape hatch for all Prisma versions #15127

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 4 commits into from
Jan 22, 2025
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
35 changes: 29 additions & 6 deletions docs/migration/v8-to-v9.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
120 changes: 81 additions & 39 deletions packages/node/src/integrations/tracing/prisma.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,97 @@
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';
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it actually be more consistent if we were to leave this as prisma:engine:db_query or maybe make this opt-in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would clash with our contract with Sentry but I'll let @AbhiPrasad confirm.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is done to make the queries module work, as the queries module expects the query as the span name in it's current state.

eventually this won't be required once we have proper OTEL support (with all the attributes), but given the timeline for that is not super clear, we have to do this for now.

}

// 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');
}
});
},
};
},
);
Loading