mbkauthe 4.9.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/api.md +29 -178
- package/docs/db.md +1 -1
- package/docs/db.sql +305 -253
- package/index.js +5 -3
- package/lib/config/cookies.js +84 -18
- package/lib/config/index.js +3 -1
- package/lib/config/tokenScopes.js +1 -1
- package/lib/createTable.js +95 -8
- package/lib/db/AuthRepository.js +57 -16
- package/lib/db/BaseRepository.js +9 -1
- package/lib/db/dialects/postgres.js +1 -1
- package/lib/main.js +5 -5
- package/lib/middleware/auth.js +201 -218
- package/lib/middleware/index.js +13 -14
- package/lib/middleware/scopeValidator.js +8 -3
- package/lib/pool.js +5 -6
- package/lib/routes/auth.js +42 -47
- package/lib/routes/dbLogs.js +247 -29
- package/lib/routes/misc.js +6 -4
- package/lib/routes/oauth.js +19 -23
- package/lib/utils/dbQueryLogger.js +485 -80
- package/lib/utils/errors.js +1 -1
- package/lib/utils/logger.js +12 -0
- package/lib/utils/timingSafeToken.js +1 -1
- package/package.json +1 -1
- package/public/main.css +1 -1
- package/test.spec.js +515 -48
- package/views/pages/dbLogs.handlebars +618 -420
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import path from "path";
|
|
2
|
+
import crypto from "crypto";
|
|
2
3
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3
4
|
|
|
4
5
|
const isDev = process.env.env === "dev" && process.env.dbLogs === "true";
|
|
@@ -11,6 +12,12 @@ const globalQueryState = {
|
|
|
11
12
|
};
|
|
12
13
|
let autoPoolId = 0;
|
|
13
14
|
|
|
15
|
+
const callsiteCaptureSetting = (process.env.dbLogsCallsite || "true").toLowerCase();
|
|
16
|
+
const parsedCallsiteSampleRate = Number(process.env.dbLogsCallsiteSample || "1");
|
|
17
|
+
const callsiteSampleRate = Number.isFinite(parsedCallsiteSampleRate)
|
|
18
|
+
? Math.min(1, Math.max(0, parsedCallsiteSampleRate))
|
|
19
|
+
: 1;
|
|
20
|
+
|
|
14
21
|
const safeValue = (value, depth = 0, seen = new WeakSet()) => {
|
|
15
22
|
if (value == null) return value;
|
|
16
23
|
if (typeof value === "string") {
|
|
@@ -59,40 +66,81 @@ const toWorkspacePath = (filePath) => {
|
|
|
59
66
|
return rel.replace(/\\/g, "/");
|
|
60
67
|
};
|
|
61
68
|
|
|
69
|
+
const isIgnorableStackFrame = (line) =>
|
|
70
|
+
!line.startsWith("at ") ||
|
|
71
|
+
line.includes("/lib/utils/dbQueryLogger.js") ||
|
|
72
|
+
line.includes("\\lib\\utils\\dbQueryLogger.js") ||
|
|
73
|
+
line.includes("node:internal") ||
|
|
74
|
+
line.includes("internal/process");
|
|
75
|
+
|
|
76
|
+
const parseStackFrame = (frame) => {
|
|
77
|
+
const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
|
|
78
|
+
const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
|
|
79
|
+
|
|
80
|
+
if (withFunc) {
|
|
81
|
+
return {
|
|
82
|
+
function: withFunc[1],
|
|
83
|
+
file: withFunc[2],
|
|
84
|
+
line: Number(withFunc[3]),
|
|
85
|
+
column: Number(withFunc[4]),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (noFunc) {
|
|
90
|
+
return {
|
|
91
|
+
function: null,
|
|
92
|
+
file: noFunc[1],
|
|
93
|
+
line: Number(noFunc[2]),
|
|
94
|
+
column: Number(noFunc[3]),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const isNodeModulesFrame = (filePath) => /[\\/]node_modules[\\/]/i.test(filePath || "");
|
|
102
|
+
|
|
103
|
+
const shouldCaptureCallsite = () => {
|
|
104
|
+
if (callsiteCaptureSetting === "false" || callsiteCaptureSetting === "0") {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (callsiteSampleRate <= 0) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (callsiteSampleRate >= 1) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return Math.random() < callsiteSampleRate;
|
|
117
|
+
};
|
|
118
|
+
|
|
62
119
|
const buildCallsite = () => {
|
|
120
|
+
if (!shouldCaptureCallsite()) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
63
124
|
try {
|
|
64
125
|
const stack = new Error().stack || "";
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
(line) =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
line: Number(withFunc[3]),
|
|
84
|
-
column: Number(withFunc[4]),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (noFunc) {
|
|
89
|
-
return {
|
|
90
|
-
function: null,
|
|
91
|
-
file: toWorkspacePath(noFunc[1]),
|
|
92
|
-
line: Number(noFunc[2]),
|
|
93
|
-
column: Number(noFunc[3]),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
126
|
+
const frames = stack
|
|
127
|
+
.split("\n")
|
|
128
|
+
.map((line) => line.trim())
|
|
129
|
+
.filter((line) => !isIgnorableStackFrame(line))
|
|
130
|
+
.map(parseStackFrame)
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
|
|
133
|
+
if (!frames.length) return null;
|
|
134
|
+
|
|
135
|
+
const preferredFrame = frames.find((frame) => !isNodeModulesFrame(frame.file));
|
|
136
|
+
if (!preferredFrame) return null;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
function: preferredFrame.function,
|
|
140
|
+
file: toWorkspacePath(preferredFrame.file),
|
|
141
|
+
line: preferredFrame.line,
|
|
142
|
+
column: preferredFrame.column,
|
|
143
|
+
};
|
|
96
144
|
} catch {
|
|
97
145
|
return null;
|
|
98
146
|
}
|
|
@@ -100,6 +148,43 @@ const buildCallsite = () => {
|
|
|
100
148
|
return null;
|
|
101
149
|
};
|
|
102
150
|
|
|
151
|
+
const normalizeQueryText = (queryText) =>
|
|
152
|
+
String(queryText || "")
|
|
153
|
+
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
154
|
+
.replace(/--.*$/gm, " ")
|
|
155
|
+
.replace(/\$[0-9]+\b/g, "?")
|
|
156
|
+
.replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi, "?")
|
|
157
|
+
.replace(/'(?:''|[^'])*'/g, "?")
|
|
158
|
+
.replace(/\b\d+(?:\.\d+)?\b/g, "?")
|
|
159
|
+
.replace(/\s+/g, " ")
|
|
160
|
+
.trim()
|
|
161
|
+
.toLowerCase();
|
|
162
|
+
|
|
163
|
+
const buildQueryFingerprint = (queryText) => {
|
|
164
|
+
const normalizedQuery = normalizeQueryText(queryText);
|
|
165
|
+
if (!normalizedQuery) {
|
|
166
|
+
return { fingerprint: null, normalizedQuery: "" };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
normalizedQuery,
|
|
171
|
+
fingerprint: crypto
|
|
172
|
+
.createHash("sha1")
|
|
173
|
+
.update(normalizedQuery)
|
|
174
|
+
.digest("hex")
|
|
175
|
+
.slice(0, 12),
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const isSessionStoreQuery = (normalizedQuery, queryName) => {
|
|
180
|
+
const normalizedName = String(queryName || "").toLowerCase();
|
|
181
|
+
if (normalizedName.includes("session")) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return /\b(?:from|update|into|delete from)\s+"session"\b/.test(normalizedQuery || "");
|
|
186
|
+
};
|
|
187
|
+
|
|
103
188
|
const buildRequestContext = () => {
|
|
104
189
|
const store = getRequestContext();
|
|
105
190
|
const req = store?.req;
|
|
@@ -135,6 +220,59 @@ const buildReturnValue = (result) => {
|
|
|
135
220
|
return returnValue;
|
|
136
221
|
};
|
|
137
222
|
|
|
223
|
+
const buildTriggerContext = ({ request, callsite, normalizedQuery, queryName }) => {
|
|
224
|
+
const routeText = request ? `${request.method || ""} ${request.url || ""}`.trim() : "";
|
|
225
|
+
const sessionStore = isSessionStoreQuery(normalizedQuery, queryName);
|
|
226
|
+
|
|
227
|
+
if (request && sessionStore) {
|
|
228
|
+
return {
|
|
229
|
+
type: "request",
|
|
230
|
+
source: "session-store",
|
|
231
|
+
label: routeText ? `Session store during ${routeText}` : "Session store during request",
|
|
232
|
+
route: routeText || null,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (request) {
|
|
237
|
+
return {
|
|
238
|
+
type: "request",
|
|
239
|
+
source: "route",
|
|
240
|
+
label: routeText || "Request route",
|
|
241
|
+
route: routeText || null,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (callsite) {
|
|
246
|
+
const functionName = callsite.function || "(anonymous)";
|
|
247
|
+
const location = callsite.file
|
|
248
|
+
? `${callsite.file}:${callsite.line}:${callsite.column}`
|
|
249
|
+
: "unknown location";
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
type: "code",
|
|
253
|
+
source: "callsite",
|
|
254
|
+
label: `Code trigger: ${functionName} @ ${location}`,
|
|
255
|
+
route: null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (sessionStore) {
|
|
260
|
+
return {
|
|
261
|
+
type: "code",
|
|
262
|
+
source: "session-store",
|
|
263
|
+
label: "Session store outside request context",
|
|
264
|
+
route: null,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
type: "code",
|
|
270
|
+
source: "unknown",
|
|
271
|
+
label: "Code trigger (unresolved)",
|
|
272
|
+
route: null,
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
|
|
138
276
|
const recordGlobalLog = (entry) => {
|
|
139
277
|
globalQueryState.totalCount += 1;
|
|
140
278
|
globalQueryState.log.push(entry);
|
|
@@ -171,12 +309,11 @@ export const getRequestContext = () => {
|
|
|
171
309
|
};
|
|
172
310
|
|
|
173
311
|
const resolveLoggerPool = (item) => {
|
|
174
|
-
if (item && typeof item ===
|
|
312
|
+
if (item && typeof item === "object") {
|
|
175
313
|
if (item.pool && item.pool.query) {
|
|
176
314
|
return { pool: item.pool, name: item.name || item.pool.__mbkQueryLoggerName || item.pool.name };
|
|
177
315
|
}
|
|
178
316
|
if (item.query) {
|
|
179
|
-
// Don't force 'default' here; let getPoolName assign a non-default auto name.
|
|
180
317
|
return { pool: item, name: item.__mbkQueryLoggerName || item.name || item.options?.application_name || null };
|
|
181
318
|
}
|
|
182
319
|
}
|
|
@@ -188,16 +325,35 @@ const getPoolName = (pool, fallbackName) => {
|
|
|
188
325
|
if (pool.__mbkQueryLoggerName) return pool.__mbkQueryLoggerName;
|
|
189
326
|
if (pool.name) return pool.name;
|
|
190
327
|
if (pool.options && pool.options.application_name) return pool.options.application_name;
|
|
191
|
-
// never return "default"; always provide an actual pool name
|
|
192
328
|
autoPoolId += 1;
|
|
193
329
|
return `pool-${autoPoolId}`;
|
|
194
330
|
};
|
|
195
331
|
|
|
332
|
+
const parseQueryArgs = (args) => {
|
|
333
|
+
let queryText = "";
|
|
334
|
+
let queryName = "";
|
|
335
|
+
let queryValues;
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
if (typeof args[0] === "string") {
|
|
339
|
+
queryText = args[0];
|
|
340
|
+
queryValues = Array.isArray(args[1]) ? args[1] : undefined;
|
|
341
|
+
} else if (args[0] && typeof args[0] === "object") {
|
|
342
|
+
queryText = args[0].text || "";
|
|
343
|
+
queryName = args[0].name || "";
|
|
344
|
+
queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
queryText = "";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return { queryText, queryName, queryValues };
|
|
351
|
+
};
|
|
352
|
+
|
|
196
353
|
const attachSinglePool = (pool, poolName = null) => {
|
|
197
354
|
if (!pool) return;
|
|
198
355
|
|
|
199
356
|
if (pool.__mbkQueryLoggerInstalled) {
|
|
200
|
-
// Allow late explicit naming to override a previous auto name.
|
|
201
357
|
if (poolName && pool.__mbkQueryLoggerName !== poolName) {
|
|
202
358
|
pool.__mbkQueryLoggerName = poolName;
|
|
203
359
|
}
|
|
@@ -210,6 +366,7 @@ const attachSinglePool = (pool, poolName = null) => {
|
|
|
210
366
|
let dbQueryCount = 0;
|
|
211
367
|
const dbQueryLog = [];
|
|
212
368
|
const originalQuery = pool.query.bind(pool);
|
|
369
|
+
const originalConnect = typeof pool.connect === "function" ? pool.connect.bind(pool) : null;
|
|
213
370
|
|
|
214
371
|
const recordPoolLog = (entry) => {
|
|
215
372
|
dbQueryCount += 1;
|
|
@@ -220,80 +377,328 @@ const attachSinglePool = (pool, poolName = null) => {
|
|
|
220
377
|
recordGlobalLog(entry);
|
|
221
378
|
};
|
|
222
379
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
380
|
+
const recordLogEntry = ({
|
|
381
|
+
queryText,
|
|
382
|
+
queryName,
|
|
383
|
+
queryValues,
|
|
384
|
+
callsiteSnapshot,
|
|
385
|
+
success,
|
|
386
|
+
error,
|
|
387
|
+
result,
|
|
388
|
+
durationMs,
|
|
389
|
+
executionDurationMs,
|
|
390
|
+
poolWait,
|
|
391
|
+
}) => {
|
|
392
|
+
const request = buildRequestContext();
|
|
393
|
+
const returnValue = buildReturnValue(result);
|
|
394
|
+
const { fingerprint, normalizedQuery } = buildQueryFingerprint(queryText);
|
|
395
|
+
const trigger = buildTriggerContext({
|
|
396
|
+
request,
|
|
397
|
+
callsite: callsiteSnapshot,
|
|
398
|
+
normalizedQuery,
|
|
399
|
+
queryName,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
recordPoolLog({
|
|
403
|
+
time: new Date().toISOString(),
|
|
404
|
+
query: queryText,
|
|
405
|
+
normalizedQuery,
|
|
406
|
+
fingerprint,
|
|
407
|
+
name: queryName || undefined,
|
|
408
|
+
values: queryValues,
|
|
409
|
+
durationMs,
|
|
410
|
+
executionDurationMs,
|
|
411
|
+
success,
|
|
412
|
+
error: error ? { message: error.message, code: error.code } : undefined,
|
|
413
|
+
returnValue,
|
|
414
|
+
request,
|
|
415
|
+
trigger,
|
|
416
|
+
pool: {
|
|
417
|
+
name: pool.__mbkQueryLoggerName,
|
|
418
|
+
total: pool.totalCount,
|
|
419
|
+
idle: pool.idleCount,
|
|
420
|
+
waiting: pool.waitingCount,
|
|
421
|
+
},
|
|
422
|
+
poolWait,
|
|
423
|
+
callsite: callsiteSnapshot,
|
|
424
|
+
});
|
|
425
|
+
};
|
|
227
426
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
queryValues = Array.isArray(args[1]) ? args[1] : undefined;
|
|
232
|
-
} else if (args[0] && typeof args[0] === "object") {
|
|
233
|
-
queryText = args[0].text || "";
|
|
234
|
-
queryName = args[0].name || "";
|
|
235
|
-
queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
|
|
236
|
-
}
|
|
237
|
-
} catch {
|
|
238
|
-
queryText = "";
|
|
427
|
+
const instrumentClient = (client, acquisition = null) => {
|
|
428
|
+
if (!client || typeof client.query !== "function") {
|
|
429
|
+
return client;
|
|
239
430
|
}
|
|
240
431
|
|
|
241
|
-
|
|
242
|
-
|
|
432
|
+
client.__mbkQueryLoggerAcquisition = acquisition
|
|
433
|
+
? { ...acquisition, attributedToQuery: false }
|
|
434
|
+
: null;
|
|
435
|
+
|
|
436
|
+
if (!client.__mbkQueryLoggerOriginalQuery) {
|
|
437
|
+
client.__mbkQueryLoggerOriginalQuery = client.query.bind(client);
|
|
243
438
|
}
|
|
244
439
|
|
|
245
|
-
|
|
246
|
-
|
|
440
|
+
if (!client.__mbkQueryLoggerReleaseWrapped && typeof client.release === "function") {
|
|
441
|
+
const originalRelease = client.release.bind(client);
|
|
442
|
+
client.release = (...args) => {
|
|
443
|
+
client.__mbkQueryLoggerAcquisition = null;
|
|
444
|
+
return originalRelease(...args);
|
|
445
|
+
};
|
|
446
|
+
client.__mbkQueryLoggerReleaseWrapped = true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (client.__mbkQueryLoggerQueryWrapped) {
|
|
450
|
+
return client;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
client.query = (...args) => {
|
|
454
|
+
const { queryText, queryName, queryValues } = parseQueryArgs(args);
|
|
455
|
+
if (!queryText) {
|
|
456
|
+
return client.__mbkQueryLoggerOriginalQuery(...args);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const callsiteSnapshot = buildCallsite();
|
|
460
|
+
const executionStartTime = process.hrtime.bigint();
|
|
461
|
+
const acquisitionMeta = client.__mbkQueryLoggerAcquisition;
|
|
462
|
+
const waitMs = acquisitionMeta && acquisitionMeta.attributedToQuery
|
|
463
|
+
? 0
|
|
464
|
+
: acquisitionMeta?.waitMs || 0;
|
|
465
|
+
const waitingBefore = acquisitionMeta?.waitingBefore || 0;
|
|
466
|
+
const waitSource = acquisitionMeta?.source || "pool.connect";
|
|
467
|
+
|
|
468
|
+
if (acquisitionMeta && !acquisitionMeta.attributedToQuery) {
|
|
469
|
+
acquisitionMeta.attributedToQuery = true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const finalize = (success, error, result) => {
|
|
473
|
+
const executionDurationMs = Number(process.hrtime.bigint() - executionStartTime) / 1_000_000;
|
|
474
|
+
|
|
475
|
+
recordLogEntry({
|
|
476
|
+
queryText,
|
|
477
|
+
queryName,
|
|
478
|
+
queryValues,
|
|
479
|
+
callsiteSnapshot,
|
|
480
|
+
success,
|
|
481
|
+
error,
|
|
482
|
+
result,
|
|
483
|
+
durationMs: executionDurationMs + waitMs,
|
|
484
|
+
executionDurationMs,
|
|
485
|
+
poolWait: {
|
|
486
|
+
source: waitSource,
|
|
487
|
+
waitMs,
|
|
488
|
+
waitingBefore,
|
|
489
|
+
waitingAfter: pool.waitingCount,
|
|
490
|
+
hadPoolPressure: waitingBefore > 0 || waitMs > 0,
|
|
491
|
+
captured: true,
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const result = client.__mbkQueryLoggerOriginalQuery(...args);
|
|
498
|
+
if (result && typeof result.then === "function") {
|
|
499
|
+
return result
|
|
500
|
+
.then((res) => {
|
|
501
|
+
finalize(true, null, res);
|
|
502
|
+
return res;
|
|
503
|
+
})
|
|
504
|
+
.catch((err) => {
|
|
505
|
+
finalize(false, err);
|
|
506
|
+
throw err;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
finalize(true, null, result);
|
|
511
|
+
return result;
|
|
512
|
+
} catch (err) {
|
|
513
|
+
finalize(false, err);
|
|
514
|
+
throw err;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
client.__mbkQueryLoggerQueryWrapped = true;
|
|
519
|
+
return client;
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
if (originalConnect) {
|
|
523
|
+
pool.connect = (...args) => {
|
|
524
|
+
const connectStartTime = process.hrtime.bigint();
|
|
525
|
+
const waitingBefore = pool.waitingCount;
|
|
526
|
+
|
|
527
|
+
if (typeof args[0] === "function") {
|
|
528
|
+
const callback = args[0];
|
|
529
|
+
return originalConnect((err, client, done) => {
|
|
530
|
+
if (err || !client) {
|
|
531
|
+
callback(err, client, done);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
callback(
|
|
536
|
+
null,
|
|
537
|
+
instrumentClient(client, {
|
|
538
|
+
source: "pool.connect",
|
|
539
|
+
waitMs: Number(process.hrtime.bigint() - connectStartTime) / 1_000_000,
|
|
540
|
+
waitingBefore,
|
|
541
|
+
}),
|
|
542
|
+
done
|
|
543
|
+
);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return originalConnect(...args).then((client) =>
|
|
548
|
+
instrumentClient(client, {
|
|
549
|
+
source: "pool.connect",
|
|
550
|
+
waitMs: Number(process.hrtime.bigint() - connectStartTime) / 1_000_000,
|
|
551
|
+
waitingBefore,
|
|
552
|
+
})
|
|
553
|
+
);
|
|
554
|
+
};
|
|
555
|
+
}
|
|
247
556
|
|
|
248
|
-
|
|
557
|
+
const runDirectLoggedPoolQuery = (args, { queryText, queryName, queryValues }) => {
|
|
558
|
+
const callsiteSnapshot = buildCallsite();
|
|
559
|
+
const startTime = process.hrtime.bigint();
|
|
560
|
+
const waitingBefore = pool.waitingCount;
|
|
561
|
+
const finalize = (success, error, result) => {
|
|
249
562
|
const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
name: queryName || undefined,
|
|
257
|
-
values: queryValues,
|
|
258
|
-
durationMs,
|
|
563
|
+
|
|
564
|
+
recordLogEntry({
|
|
565
|
+
queryText,
|
|
566
|
+
queryName,
|
|
567
|
+
queryValues,
|
|
568
|
+
callsiteSnapshot,
|
|
259
569
|
success,
|
|
260
|
-
error
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
570
|
+
error,
|
|
571
|
+
result,
|
|
572
|
+
durationMs,
|
|
573
|
+
executionDurationMs: durationMs,
|
|
574
|
+
poolWait: {
|
|
575
|
+
source: "pool.query",
|
|
576
|
+
waitMs: 0,
|
|
577
|
+
waitingBefore,
|
|
578
|
+
waitingAfter: pool.waitingCount,
|
|
579
|
+
hadPoolPressure: waitingBefore > 0,
|
|
580
|
+
captured: false,
|
|
268
581
|
},
|
|
269
|
-
|
|
582
|
+
});
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const callbackIndex = args.findIndex((arg) => typeof arg === "function");
|
|
586
|
+
if (callbackIndex >= 0) {
|
|
587
|
+
const wrappedArgs = [...args];
|
|
588
|
+
const originalCallback = wrappedArgs[callbackIndex];
|
|
589
|
+
wrappedArgs[callbackIndex] = (err, result) => {
|
|
590
|
+
finalize(!err, err, result);
|
|
591
|
+
return originalCallback(err, result);
|
|
270
592
|
};
|
|
271
593
|
|
|
272
|
-
|
|
273
|
-
|
|
594
|
+
try {
|
|
595
|
+
return originalQuery(...wrappedArgs);
|
|
596
|
+
} catch (err) {
|
|
597
|
+
finalize(false, err, null);
|
|
598
|
+
throw err;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
274
601
|
|
|
275
602
|
try {
|
|
276
603
|
const result = originalQuery(...args);
|
|
277
604
|
if (result && typeof result.then === "function") {
|
|
278
605
|
return result
|
|
279
606
|
.then((res) => {
|
|
280
|
-
|
|
607
|
+
finalize(true, null, res);
|
|
281
608
|
return res;
|
|
282
609
|
})
|
|
283
610
|
.catch((err) => {
|
|
284
|
-
|
|
611
|
+
finalize(false, err, null);
|
|
285
612
|
throw err;
|
|
286
613
|
});
|
|
287
614
|
}
|
|
288
615
|
|
|
289
|
-
|
|
616
|
+
finalize(true, null, result);
|
|
290
617
|
return result;
|
|
291
618
|
} catch (err) {
|
|
292
|
-
|
|
619
|
+
finalize(false, err, null);
|
|
293
620
|
throw err;
|
|
294
621
|
}
|
|
295
622
|
};
|
|
296
623
|
|
|
624
|
+
pool.query = (...args) => {
|
|
625
|
+
const { queryText, queryName, queryValues } = parseQueryArgs(args);
|
|
626
|
+
const usesCallback = args.some((arg) => typeof arg === "function");
|
|
627
|
+
|
|
628
|
+
if (!queryText) {
|
|
629
|
+
return originalQuery(...args);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (usesCallback || !originalConnect) {
|
|
633
|
+
return runDirectLoggedPoolQuery(args, { queryText, queryName, queryValues });
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const callsiteSnapshot = buildCallsite();
|
|
637
|
+
const connectStartTime = process.hrtime.bigint();
|
|
638
|
+
const waitingBefore = pool.waitingCount;
|
|
639
|
+
|
|
640
|
+
return originalConnect()
|
|
641
|
+
.then(async (client) => {
|
|
642
|
+
const waitMs = Number(process.hrtime.bigint() - connectStartTime) / 1_000_000;
|
|
643
|
+
const rawQuery = client.query.bind(client);
|
|
644
|
+
const release = typeof client.release === "function" ? client.release.bind(client) : null;
|
|
645
|
+
const executionStartTime = process.hrtime.bigint();
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
const result = await rawQuery(...args);
|
|
649
|
+
const executionDurationMs = Number(process.hrtime.bigint() - executionStartTime) / 1_000_000;
|
|
650
|
+
|
|
651
|
+
recordLogEntry({
|
|
652
|
+
queryText,
|
|
653
|
+
queryName,
|
|
654
|
+
queryValues,
|
|
655
|
+
callsiteSnapshot,
|
|
656
|
+
success: true,
|
|
657
|
+
error: null,
|
|
658
|
+
result,
|
|
659
|
+
durationMs: executionDurationMs + waitMs,
|
|
660
|
+
executionDurationMs,
|
|
661
|
+
poolWait: {
|
|
662
|
+
source: "pool.query",
|
|
663
|
+
waitMs,
|
|
664
|
+
waitingBefore,
|
|
665
|
+
waitingAfter: pool.waitingCount,
|
|
666
|
+
hadPoolPressure: waitingBefore > 0 || waitMs > 0,
|
|
667
|
+
captured: true,
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
return result;
|
|
672
|
+
} catch (err) {
|
|
673
|
+
const executionDurationMs = Number(process.hrtime.bigint() - executionStartTime) / 1_000_000;
|
|
674
|
+
|
|
675
|
+
recordLogEntry({
|
|
676
|
+
queryText,
|
|
677
|
+
queryName,
|
|
678
|
+
queryValues,
|
|
679
|
+
callsiteSnapshot,
|
|
680
|
+
success: false,
|
|
681
|
+
error: err,
|
|
682
|
+
result: null,
|
|
683
|
+
durationMs: executionDurationMs + waitMs,
|
|
684
|
+
executionDurationMs,
|
|
685
|
+
poolWait: {
|
|
686
|
+
source: "pool.query",
|
|
687
|
+
waitMs,
|
|
688
|
+
waitingBefore,
|
|
689
|
+
waitingAfter: pool.waitingCount,
|
|
690
|
+
hadPoolPressure: waitingBefore > 0 || waitMs > 0,
|
|
691
|
+
captured: true,
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
throw err;
|
|
696
|
+
} finally {
|
|
697
|
+
release?.();
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
};
|
|
701
|
+
|
|
297
702
|
pool.getQueryCount = () => dbQueryCount;
|
|
298
703
|
pool.resetQueryCount = () => {
|
|
299
704
|
dbQueryCount = 0;
|
package/lib/utils/errors.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import createDebug from "debug";
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
createDebug.enable(process.env.DEBUG || "");
|
|
6
|
+
|
|
7
|
+
export const createLogger = (namespace = "") => {
|
|
8
|
+
const suffix = namespace ? `:${namespace}` : "";
|
|
9
|
+
return createDebug(`mbkauthe${suffix}`);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const logDebug = createLogger();
|