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 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: "slow",
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: 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 MAX_LOG_STRING_LEN = 100;
23
- const MAX_LOG_ARRAY_ITEMS = 100;
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 <= MAX_LOG_STRING_LEN) return val;
335
+ if (val.length <= maxLogStringLen) return val;
305
336
  stats.truncatedStrings = (stats.truncatedStrings || 0) + 1;
306
- return val.slice(0, MAX_LOG_STRING_LEN);
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 safeToString(val: any, visited: WeakSet<object>, stats: Record<string, number>): string {
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 = (_k: string, v: any) => {
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 isSensitiveKey(key: string): boolean {
358
- const lower = String(key).toLowerCase();
359
- if (sensitiveKeySet.has(lower)) return true;
360
-
361
- for (const suffix of sensitiveSuffixMatchers) {
362
- if (lower.endsWith(suffix)) return true;
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
- return false;
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 sanitizeNestedValue(val: any, visited: WeakSet<object>, stats: Record<string, number>): any {
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
- const errObj: Record<string, any> = {
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
- stats.valuesStringified = (stats.valuesStringified || 0) + 1;
399
- const str = safeToString(val, visited, stats);
400
- return truncateString(str, stats);
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
- function sanitizeObjectFirstLayer(obj: Record<string, any>, visited: WeakSet<object>, stats: Record<string, number>): Record<string, any> {
404
- if (visited.has(obj)) {
451
+ // 防环(根节点)
452
+ if (visited.has(val as object)) {
405
453
  stats.circularRefs = (stats.circularRefs || 0) + 1;
406
- return { value: "[Circular]" };
454
+ return "[Circular]";
407
455
  }
408
- visited.add(obj);
456
+ visited.add(val as object);
409
457
 
410
- const out: Record<string, any> = {};
411
- for (const [key, val] of Object.entries(obj)) {
412
- if (isSensitiveKey(key)) {
413
- stats.maskedKeys = (stats.maskedKeys || 0) + 1;
414
- out[key] = "[MASKED]";
415
- continue;
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
- function sanitizeArray(arr: any[], visited: WeakSet<object>, stats: Record<string, number>): any[] {
423
- const max = MAX_LOG_ARRAY_ITEMS;
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
- const out: any[] = [];
428
- for (let i = 0; i < limit; i++) {
429
- const item = arr[i];
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
- if (item instanceof Error) {
435
- const errObj: Record<string, any> = {
436
- name: item.name || "Error",
437
- message: truncateString(item.message || "", stats)
438
- };
439
- if (typeof item.stack === "string") {
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 (typeof item === "string") {
446
- out.push(truncateString(item, stats));
447
- continue;
497
+ if (nodes >= sanitizeNodesLimit) {
498
+ stats.nodesLimited = (stats.nodesLimited || 0) + 1;
499
+ dst[key] = stringifyPreview(child, visited, stats);
500
+ return;
448
501
  }
449
- if (typeof item === "number" || typeof item === "boolean" || item === null || item === undefined) {
450
- out.push(item);
451
- continue;
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
- if (typeof item === "bigint") {
454
- out.push(item);
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
- stats.valuesStringified = (stats.valuesStringified || 0) + 1;
460
- const str = safeToString(item, visited, stats);
461
- out.push(truncateString(str, stats));
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
- if (len > max) {
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 out;
569
+ return rootOut;
470
570
  }
471
571
 
472
572
  function sanitizeTopValue(val: any, visited: WeakSet<object>, stats: Record<string, number>): any {
473
- if (val === null || val === undefined) return val;
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 = stats.maskedKeys > 0 || stats.truncatedStrings > 0 || stats.arraysTruncated > 0 || stats.arrayItemsOmitted > 0 || stats.valuesStringified > 0 || stats.circularRefs > 0;
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.11",
4
- "gitHead": "b2a7116ecbf488ef3e7e74c5cb1da1ff33a64f2c",
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
@@ -37,6 +37,9 @@ export interface DatabaseConfig {
37
37
  database?: string;
38
38
  /** 连接池最大连接数 @default 1 */
39
39
  poolMax?: number;
40
+
41
+ /** 是否打印数据库操作日志 (0: 关闭, 1: 开启) @default 0 */
42
+ debug?: number;
40
43
  }
41
44
 
42
45
  /**
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
  }