-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'); | ||
} | ||
}); | ||
}, | ||
}; | ||
}, | ||
); |
Uh oh!
There was an error while loading. Please reload this page.