Skip to content

Commit ccfb2b0

Browse files
authored
feat: options for logging queries sent to prisma (#488)
1 parent 1b67eba commit ccfb2b0

File tree

10 files changed

+201
-72
lines changed

10 files changed

+201
-72
lines changed

packages/runtime/src/enhancements/omit.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,21 @@ import { DefaultPrismaProxyHandler, makeProxy } from './proxy';
77
import { ModelMeta } from './types';
88
import { enumerate, getModelFields } from './utils';
99

10+
/**
11+
* Options for @see withOmit
12+
*/
13+
export type WithOmitOptions = {
14+
/**
15+
* Model metatadata
16+
*/
17+
modelMeta?: ModelMeta;
18+
};
19+
1020
/**
1121
* Gets an enhanced Prisma client that supports @omit attribute.
1222
*/
13-
export function withOmit<DbClient extends object>(prisma: DbClient, modelMeta?: ModelMeta): DbClient {
14-
const _modelMeta = modelMeta ?? getDefaultModelMeta();
23+
export function withOmit<DbClient extends object>(prisma: DbClient, options?: WithOmitOptions): DbClient {
24+
const _modelMeta = options?.modelMeta ?? getDefaultModelMeta();
1525
return makeProxy(
1626
prisma,
1727
_modelMeta,

packages/runtime/src/enhancements/password.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,21 @@ import { NestedWriteVisitor } from './nested-write-vistor';
99
import { DefaultPrismaProxyHandler, PrismaProxyActions, makeProxy } from './proxy';
1010
import { ModelMeta } from './types';
1111

12+
/**
13+
* Options for @see withPassword
14+
*/
15+
export type WithPasswordOptions = {
16+
/**
17+
* Model metatadata
18+
*/
19+
modelMeta?: ModelMeta;
20+
};
21+
1222
/**
1323
* Gets an enhanced Prisma client that supports @password attribute.
1424
*/
15-
export function withPassword<DbClient extends object = any>(prisma: DbClient, modelMeta?: ModelMeta): DbClient {
16-
const _modelMeta = modelMeta ?? getDefaultModelMeta();
25+
export function withPassword<DbClient extends object = any>(prisma: DbClient, options?: WithPasswordOptions): DbClient {
26+
const _modelMeta = options?.modelMeta ?? getDefaultModelMeta();
1727
return makeProxy(
1828
prisma,
1929
_modelMeta,

packages/runtime/src/enhancements/policy/handler.ts

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CrudFailureReason } from '../../constants';
44
import { AuthUser, DbClientContract, PolicyOperationKind } from '../../types';
55
import { BatchResult, PrismaProxyHandler } from '../proxy';
66
import { ModelMeta, PolicyDef } from '../types';
7-
import { prismaClientValidationError } from '../utils';
7+
import { formatObject, prismaClientValidationError } from '../utils';
88
import { Logger } from './logger';
99
import { PolicyUtil } from './policy-utils';
1010

@@ -20,10 +20,11 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
2020
private readonly policy: PolicyDef,
2121
private readonly modelMeta: ModelMeta,
2222
private readonly model: string,
23-
private readonly user?: AuthUser
23+
private readonly user?: AuthUser,
24+
private readonly logPrismaQuery?: boolean
2425
) {
2526
this.logger = new Logger(prisma);
26-
this.utils = new PolicyUtil(this.prisma, this.modelMeta, this.policy, this.user);
27+
this.utils = new PolicyUtil(this.prisma, this.modelMeta, this.policy, this.user, this.logPrismaQuery);
2728
}
2829

2930
private get modelClient() {
@@ -107,9 +108,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
107108

108109
// use a transaction to wrap the write so it can be reverted if the created
109110
// entity fails access policies
110-
const result: any = await this.utils.processWrite(this.model, 'create', args, (dbOps, writeArgs) =>
111-
dbOps.create(writeArgs)
112-
);
111+
const result: any = await this.utils.processWrite(this.model, 'create', args, (dbOps, writeArgs) => {
112+
if (this.logPrismaQuery && this.logger.enabled('info')) {
113+
this.logger.info(`[withPolicy] \`create\`: ${formatObject(writeArgs)}`);
114+
}
115+
return dbOps.create(writeArgs);
116+
});
113117

114118
const ids = this.utils.getEntityIds(this.model, result);
115119
if (Object.keys(ids).length === 0) {
@@ -133,9 +137,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
133137

134138
// use a transaction to wrap the write so it can be reverted if any created
135139
// entity fails access policies
136-
const result = await this.utils.processWrite(this.model, 'create', args, (dbOps, writeArgs) =>
137-
dbOps.createMany(writeArgs, skipDuplicates)
138-
);
140+
const result = await this.utils.processWrite(this.model, 'create', args, (dbOps, writeArgs) => {
141+
if (this.logPrismaQuery && this.logger.enabled('info')) {
142+
this.logger.info(`[withPolicy] \`createMany\`: ${formatObject(writeArgs)}`);
143+
}
144+
return dbOps.createMany(writeArgs, skipDuplicates);
145+
});
139146

140147
return result as BatchResult;
141148
}
@@ -158,9 +165,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
158165

159166
// use a transaction to wrap the write so it can be reverted if any nested
160167
// create fails access policies
161-
const result: any = await this.utils.processWrite(this.model, 'update', args, (dbOps, writeArgs) =>
162-
dbOps.update(writeArgs)
163-
);
168+
const result: any = await this.utils.processWrite(this.model, 'update', args, (dbOps, writeArgs) => {
169+
if (this.logPrismaQuery && this.logger.enabled('info')) {
170+
this.logger.info(`[withPolicy] \`update\`: ${formatObject(writeArgs)}`);
171+
}
172+
return dbOps.update(writeArgs);
173+
});
164174

165175
const ids = this.utils.getEntityIds(this.model, result);
166176
if (Object.keys(ids).length === 0) {
@@ -183,9 +193,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
183193

184194
// use a transaction to wrap the write so it can be reverted if any nested
185195
// create fails access policies
186-
const result = await this.utils.processWrite(this.model, 'updateMany', args, (dbOps, writeArgs) =>
187-
dbOps.updateMany(writeArgs)
188-
);
196+
const result = await this.utils.processWrite(this.model, 'updateMany', args, (dbOps, writeArgs) => {
197+
if (this.logPrismaQuery && this.logger.enabled('info')) {
198+
this.logger.info(`[withPolicy] \`updateMany\`: ${formatObject(writeArgs)}`);
199+
}
200+
return dbOps.updateMany(writeArgs);
201+
});
189202

190203
return result as BatchResult;
191204
}
@@ -212,9 +225,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
212225

213226
// use a transaction to wrap the write so it can be reverted if any nested
214227
// create fails access policies
215-
const result: any = await this.utils.processWrite(this.model, 'upsert', args, (dbOps, writeArgs) =>
216-
dbOps.upsert(writeArgs)
217-
);
228+
const result: any = await this.utils.processWrite(this.model, 'upsert', args, (dbOps, writeArgs) => {
229+
if (this.logPrismaQuery && this.logger.enabled('info')) {
230+
this.logger.info(`[withPolicy] \`upsert\`: ${formatObject(writeArgs)}`);
231+
}
232+
return dbOps.upsert(writeArgs);
233+
});
218234

219235
const ids = this.utils.getEntityIds(this.model, result);
220236
if (Object.keys(ids).length === 0) {
@@ -248,6 +264,9 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
248264
}
249265

250266
// conduct the deletion
267+
if (this.logPrismaQuery && this.logger.enabled('info')) {
268+
this.logger.info(`[withPolicy] \`delete\`:\n${formatObject(args)}`);
269+
}
251270
await this.modelClient.delete(args);
252271

253272
if (!readResult) {
@@ -270,6 +289,9 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
270289
await this.utils.injectAuthGuard(args, this.model, 'delete');
271290

272291
// conduct the deletion
292+
if (this.logPrismaQuery && this.logger.enabled('info')) {
293+
this.logger.info(`[withPolicy] \`deleteMany\`:\n${formatObject(args)}`);
294+
}
273295
return this.modelClient.deleteMany(args);
274296
}
275297

@@ -282,6 +304,10 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
282304

283305
// inject policy conditions
284306
await this.utils.injectAuthGuard(args, this.model, 'read');
307+
308+
if (this.logPrismaQuery && this.logger.enabled('info')) {
309+
this.logger.info(`[withPolicy] \`aggregate\`:\n${formatObject(args)}`);
310+
}
285311
return this.modelClient.aggregate(args);
286312
}
287313

@@ -295,6 +321,9 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
295321
// inject policy conditions
296322
await this.utils.injectAuthGuard(args, this.model, 'read');
297323

324+
if (this.logPrismaQuery && this.logger.enabled('info')) {
325+
this.logger.info(`[withPolicy] \`groupBy\`:\n${formatObject(args)}`);
326+
}
298327
return this.modelClient.groupBy(args);
299328
}
300329

@@ -304,6 +333,10 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
304333
// inject policy conditions
305334
args = args ?? {};
306335
await this.utils.injectAuthGuard(args, this.model, 'read');
336+
337+
if (this.logPrismaQuery && this.logger.enabled('info')) {
338+
this.logger.info(`[withPolicy] \`count\`:\n${formatObject(args)}`);
339+
}
307340
return this.modelClient.count(args);
308341
}
309342

@@ -323,7 +356,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
323356
const readArgs = { select: origArgs.select, include: origArgs.include, where: ids };
324357
const result = await this.utils.readWithCheck(this.model, readArgs);
325358
if (result.length === 0) {
326-
this.logger.warn(`${action} result cannot be read back`);
359+
this.logger.info(`${action} result cannot be read back`);
327360
throw this.utils.deniedByPolicy(
328361
this.model,
329362
operation,

packages/runtime/src/enhancements/policy/index.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,26 @@ export type WithPolicyContext = {
1313
user?: AuthUser;
1414
};
1515

16+
/**
17+
* Options for @see withPolicy
18+
*/
19+
export type WithPolicyOptions = {
20+
/**
21+
* Policy definition
22+
*/
23+
policy?: PolicyDef;
24+
25+
/**
26+
* Model metatadata
27+
*/
28+
modelMeta?: ModelMeta;
29+
30+
/**
31+
* Whether to log Prisma query
32+
*/
33+
logPrismaQuery?: boolean;
34+
};
35+
1636
/**
1737
* Gets an enhanced Prisma client with access policy check.
1838
*
@@ -24,16 +44,22 @@ export type WithPolicyContext = {
2444
export function withPolicy<DbClient extends object>(
2545
prisma: DbClient,
2646
context?: WithPolicyContext,
27-
policy?: PolicyDef,
28-
modelMeta?: ModelMeta
47+
options?: WithPolicyOptions
2948
): DbClient {
30-
const _policy = policy ?? getDefaultPolicy();
31-
const _modelMeta = modelMeta ?? getDefaultModelMeta();
49+
const _policy = options?.policy ?? getDefaultPolicy();
50+
const _modelMeta = options?.modelMeta ?? getDefaultModelMeta();
3251
return makeProxy(
3352
prisma,
3453
_modelMeta,
3554
(_prisma, model) =>
36-
new PolicyProxyHandler(_prisma as DbClientContract, _policy, _modelMeta, model, context?.user),
55+
new PolicyProxyHandler(
56+
_prisma as DbClientContract,
57+
_policy,
58+
_modelMeta,
59+
model,
60+
context?.user,
61+
options?.logPrismaQuery
62+
),
3763
'policy'
3864
);
3965
}

packages/runtime/src/enhancements/policy/logger.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,27 @@ import { EventEmitter } from 'stream';
66
* A logger that uses an existing Prisma client to emit.
77
*/
88
export class Logger {
9-
constructor(private readonly prisma: any) {}
9+
private emitter: EventEmitter | undefined;
10+
private eventNames: Array<string | symbol> = [];
1011

11-
private get emitter() {
12+
constructor(private readonly prisma: any) {
1213
const engine = (this.prisma as any).getEngine();
13-
return engine ? (engine.logEmitter as EventEmitter) : undefined;
14+
this.emitter = engine ? (engine.logEmitter as EventEmitter) : undefined;
15+
if (this.emitter) {
16+
this.eventNames = this.emitter.eventNames();
17+
}
1418
}
1519

20+
/**
21+
* Checks if a log level is enabled.
22+
*/
23+
public enabled(level: 'info' | 'warn' | 'error') {
24+
return !!this.eventNames.includes(level);
25+
}
26+
27+
/**
28+
* Generates a message with the given level.
29+
*/
1630
public log(level: 'info' | 'warn' | 'error', message: string) {
1731
this.emitter?.emit(level, {
1832
timestamp: new Date(),

0 commit comments

Comments
 (0)