66 lines
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
import { env } from "~/env.server";
|
|
import { logger } from "~/services/logger.server";
|
|
|
|
export interface QueryPerformanceConfig {
|
|
verySlowQueryThreshold?: number; // ms
|
|
maxQueryLogLength: number;
|
|
}
|
|
|
|
export class QueryPerformanceMonitor {
|
|
private config: QueryPerformanceConfig;
|
|
|
|
constructor(config: Partial<QueryPerformanceConfig> = {}) {
|
|
this.config = {
|
|
maxQueryLogLength: 1000,
|
|
...config,
|
|
};
|
|
}
|
|
|
|
onQuery(
|
|
clientType: "writer" | "replica",
|
|
log: {
|
|
duration: number;
|
|
query: string;
|
|
params: string;
|
|
target: string;
|
|
timestamp: Date;
|
|
}
|
|
) {
|
|
if (this.config.verySlowQueryThreshold === undefined) {
|
|
return;
|
|
}
|
|
|
|
const { duration, query, params, target, timestamp } = log;
|
|
|
|
// Slow queries are an observability signal (DB load, missing indexes,
|
|
// etc.), not an application error. Logged at warn so it lands in stdout
|
|
// without flowing to Sentry — track via metrics/dashboards instead.
|
|
if (duration > this.config.verySlowQueryThreshold) {
|
|
const truncatedQuery =
|
|
query.length > this.config.maxQueryLogLength
|
|
? query.substring(0, this.config.maxQueryLogLength) + "..."
|
|
: query;
|
|
|
|
logger.warn("Prisma: very slow database query", {
|
|
clientType,
|
|
durationMs: duration,
|
|
query: truncatedQuery,
|
|
target,
|
|
timestamp,
|
|
paramCount: this.countParams(query),
|
|
hasParams: params !== "[]" && params !== "",
|
|
});
|
|
}
|
|
}
|
|
|
|
private countParams(query: string): number {
|
|
// Count the number of $1, $2, etc. parameters in the query
|
|
const paramMatches = query.match(/\$\d+/g);
|
|
return paramMatches ? paramMatches.length : 0;
|
|
}
|
|
}
|
|
|
|
export const queryPerformanceMonitor = new QueryPerformanceMonitor({
|
|
verySlowQueryThreshold: env.VERY_SLOW_QUERY_THRESHOLD_MS,
|
|
});
|