Skip to content

Commit 1e0e9d6

Browse files
feat(tracing): Add Prisma ORM integration. (#4931)
Co-authored-by: Abhijeet Prasad <[email protected]>
1 parent 7fb13d7 commit 1e0e9d6

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

packages/tracing/src/integrations/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { Express } from './node/express';
22
export { Postgres } from './node/postgres';
33
export { Mysql } from './node/mysql';
44
export { Mongo } from './node/mongo';
5+
export { Prisma } from './node/prisma';
56

67
// TODO(v7): Remove this export
78
// Please see `src/index.ts` for more details.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { Hub } from '@sentry/hub';
2+
import { EventProcessor, Integration } from '@sentry/types';
3+
import { isThenable, logger } from '@sentry/utils';
4+
5+
import { IS_DEBUG_BUILD } from '../../flags';
6+
7+
type PrismaAction =
8+
| 'findUnique'
9+
| 'findMany'
10+
| 'findFirst'
11+
| 'create'
12+
| 'createMany'
13+
| 'update'
14+
| 'updateMany'
15+
| 'upsert'
16+
| 'delete'
17+
| 'deleteMany'
18+
| 'executeRaw'
19+
| 'queryRaw'
20+
| 'aggregate'
21+
| 'count'
22+
| 'runCommandRaw';
23+
24+
interface PrismaMiddlewareParams {
25+
model?: unknown;
26+
action: PrismaAction;
27+
args: unknown;
28+
dataPath: string[];
29+
runInTransaction: boolean;
30+
}
31+
32+
type PrismaMiddleware<T = unknown> = (
33+
params: PrismaMiddlewareParams,
34+
next: (params: PrismaMiddlewareParams) => Promise<T>,
35+
) => Promise<T>;
36+
37+
interface PrismaClient {
38+
$use: (cb: PrismaMiddleware) => void;
39+
}
40+
41+
/** Tracing integration for @prisma/client package */
42+
export class Prisma implements Integration {
43+
/**
44+
* @inheritDoc
45+
*/
46+
public static id: string = 'Prisma';
47+
48+
/**
49+
* @inheritDoc
50+
*/
51+
public name: string = Prisma.id;
52+
53+
/**
54+
* Prisma ORM Client Instance
55+
*/
56+
private readonly _client?: PrismaClient;
57+
58+
/**
59+
* @inheritDoc
60+
*/
61+
public constructor(options: { client?: PrismaClient } = {}) {
62+
this._client = options.client;
63+
}
64+
65+
/**
66+
* @inheritDoc
67+
*/
68+
public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
69+
if (!this._client) {
70+
IS_DEBUG_BUILD && logger.error('PrismaIntegration is missing a Prisma Client Instance');
71+
return;
72+
}
73+
74+
this._client.$use((params: PrismaMiddlewareParams, next: (params: PrismaMiddlewareParams) => Promise<unknown>) => {
75+
const scope = getCurrentHub().getScope();
76+
const parentSpan = scope?.getSpan();
77+
78+
const action = params.action;
79+
const model = params.model;
80+
81+
const span = parentSpan?.startChild({
82+
description: model ? `${model} ${action}` : action,
83+
op: 'db.prisma',
84+
});
85+
86+
const rv = next(params);
87+
88+
if (isThenable(rv)) {
89+
return rv.then((res: unknown) => {
90+
span?.finish();
91+
return res;
92+
});
93+
}
94+
95+
span?.finish();
96+
return rv;
97+
});
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* eslint-disable @typescript-eslint/unbound-method */
2+
import { Hub, Scope } from '@sentry/hub';
3+
4+
import { Prisma } from '../../../src/integrations/node/prisma';
5+
import { Span } from '../../../src/span';
6+
7+
type PrismaMiddleware = (params: unknown, next: (params?: unknown) => Promise<unknown>) => Promise<unknown>;
8+
9+
class PrismaClient {
10+
public user: { create: () => Promise<unknown> | undefined } = {
11+
create: () => this._middleware?.({ action: 'create', model: 'user' }, () => Promise.resolve('result')),
12+
};
13+
14+
private _middleware?: PrismaMiddleware;
15+
16+
constructor() {
17+
this._middleware = undefined;
18+
}
19+
20+
public $use(cb: PrismaMiddleware) {
21+
this._middleware = cb;
22+
}
23+
}
24+
25+
describe('setupOnce', function () {
26+
const Client: PrismaClient = new PrismaClient();
27+
28+
let scope = new Scope();
29+
let parentSpan: Span;
30+
let childSpan: Span;
31+
32+
beforeAll(() => {
33+
// @ts-ignore, not to export PrismaClient types from integration source
34+
new Prisma({ client: Client }).setupOnce(
35+
() => undefined,
36+
() => new Hub(undefined, scope),
37+
);
38+
});
39+
40+
beforeEach(() => {
41+
scope = new Scope();
42+
parentSpan = new Span();
43+
childSpan = parentSpan.startChild();
44+
jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan);
45+
jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan);
46+
jest.spyOn(childSpan, 'finish');
47+
});
48+
49+
it('should add middleware with $use method correctly', done => {
50+
void Client.user.create()?.then(res => {
51+
expect(res).toBe('result');
52+
expect(scope.getSpan).toBeCalled();
53+
expect(parentSpan.startChild).toBeCalledWith({
54+
description: 'user create',
55+
op: 'db.prisma',
56+
});
57+
expect(childSpan.finish).toBeCalled();
58+
done();
59+
});
60+
});
61+
});

0 commit comments

Comments
 (0)