Skip to content

Commit fc7ef13

Browse files
author
Luca Forstner
authored
fix(tracing): Instrument Prisma client in constructor of integration (#8383)
1 parent 9fd5016 commit fc7ef13

File tree

3 files changed

+41
-51
lines changed

3 files changed

+41
-51
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ jobs:
8989
- 'scripts/**'
9090
- 'packages/core/**'
9191
- 'packages/tracing/**'
92+
- 'packages/tracing-internal/**'
9293
- 'packages/utils/**'
9394
- 'packages/types/**'
9495
- 'packages/integrations/**'

packages/tracing-internal/src/node/integrations/prisma.ts

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import type { Hub } from '@sentry/core';
2-
import { trace } from '@sentry/core';
3-
import type { EventProcessor, Integration } from '@sentry/types';
4-
import { logger } from '@sentry/utils';
1+
import { getCurrentHub, trace } from '@sentry/core';
2+
import type { Integration } from '@sentry/types';
3+
import { addNonEnumerableProperty, logger } from '@sentry/utils';
54

65
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
76

@@ -36,6 +35,7 @@ type PrismaMiddleware<T = unknown> = (
3635
) => Promise<T>;
3736

3837
interface PrismaClient {
38+
_sentryInstrumented?: boolean;
3939
$use: (cb: PrismaMiddleware) => void;
4040
}
4141

@@ -55,17 +55,30 @@ export class Prisma implements Integration {
5555
*/
5656
public name: string = Prisma.id;
5757

58-
/**
59-
* Prisma ORM Client Instance
60-
*/
61-
private readonly _client?: PrismaClient;
62-
6358
/**
6459
* @inheritDoc
6560
*/
6661
public constructor(options: { client?: unknown } = {}) {
67-
if (isValidPrismaClient(options.client)) {
68-
this._client = options.client;
62+
// We instrument the PrismaClient inside the constructor and not inside `setupOnce` because in some cases of server-side
63+
// bundling (Next.js) multiple Prisma clients can be instantiated, even though users don't intend to. When instrumenting
64+
// in setupOnce we can only ever instrument one client.
65+
// https://github.com/getsentry/sentry-javascript/issues/7216#issuecomment-1602375012
66+
// In the future we might explore providing a dedicated PrismaClient middleware instead of this hack.
67+
if (isValidPrismaClient(options.client) && !options.client._sentryInstrumented) {
68+
addNonEnumerableProperty(options.client as any, '_sentryInstrumented', true);
69+
70+
options.client.$use((params, next: (params: PrismaMiddlewareParams) => Promise<unknown>) => {
71+
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
72+
return next(params);
73+
}
74+
75+
const action = params.action;
76+
const model = params.model;
77+
return trace(
78+
{ name: model ? `${model} ${action}` : action, op: 'db.sql.prisma', data: { 'db.system': 'prisma' } },
79+
() => next(params),
80+
);
81+
});
6982
} else {
7083
__DEBUG_BUILD__ &&
7184
logger.warn(
@@ -77,24 +90,7 @@ export class Prisma implements Integration {
7790
/**
7891
* @inheritDoc
7992
*/
80-
public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
81-
if (!this._client) {
82-
__DEBUG_BUILD__ && logger.error('PrismaIntegration is missing a Prisma Client Instance');
83-
return;
84-
}
85-
86-
if (shouldDisableAutoInstrumentation(getCurrentHub)) {
87-
__DEBUG_BUILD__ && logger.log('Prisma Integration is skipped because of instrumenter configuration.');
88-
return;
89-
}
90-
91-
this._client.$use((params, next: (params: PrismaMiddlewareParams) => Promise<unknown>) => {
92-
const action = params.action;
93-
const model = params.model;
94-
return trace(
95-
{ name: model ? `${model} ${action}` : action, op: 'db.sql.prisma', data: { 'db.system': 'prisma' } },
96-
() => next(params),
97-
);
98-
});
93+
public setupOnce(): void {
94+
// Noop - here for backwards compatibility
9995
}
10096
}

packages/tracing/test/integrations/node/prisma.test.ts

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable deprecation/deprecation */
2-
/* eslint-disable @typescript-eslint/unbound-method */
3-
import { Hub, Scope } from '@sentry/core';
4-
import { logger } from '@sentry/utils';
2+
import * as sentryCore from '@sentry/core';
3+
import { Hub } from '@sentry/core';
54

65
import { Integrations } from '../../../src';
76
import { getTestClient } from '../../testutils';
@@ -38,21 +37,15 @@ class PrismaClient {
3837
}
3938

4039
describe('setupOnce', function () {
41-
const Client: PrismaClient = new PrismaClient();
42-
43-
beforeAll(() => {
44-
new Integrations.Prisma({ client: Client }).setupOnce(
45-
() => undefined,
46-
() => new Hub(undefined, new Scope()),
47-
);
48-
});
49-
5040
beforeEach(() => {
5141
mockTrace.mockClear();
42+
mockTrace.mockReset();
5243
});
5344

5445
it('should add middleware with $use method correctly', done => {
55-
void Client.user.create()?.then(() => {
46+
const prismaClient = new PrismaClient();
47+
new Integrations.Prisma({ client: prismaClient });
48+
void prismaClient.user.create()?.then(() => {
5649
expect(mockTrace).toHaveBeenCalledTimes(1);
5750
expect(mockTrace).toHaveBeenLastCalledWith(
5851
{ name: 'user create', op: 'db.sql.prisma', data: { 'db.system': 'prisma' } },
@@ -62,18 +55,18 @@ describe('setupOnce', function () {
6255
});
6356
});
6457

65-
it("doesn't attach when using otel instrumenter", () => {
66-
const loggerLogSpy = jest.spyOn(logger, 'log');
58+
it("doesn't trace when using otel instrumenter", done => {
59+
const prismaClient = new PrismaClient();
60+
new Integrations.Prisma({ client: prismaClient });
6761

6862
const client = getTestClient({ instrumenter: 'otel' });
6963
const hub = new Hub(client);
7064

71-
const integration = new Integrations.Prisma({ client: Client });
72-
integration.setupOnce(
73-
() => {},
74-
() => hub,
75-
);
65+
jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub);
7666

77-
expect(loggerLogSpy).toBeCalledWith('Prisma Integration is skipped because of instrumenter configuration.');
67+
void prismaClient.user.create()?.then(() => {
68+
expect(mockTrace).not.toHaveBeenCalled();
69+
done();
70+
});
7871
});
7972
});

0 commit comments

Comments
 (0)