Skip to content

Commit 75aa9d8

Browse files
committed
Log errors for very slow queries
1 parent ffb144d commit 75aa9d8

File tree

3 files changed

+93
-4
lines changed

3 files changed

+93
-4
lines changed

apps/webapp/app/db.server.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { isValidDatabaseUrl } from "./utils/db";
1515
import { singleton } from "./utils/singleton";
1616
import { startActiveSpan } from "./v3/tracer.server";
1717
import { Span } from "@opentelemetry/api";
18+
import { queryPerformanceMonitor } from "./utils/queryPerformanceMonitor.server";
1819

1920
export type {
2021
PrismaTransactionClient,
@@ -153,13 +154,19 @@ function getClient() {
153154
},
154155
]
155156
: []) satisfies Prisma.LogDefinition[]),
156-
// verbose
157-
...((process.env.VERBOSE_PRISMA_LOGS === "1"
157+
// Query performance monitoring
158+
...((process.env.VERBOSE_PRISMA_LOGS === "1" ||
159+
process.env.VERY_SLOW_QUERY_THRESHOLD_MS !== undefined
158160
? [
159161
{
160162
emit: "event",
161163
level: "query",
162164
},
165+
]
166+
: []) satisfies Prisma.LogDefinition[]),
167+
// verbose
168+
...((process.env.VERBOSE_PRISMA_LOGS === "1"
169+
? [
163170
{
164171
emit: "stdout",
165172
level: "query",
@@ -206,6 +213,11 @@ function getClient() {
206213
});
207214
}
208215

216+
// Add query performance monitoring
217+
client.$on("query", (log) => {
218+
queryPerformanceMonitor.onQuery("writer", log);
219+
});
220+
209221
// connect eagerly
210222
client.$connect();
211223

@@ -265,13 +277,19 @@ function getReplicaClient() {
265277
},
266278
]
267279
: []) satisfies Prisma.LogDefinition[]),
268-
// verbose
269-
...((process.env.VERBOSE_PRISMA_LOGS === "1"
280+
// Query performance monitoring
281+
...((process.env.VERBOSE_PRISMA_LOGS === "1" ||
282+
process.env.VERY_SLOW_QUERY_THRESHOLD_MS !== undefined
270283
? [
271284
{
272285
emit: "event",
273286
level: "query",
274287
},
288+
]
289+
: []) satisfies Prisma.LogDefinition[]),
290+
// verbose
291+
...((process.env.VERBOSE_PRISMA_LOGS === "1"
292+
? [
275293
{
276294
emit: "stdout",
277295
level: "query",
@@ -317,6 +335,11 @@ function getReplicaClient() {
317335
});
318336
}
319337

338+
// Add query performance monitoring for replica client
339+
replicaClient.$on("query", (log) => {
340+
queryPerformanceMonitor.onQuery("replica", log);
341+
});
342+
320343
// connect eagerly
321344
replicaClient.$connect();
322345

apps/webapp/app/env.server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,8 @@ const EnvironmentSchema = z.object({
10801080
AI_RUN_FILTER_MODEL: z.string().optional(),
10811081

10821082
EVENT_LOOP_MONITOR_THRESHOLD_MS: z.coerce.number().int().default(100),
1083+
1084+
VERY_SLOW_QUERY_THRESHOLD_MS: z.coerce.number().int().optional(),
10831085
});
10841086

10851087
export type Environment = z.infer<typeof EnvironmentSchema>;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { env } from "~/env.server";
2+
import { logger } from "~/services/logger.server";
3+
4+
export interface QueryPerformanceConfig {
5+
verySlowQueryThreshold?: number; // ms
6+
maxQueryLogLength: number;
7+
}
8+
9+
export class QueryPerformanceMonitor {
10+
private config: QueryPerformanceConfig;
11+
12+
constructor(config: Partial<QueryPerformanceConfig> = {}) {
13+
this.config = {
14+
maxQueryLogLength: 1000,
15+
...config,
16+
};
17+
}
18+
19+
onQuery(
20+
clientType: "writer" | "replica",
21+
log: {
22+
duration: number;
23+
query: string;
24+
params: string;
25+
target: string;
26+
timestamp: Date;
27+
}
28+
) {
29+
if (this.config.verySlowQueryThreshold === undefined) {
30+
return;
31+
}
32+
33+
const { duration, query, params, target, timestamp } = log;
34+
35+
// Only log very slow queries as errors
36+
if (duration > this.config.verySlowQueryThreshold) {
37+
// Truncate long queries for readability
38+
const truncatedQuery =
39+
query.length > this.config.maxQueryLogLength
40+
? query.substring(0, this.config.maxQueryLogLength) + "..."
41+
: query;
42+
43+
logger.error("Prisma: very slow database query", {
44+
clientType,
45+
durationMs: duration,
46+
query: truncatedQuery,
47+
target,
48+
timestamp,
49+
paramCount: this.countParams(query),
50+
hasParams: params !== "[]" && params !== "",
51+
});
52+
}
53+
}
54+
55+
private countParams(query: string): number {
56+
// Count the number of $1, $2, etc. parameters in the query
57+
const paramMatches = query.match(/\$\d+/g);
58+
return paramMatches ? paramMatches.length : 0;
59+
}
60+
}
61+
62+
export const queryPerformanceMonitor = new QueryPerformanceMonitor({
63+
verySlowQueryThreshold: env.VERY_SLOW_QUERY_THRESHOLD_MS,
64+
});

0 commit comments

Comments
 (0)