befly 3.10.11 → 3.10.13
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/befly.config.ts +5 -2
- package/lib/dbHelper.ts +25 -6
- package/lib/logger.ts +202 -113
- package/package.json +2 -2
- package/plugins/db.ts +4 -1
- package/types/befly.d.ts +3 -0
- package/types/logger.d.ts +35 -0
package/befly.config.ts
CHANGED
|
@@ -26,7 +26,9 @@ const defaultOptions: BeflyOptions = {
|
|
|
26
26
|
excludeFields: ["password", "token", "secret"],
|
|
27
27
|
dir: "./logs",
|
|
28
28
|
console: 1,
|
|
29
|
-
maxSize: 10485760 // 10MB
|
|
29
|
+
maxSize: 10485760, // 10MB
|
|
30
|
+
maxStringLen: 100,
|
|
31
|
+
maxArrayItems: 100
|
|
30
32
|
},
|
|
31
33
|
|
|
32
34
|
// ========== 数据库配置 ==========
|
|
@@ -37,7 +39,8 @@ const defaultOptions: BeflyOptions = {
|
|
|
37
39
|
username: "root",
|
|
38
40
|
password: "root",
|
|
39
41
|
database: "befly_demo",
|
|
40
|
-
poolMax: 10
|
|
42
|
+
poolMax: 10,
|
|
43
|
+
debug: 0
|
|
41
44
|
},
|
|
42
45
|
|
|
43
46
|
// ========== Redis 配置 ==========
|
package/lib/dbHelper.ts
CHANGED
|
@@ -36,16 +36,18 @@ export class DbHelper {
|
|
|
36
36
|
private dialect: DbDialect;
|
|
37
37
|
private sql: any = null;
|
|
38
38
|
private isTransaction: boolean = false;
|
|
39
|
+
private debug: number = 0;
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* 构造函数
|
|
42
43
|
* @param redis - Redis 实例
|
|
43
44
|
* @param sql - Bun SQL 客户端(可选,用于事务)
|
|
44
45
|
*/
|
|
45
|
-
constructor(options: { redis: RedisCacheLike; sql?: any | null; dialect?: DbDialect }) {
|
|
46
|
+
constructor(options: { redis: RedisCacheLike; sql?: any | null; dialect?: DbDialect; debug?: number }) {
|
|
46
47
|
this.redis = options.redis;
|
|
47
48
|
this.sql = options.sql || null;
|
|
48
49
|
this.isTransaction = !!options.sql;
|
|
50
|
+
this.debug = options.debug === 1 ? 1 : 0;
|
|
49
51
|
|
|
50
52
|
// 默认使用 MySQL 方言(当前 core 的表结构/语法也主要基于 MySQL)
|
|
51
53
|
this.dialect = options.dialect ? options.dialect : new MySqlDialect();
|
|
@@ -182,12 +184,28 @@ export class DbHelper {
|
|
|
182
184
|
// 计算执行时间
|
|
183
185
|
const duration = Date.now() - startTime;
|
|
184
186
|
|
|
187
|
+
if (this.debug === 1) {
|
|
188
|
+
Logger.debug(
|
|
189
|
+
{
|
|
190
|
+
subsystem: "db",
|
|
191
|
+
event: "db_sql",
|
|
192
|
+
dbEvent: "query",
|
|
193
|
+
duration: duration,
|
|
194
|
+
sqlPreview: sqlStr,
|
|
195
|
+
paramsCount: (params || []).length,
|
|
196
|
+
params: params || []
|
|
197
|
+
},
|
|
198
|
+
"DB"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
185
202
|
// 慢查询警告(超过 5000ms)
|
|
186
203
|
if (duration > 5000) {
|
|
187
204
|
Logger.warn(
|
|
188
205
|
{
|
|
189
206
|
subsystem: "db",
|
|
190
|
-
event: "
|
|
207
|
+
event: "db_sql",
|
|
208
|
+
dbEvent: "slow",
|
|
191
209
|
duration: duration,
|
|
192
210
|
sqlPreview: sqlStr,
|
|
193
211
|
params: params || [],
|
|
@@ -200,12 +218,13 @@ export class DbHelper {
|
|
|
200
218
|
return result;
|
|
201
219
|
} catch (error: any) {
|
|
202
220
|
const duration = Date.now() - startTime;
|
|
203
|
-
|
|
204
|
-
const sqlPreview = sqlStr.length > 200 ? sqlStr.substring(0, 200) + "..." : sqlStr;
|
|
205
221
|
Logger.error(
|
|
206
222
|
{
|
|
223
|
+
subsystem: "db",
|
|
224
|
+
event: "db_sql",
|
|
225
|
+
dbEvent: "error",
|
|
207
226
|
err: error,
|
|
208
|
-
sqlPreview:
|
|
227
|
+
sqlPreview: sqlStr,
|
|
209
228
|
params: params || [],
|
|
210
229
|
duration: duration
|
|
211
230
|
},
|
|
@@ -751,7 +770,7 @@ export class DbHelper {
|
|
|
751
770
|
// 使用 Bun SQL 的 begin 方法开启事务
|
|
752
771
|
// begin 方法会自动处理 commit/rollback
|
|
753
772
|
return await this.sql.begin(async (tx: any) => {
|
|
754
|
-
const trans = new DbHelper({ redis: this.redis, sql: tx, dialect: this.dialect });
|
|
773
|
+
const trans = new DbHelper({ redis: this.redis, sql: tx, dialect: this.dialect, debug: this.debug });
|
|
755
774
|
return await callback(trans);
|
|
756
775
|
});
|
|
757
776
|
}
|
package/lib/logger.ts
CHANGED
|
@@ -19,8 +19,21 @@ import { getCtx } from "./asyncContext.js";
|
|
|
19
19
|
// 为避免相对路径的 logs 目录随着 cwd 变化,使用模块加载时的初始 cwd 作为锚点。
|
|
20
20
|
const INITIAL_CWD = process.cwd();
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
const
|
|
22
|
+
const DEFAULT_LOG_STRING_LEN = 100;
|
|
23
|
+
const DEFAULT_LOG_ARRAY_ITEMS = 100;
|
|
24
|
+
|
|
25
|
+
let maxLogStringLen = DEFAULT_LOG_STRING_LEN;
|
|
26
|
+
let maxLogArrayItems = DEFAULT_LOG_ARRAY_ITEMS;
|
|
27
|
+
|
|
28
|
+
// 为避免递归导致栈溢出/性能抖动:使用非递归遍历,并对深度/节点数做硬限制。
|
|
29
|
+
// 说明:这不是业务数据结构的“真实深度”,而是日志清洗的最大深入层级(越大越重)。
|
|
30
|
+
const DEFAULT_LOG_SANITIZE_DEPTH = 3;
|
|
31
|
+
const DEFAULT_LOG_OBJECT_KEYS = 100;
|
|
32
|
+
const DEFAULT_LOG_SANITIZE_NODES = 500;
|
|
33
|
+
|
|
34
|
+
let sanitizeDepthLimit = DEFAULT_LOG_SANITIZE_DEPTH;
|
|
35
|
+
let sanitizeObjectKeysLimit = DEFAULT_LOG_OBJECT_KEYS;
|
|
36
|
+
let sanitizeNodesLimit = DEFAULT_LOG_SANITIZE_NODES;
|
|
24
37
|
|
|
25
38
|
const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
|
|
26
39
|
|
|
@@ -45,6 +58,15 @@ let config: LoggerConfig = {
|
|
|
45
58
|
maxSize: 10
|
|
46
59
|
};
|
|
47
60
|
|
|
61
|
+
function normalizePositiveInt(value: any, fallback: number, min: number, max: number): number {
|
|
62
|
+
if (typeof value !== "number") return fallback;
|
|
63
|
+
if (!Number.isFinite(value)) return fallback;
|
|
64
|
+
const v = Math.floor(value);
|
|
65
|
+
if (v < min) return fallback;
|
|
66
|
+
if (v > max) return max;
|
|
67
|
+
return v;
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
function resolveLogDir(): string {
|
|
49
71
|
const rawDir = config.dir || "./logs";
|
|
50
72
|
if (nodePathIsAbsolute(rawDir)) {
|
|
@@ -121,6 +143,15 @@ export function configure(cfg: LoggerConfig): void {
|
|
|
121
143
|
didPruneOldLogFiles = false;
|
|
122
144
|
didEnsureLogDir = false;
|
|
123
145
|
|
|
146
|
+
// 运行时清洗上限(可配置)
|
|
147
|
+
sanitizeDepthLimit = normalizePositiveInt(config.sanitizeDepth, DEFAULT_LOG_SANITIZE_DEPTH, 1, 10);
|
|
148
|
+
sanitizeNodesLimit = normalizePositiveInt(config.sanitizeNodes, DEFAULT_LOG_SANITIZE_NODES, 50, 20000);
|
|
149
|
+
sanitizeObjectKeysLimit = normalizePositiveInt(config.sanitizeObjectKeys, DEFAULT_LOG_OBJECT_KEYS, 10, 5000);
|
|
150
|
+
|
|
151
|
+
// 运行时截断上限(可配置)
|
|
152
|
+
maxLogStringLen = normalizePositiveInt(config.maxStringLen, DEFAULT_LOG_STRING_LEN, 20, 200000);
|
|
153
|
+
maxLogArrayItems = normalizePositiveInt(config.maxArrayItems, DEFAULT_LOG_ARRAY_ITEMS, 10, 5000);
|
|
154
|
+
|
|
124
155
|
// 仅支持数组配置:excludeFields?: string[]
|
|
125
156
|
const userPatterns = Array.isArray(config.excludeFields) ? config.excludeFields : [];
|
|
126
157
|
const patterns = [...BUILTIN_SENSITIVE_KEYS, ...userPatterns]
|
|
@@ -301,12 +332,34 @@ function getErrorLogger(): pino.Logger {
|
|
|
301
332
|
}
|
|
302
333
|
|
|
303
334
|
function truncateString(val: string, stats: Record<string, number>): string {
|
|
304
|
-
if (val.length <=
|
|
335
|
+
if (val.length <= maxLogStringLen) return val;
|
|
305
336
|
stats.truncatedStrings = (stats.truncatedStrings || 0) + 1;
|
|
306
|
-
return val.slice(0,
|
|
337
|
+
return val.slice(0, maxLogStringLen);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function isSensitiveKey(key: string): boolean {
|
|
341
|
+
const lower = String(key).toLowerCase();
|
|
342
|
+
if (sensitiveKeySet.has(lower)) return true;
|
|
343
|
+
|
|
344
|
+
for (const suffix of sensitiveSuffixMatchers) {
|
|
345
|
+
if (lower.endsWith(suffix)) return true;
|
|
346
|
+
}
|
|
347
|
+
for (const prefix of sensitivePrefixMatchers) {
|
|
348
|
+
if (lower.startsWith(prefix)) return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (sensitiveContainsRegex) {
|
|
352
|
+
return sensitiveContainsRegex.test(lower);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for (const part of sensitiveContainsMatchers) {
|
|
356
|
+
if (lower.includes(part)) return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return false;
|
|
307
360
|
}
|
|
308
361
|
|
|
309
|
-
function
|
|
362
|
+
function safeToStringMasked(val: any, visited: WeakSet<object>, stats: Record<string, number>): string {
|
|
310
363
|
if (typeof val === "string") return val;
|
|
311
364
|
|
|
312
365
|
if (val instanceof Error) {
|
|
@@ -334,7 +387,13 @@ function safeToString(val: any, visited: WeakSet<object>, stats: Record<string,
|
|
|
334
387
|
|
|
335
388
|
try {
|
|
336
389
|
const localVisited = visited;
|
|
337
|
-
const replacer = (
|
|
390
|
+
const replacer = (k: string, v: any) => {
|
|
391
|
+
// JSON.stringify 的根节点 key 为空字符串
|
|
392
|
+
if (k && isSensitiveKey(k)) {
|
|
393
|
+
stats.maskedKeys = (stats.maskedKeys || 0) + 1;
|
|
394
|
+
return "[MASKED]";
|
|
395
|
+
}
|
|
396
|
+
|
|
338
397
|
if (v && typeof v === "object") {
|
|
339
398
|
if (localVisited.has(v as object)) {
|
|
340
399
|
stats.circularRefs = (stats.circularRefs || 0) + 1;
|
|
@@ -354,29 +413,24 @@ function safeToString(val: any, visited: WeakSet<object>, stats: Record<string,
|
|
|
354
413
|
}
|
|
355
414
|
}
|
|
356
415
|
|
|
357
|
-
function
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
for (const prefix of sensitivePrefixMatchers) {
|
|
365
|
-
if (lower.startsWith(prefix)) return true;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (sensitiveContainsRegex) {
|
|
369
|
-
return sensitiveContainsRegex.test(lower);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
for (const part of sensitiveContainsMatchers) {
|
|
373
|
-
if (lower.includes(part)) return true;
|
|
416
|
+
function sanitizeErrorValue(err: Error, stats: Record<string, number>): Record<string, any> {
|
|
417
|
+
const errObj: Record<string, any> = {
|
|
418
|
+
name: err.name || "Error",
|
|
419
|
+
message: truncateString(err.message || "", stats)
|
|
420
|
+
};
|
|
421
|
+
if (typeof err.stack === "string") {
|
|
422
|
+
errObj.stack = truncateString(err.stack, stats);
|
|
374
423
|
}
|
|
424
|
+
return errObj;
|
|
425
|
+
}
|
|
375
426
|
|
|
376
|
-
|
|
427
|
+
function stringifyPreview(val: any, visited: WeakSet<object>, stats: Record<string, number>): string {
|
|
428
|
+
stats.valuesStringified = (stats.valuesStringified || 0) + 1;
|
|
429
|
+
const str = safeToStringMasked(val, visited, stats);
|
|
430
|
+
return truncateString(str, stats);
|
|
377
431
|
}
|
|
378
432
|
|
|
379
|
-
function
|
|
433
|
+
function sanitizeValueLimited(val: any, visited: WeakSet<object>, stats: Record<string, number>): any {
|
|
380
434
|
if (val === null || val === undefined) return val;
|
|
381
435
|
if (typeof val === "string") return truncateString(val, stats);
|
|
382
436
|
if (typeof val === "number") return val;
|
|
@@ -384,113 +438,139 @@ function sanitizeNestedValue(val: any, visited: WeakSet<object>, stats: Record<s
|
|
|
384
438
|
if (typeof val === "bigint") return val;
|
|
385
439
|
|
|
386
440
|
if (val instanceof Error) {
|
|
387
|
-
|
|
388
|
-
name: val.name || "Error",
|
|
389
|
-
message: truncateString(val.message || "", stats)
|
|
390
|
-
};
|
|
391
|
-
if (typeof val.stack === "string") {
|
|
392
|
-
errObj.stack = truncateString(val.stack, stats);
|
|
393
|
-
}
|
|
394
|
-
return errObj;
|
|
441
|
+
return sanitizeErrorValue(val, stats);
|
|
395
442
|
}
|
|
396
443
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
444
|
+
// 仅支持数组 + plain object 的结构化清洗,其余类型走字符串预览。
|
|
445
|
+
const isArr = Array.isArray(val);
|
|
446
|
+
const isObj = isPlainObject(val);
|
|
447
|
+
if (!isArr && !isObj) {
|
|
448
|
+
return stringifyPreview(val, visited, stats);
|
|
449
|
+
}
|
|
402
450
|
|
|
403
|
-
|
|
404
|
-
if (visited.has(
|
|
451
|
+
// 防环(根节点)
|
|
452
|
+
if (visited.has(val as object)) {
|
|
405
453
|
stats.circularRefs = (stats.circularRefs || 0) + 1;
|
|
406
|
-
return
|
|
454
|
+
return "[Circular]";
|
|
407
455
|
}
|
|
408
|
-
visited.add(
|
|
456
|
+
visited.add(val as object);
|
|
409
457
|
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
458
|
+
const rootOut: any = isArr ? [] : {};
|
|
459
|
+
|
|
460
|
+
type Frame = { src: any; dst: any; depth: number };
|
|
461
|
+
const stack: Frame[] = [{ src: val, dst: rootOut, depth: 1 }];
|
|
462
|
+
|
|
463
|
+
let nodes = 0;
|
|
464
|
+
|
|
465
|
+
const tryAssign = (dst: any, key: string | number, child: any, depth: number) => {
|
|
466
|
+
if (child === null || child === undefined) {
|
|
467
|
+
dst[key] = child;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (typeof child === "string") {
|
|
471
|
+
dst[key] = truncateString(child, stats);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (typeof child === "number" || typeof child === "boolean" || typeof child === "bigint") {
|
|
475
|
+
dst[key] = child;
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (child instanceof Error) {
|
|
479
|
+
dst[key] = sanitizeErrorValue(child, stats);
|
|
480
|
+
return;
|
|
416
481
|
}
|
|
417
|
-
out[key] = sanitizeNestedValue(val, visited, stats);
|
|
418
|
-
}
|
|
419
|
-
return out;
|
|
420
|
-
}
|
|
421
482
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const len = arr.length;
|
|
425
|
-
const limit = len > max ? max : len;
|
|
483
|
+
const childIsArr = Array.isArray(child);
|
|
484
|
+
const childIsObj = isPlainObject(child);
|
|
426
485
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Error)) {
|
|
431
|
-
out.push(sanitizeObjectFirstLayer(item as Record<string, any>, visited, stats));
|
|
432
|
-
continue;
|
|
486
|
+
if (!childIsArr && !childIsObj) {
|
|
487
|
+
dst[key] = stringifyPreview(child, visited, stats);
|
|
488
|
+
return;
|
|
433
489
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
errObj.stack = truncateString(item.stack, stats);
|
|
441
|
-
}
|
|
442
|
-
out.push(errObj);
|
|
443
|
-
continue;
|
|
490
|
+
|
|
491
|
+
// 深度/节点数上限:超出则降级为字符串预览
|
|
492
|
+
if (depth >= sanitizeDepthLimit) {
|
|
493
|
+
stats.depthLimited = (stats.depthLimited || 0) + 1;
|
|
494
|
+
dst[key] = stringifyPreview(child, visited, stats);
|
|
495
|
+
return;
|
|
444
496
|
}
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
|
|
497
|
+
if (nodes >= sanitizeNodesLimit) {
|
|
498
|
+
stats.nodesLimited = (stats.nodesLimited || 0) + 1;
|
|
499
|
+
dst[key] = stringifyPreview(child, visited, stats);
|
|
500
|
+
return;
|
|
448
501
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
502
|
+
|
|
503
|
+
// 防环
|
|
504
|
+
if (visited.has(child as object)) {
|
|
505
|
+
stats.circularRefs = (stats.circularRefs || 0) + 1;
|
|
506
|
+
dst[key] = "[Circular]";
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
visited.add(child as object);
|
|
510
|
+
|
|
511
|
+
const childOut: any = childIsArr ? [] : {};
|
|
512
|
+
dst[key] = childOut;
|
|
513
|
+
stack.push({ src: child, dst: childOut, depth: depth + 1 });
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
while (stack.length > 0) {
|
|
517
|
+
const frame = stack.pop() as Frame;
|
|
518
|
+
nodes = nodes + 1;
|
|
519
|
+
if (nodes > sanitizeNodesLimit) {
|
|
520
|
+
// 超出节点上限:不再深入(已入栈的节点会被忽略,留空结构由上层兜底预览)。
|
|
521
|
+
stats.nodesLimited = (stats.nodesLimited || 0) + 1;
|
|
522
|
+
break;
|
|
452
523
|
}
|
|
453
|
-
|
|
454
|
-
|
|
524
|
+
|
|
525
|
+
if (Array.isArray(frame.src)) {
|
|
526
|
+
const arr = frame.src as any[];
|
|
527
|
+
const len = arr.length;
|
|
528
|
+
const limit = len > maxLogArrayItems ? maxLogArrayItems : len;
|
|
529
|
+
|
|
530
|
+
for (let i = 0; i < limit; i++) {
|
|
531
|
+
tryAssign(frame.dst, i, arr[i], frame.depth);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (len > maxLogArrayItems) {
|
|
535
|
+
stats.arraysTruncated = (stats.arraysTruncated || 0) + 1;
|
|
536
|
+
stats.arrayItemsOmitted = (stats.arrayItemsOmitted || 0) + (len - maxLogArrayItems);
|
|
537
|
+
}
|
|
538
|
+
|
|
455
539
|
continue;
|
|
456
540
|
}
|
|
457
541
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
542
|
+
if (isPlainObject(frame.src)) {
|
|
543
|
+
const entries = Object.entries(frame.src as Record<string, any>);
|
|
544
|
+
const len = entries.length;
|
|
545
|
+
const limit = len > sanitizeObjectKeysLimit ? sanitizeObjectKeysLimit : len;
|
|
546
|
+
|
|
547
|
+
for (let i = 0; i < limit; i++) {
|
|
548
|
+
const key = entries[i][0];
|
|
549
|
+
const child = entries[i][1];
|
|
550
|
+
if (isSensitiveKey(key)) {
|
|
551
|
+
stats.maskedKeys = (stats.maskedKeys || 0) + 1;
|
|
552
|
+
frame.dst[key] = "[MASKED]";
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
tryAssign(frame.dst, key, child, frame.depth);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (len > sanitizeObjectKeysLimit) {
|
|
559
|
+
stats.objectKeysLimited = (stats.objectKeysLimited || 0) + 1;
|
|
560
|
+
stats.objectKeysOmitted = (stats.objectKeysOmitted || 0) + (len - sanitizeObjectKeysLimit);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
463
565
|
|
|
464
|
-
|
|
465
|
-
stats.arraysTruncated = (stats.arraysTruncated || 0) + 1;
|
|
466
|
-
stats.arrayItemsOmitted = (stats.arrayItemsOmitted || 0) + (len - max);
|
|
566
|
+
// 兜底:理论上不会到这里(frame 只会压入 array/plain object)
|
|
467
567
|
}
|
|
468
568
|
|
|
469
|
-
return
|
|
569
|
+
return rootOut;
|
|
470
570
|
}
|
|
471
571
|
|
|
472
572
|
function sanitizeTopValue(val: any, visited: WeakSet<object>, stats: Record<string, number>): any {
|
|
473
|
-
|
|
474
|
-
if (typeof val === "string") return truncateString(val, stats);
|
|
475
|
-
if (typeof val === "number") return val;
|
|
476
|
-
if (typeof val === "boolean") return val;
|
|
477
|
-
if (typeof val === "bigint") return val;
|
|
478
|
-
if (val instanceof Error) {
|
|
479
|
-
const errObj: Record<string, any> = {
|
|
480
|
-
name: val.name || "Error",
|
|
481
|
-
message: truncateString(val.message || "", stats)
|
|
482
|
-
};
|
|
483
|
-
if (typeof val.stack === "string") {
|
|
484
|
-
errObj.stack = truncateString(val.stack, stats);
|
|
485
|
-
}
|
|
486
|
-
return errObj;
|
|
487
|
-
}
|
|
488
|
-
if (Array.isArray(val)) return sanitizeArray(val, visited, stats);
|
|
489
|
-
if (isPlainObject(val)) return sanitizeObjectFirstLayer(val as Record<string, any>, visited, stats);
|
|
490
|
-
|
|
491
|
-
stats.valuesStringified = (stats.valuesStringified || 0) + 1;
|
|
492
|
-
const str = safeToString(val, visited, stats);
|
|
493
|
-
return truncateString(str, stats);
|
|
573
|
+
return sanitizeValueLimited(val, visited, stats);
|
|
494
574
|
}
|
|
495
575
|
|
|
496
576
|
function sanitizeLogObject(obj: Record<string, any>): Record<string, any> {
|
|
@@ -501,7 +581,11 @@ function sanitizeLogObject(obj: Record<string, any>): Record<string, any> {
|
|
|
501
581
|
arraysTruncated: 0,
|
|
502
582
|
arrayItemsOmitted: 0,
|
|
503
583
|
valuesStringified: 0,
|
|
504
|
-
circularRefs: 0
|
|
584
|
+
circularRefs: 0,
|
|
585
|
+
depthLimited: 0,
|
|
586
|
+
nodesLimited: 0,
|
|
587
|
+
objectKeysLimited: 0,
|
|
588
|
+
objectKeysOmitted: 0
|
|
505
589
|
};
|
|
506
590
|
|
|
507
591
|
const out: Record<string, any> = {};
|
|
@@ -514,7 +598,8 @@ function sanitizeLogObject(obj: Record<string, any>): Record<string, any> {
|
|
|
514
598
|
out[key] = sanitizeTopValue(val, visited, stats);
|
|
515
599
|
}
|
|
516
600
|
|
|
517
|
-
const hasChanges =
|
|
601
|
+
const hasChanges =
|
|
602
|
+
stats.maskedKeys > 0 || stats.truncatedStrings > 0 || stats.arraysTruncated > 0 || stats.arrayItemsOmitted > 0 || stats.valuesStringified > 0 || stats.circularRefs > 0 || stats.depthLimited > 0 || stats.nodesLimited > 0 || stats.objectKeysLimited > 0 || stats.objectKeysOmitted > 0;
|
|
518
603
|
|
|
519
604
|
if (hasChanges) {
|
|
520
605
|
out.logTrimStats = {
|
|
@@ -523,7 +608,11 @@ function sanitizeLogObject(obj: Record<string, any>): Record<string, any> {
|
|
|
523
608
|
arraysTruncated: stats.arraysTruncated,
|
|
524
609
|
arrayItemsOmitted: stats.arrayItemsOmitted,
|
|
525
610
|
valuesStringified: stats.valuesStringified,
|
|
526
|
-
circularRefs: stats.circularRefs
|
|
611
|
+
circularRefs: stats.circularRefs,
|
|
612
|
+
depthLimited: stats.depthLimited,
|
|
613
|
+
nodesLimited: stats.nodesLimited,
|
|
614
|
+
objectKeysLimited: stats.objectKeysLimited,
|
|
615
|
+
objectKeysOmitted: stats.objectKeysOmitted
|
|
527
616
|
};
|
|
528
617
|
}
|
|
529
618
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.10.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.10.13",
|
|
4
|
+
"gitHead": "391e4e5cacb1d46cf37b5aa1179c9fe0a602337d",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
package/plugins/db.ts
CHANGED
|
@@ -28,8 +28,11 @@ export default {
|
|
|
28
28
|
const resolvedDbType = rawDbType === "postgres" ? "postgresql" : rawDbType;
|
|
29
29
|
const dialect = getDialectByName(resolvedDbType === "postgresql" || resolvedDbType === "sqlite" ? resolvedDbType : "mysql");
|
|
30
30
|
|
|
31
|
+
const dbDebug = befly.config && befly.config.db ? befly.config.db.debug : undefined;
|
|
32
|
+
const debug = dbDebug === 1 ? 1 : 0;
|
|
33
|
+
|
|
31
34
|
// 创建数据库管理器实例
|
|
32
|
-
const dbManager = new DbHelper({ redis: befly.redis, sql: sql, dialect: dialect });
|
|
35
|
+
const dbManager = new DbHelper({ redis: befly.redis, sql: sql, dialect: dialect, debug: debug });
|
|
33
36
|
|
|
34
37
|
return dbManager;
|
|
35
38
|
} catch (error: any) {
|
package/types/befly.d.ts
CHANGED
package/types/logger.d.ts
CHANGED
|
@@ -27,4 +27,39 @@ export interface LoggerConfig {
|
|
|
27
27
|
console?: number;
|
|
28
28
|
/** 单个日志文件最大大小 (MB) @default 10 */
|
|
29
29
|
maxSize?: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 字符串最大长度(超过会被截断)
|
|
33
|
+
* - 影响所有结构化字段中的 string(包括 db.debug 打印的 sqlPreview)
|
|
34
|
+
* - 生产环境建议保持较小值;需要更完整 SQL 时可在开发环境调大
|
|
35
|
+
* @default 100
|
|
36
|
+
*/
|
|
37
|
+
maxStringLen?: number;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 数组最多保留的元素数量(超出部分丢弃)
|
|
41
|
+
* - 仅影响结构化日志清洗(数组裁剪)
|
|
42
|
+
* @default 100
|
|
43
|
+
*/
|
|
44
|
+
maxArrayItems?: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 结构化日志清洗:最大清洗深度(非递归遍历的 depth 上限)
|
|
48
|
+
* - 值越大,日志更“完整”,但 CPU/内存开销更高
|
|
49
|
+
* - 建议:生产环境保持较小值(默认 3),开发环境可调大(例如 5)
|
|
50
|
+
* @default 3
|
|
51
|
+
*/
|
|
52
|
+
sanitizeDepth?: number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 结构化日志清洗:最大遍历节点数(防止超大对象/数组导致性能抖动)
|
|
56
|
+
* @default 500
|
|
57
|
+
*/
|
|
58
|
+
sanitizeNodes?: number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 结构化日志清洗:对象最多保留的 key 数(超出部分丢弃)
|
|
62
|
+
* @default 100
|
|
63
|
+
*/
|
|
64
|
+
sanitizeObjectKeys?: number;
|
|
30
65
|
}
|