befly 3.61.0 → 3.62.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/checks/config.js CHANGED
@@ -63,10 +63,7 @@ const configSchema = z
63
63
  logger: z
64
64
  .object({
65
65
  debug: boolIntSchema,
66
- excludeFields: z.array(noTrimString),
67
- dir: noTrimString,
68
- console: boolIntSchema,
69
- maxSize: z.int().min(1)
66
+ excludeFields: z.array(noTrimString)
70
67
  })
71
68
  .strict(),
72
69
 
@@ -19,10 +19,7 @@
19
19
  "excludeApisLog": ["/api/core/tongJi/*Report"],
20
20
  "logger": {
21
21
  "debug": 1,
22
- "excludeFields": ["password", "token", "secret"],
23
- "dir": "./logs",
24
- "console": 1,
25
- "maxSize": 20
22
+ "excludeFields": ["password", "token", "secret"]
26
23
  },
27
24
 
28
25
  "mysql": {
package/index.js CHANGED
@@ -138,6 +138,12 @@ export async function createBefly(env = {}, config = {}, menus = []) {
138
138
  });
139
139
  }
140
140
 
141
+ // 在插件加载前用项目 runMode 初始化 Logger,确保后续所有日志目录正确
142
+ Logger.configure({
143
+ runtimeEnv: mergedConfig.runMode,
144
+ ...mergedConfig.logger
145
+ });
146
+
141
147
  return new Befly({
142
148
  env: env,
143
149
  config: mergedConfig,
package/lib/logger.js CHANGED
@@ -2,19 +2,18 @@
2
2
  * 日志系统 - Bun 环境自定义实现(替换 pino / pino-roll)
3
3
  */
4
4
 
5
- import { createWriteStream, existsSync, mkdirSync } from "node:fs";
6
- import { stat } from "node:fs/promises";
5
+ import { createWriteStream, existsSync, mkdirSync, rmSync, statSync } from "node:fs";
7
6
  import { join as nodePathJoin, resolve as nodePathResolve } from "node:path";
8
7
 
9
8
  import { formatYmdHms } from "../utils/datetime.js";
10
- import { isFiniteNumber, isNumber, isPlainObject, isString } from "../utils/is.js";
9
+ import { isPlainObject } from "../utils/is.js";
11
10
  import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils.js";
12
11
  import { normalizePositiveInt } from "../utils/util.js";
13
12
 
14
13
  // 注意:Logger 可能在运行时/测试中被 process.chdir() 影响。
15
14
  // 为避免相对路径的 logs 目录随着 cwd 变化,使用模块加载时的初始 cwd 作为锚点。
16
- const INITIAL_CWD = process.cwd();
17
- const RUNTIME_LOG_DIR = nodePathResolve(INITIAL_CWD, "logs");
15
+ const DEFAULT_MAX_SIZE_MB = 20;
16
+ const MAX_FILE_BYTES = DEFAULT_MAX_SIZE_MB * 1024 * 1024;
18
17
 
19
18
  const BUILTIN_SENSITIVE_KEYS = ["*password*", "pass", "pwd", "*token*", "access_token", "refresh_token", "accessToken", "refreshToken", "authorization", "cookie", "set-cookie", "*secret*", "apiKey", "api_key", "privateKey", "private_key"];
20
19
 
@@ -32,168 +31,79 @@ let errorFileSink = null;
32
31
 
33
32
  let config = {
34
33
  debug: 0,
35
- dir: RUNTIME_LOG_DIR,
36
- maxSize: 20,
34
+ dir: "",
37
35
  runtimeEnv: "production"
38
36
  };
39
37
 
40
38
  function safeWriteStderr(msg) {
41
- try {
42
- process.stderr.write(`${msg}\n`);
43
- } catch {
44
- // ignore
45
- }
46
- }
47
-
48
- function shiftBatchFromPending(pending, maxBatchBytes) {
49
- const parts = [];
50
- let bytes = 0;
51
-
52
- while (pending.length > 0) {
53
- const next = pending[0];
54
- const nextBytes = Buffer.byteLength(next);
55
- if (parts.length > 0 && bytes + nextBytes > maxBatchBytes) {
56
- break;
57
- }
58
- parts.push(next);
59
- bytes = bytes + nextBytes;
60
- pending.shift();
61
- }
62
-
63
- return { chunk: parts.join(""), bytes: bytes };
39
+ process.stderr.write(`${msg}\n`);
64
40
  }
65
41
 
66
42
  class LogFileSink {
67
- constructor(options) {
68
- this.prefix = options.prefix;
69
- this.maxFileBytes = options.maxFileBytes;
43
+ constructor(prefix) {
44
+ this.prefix = prefix;
70
45
 
71
46
  this.stream = null;
72
47
  this.streamDate = "";
73
48
  this.streamIndex = 0;
74
49
  this.streamSizeBytes = 0;
75
50
  this.disabled = false;
76
-
77
- this.pending = [];
78
- this.pendingBytes = 0;
79
- this.scheduledTimer = null;
80
- this.flushing = false;
81
-
82
- this.maxBufferBytes = 10 * 1024 * 1024;
83
- this.flushDelayMs = 10;
84
- this.maxBatchBytes = 64 * 1024;
85
51
  }
86
52
 
87
- enqueue(line) {
53
+ write(line) {
88
54
  if (this.disabled) return;
89
55
 
90
- const bytes = Buffer.byteLength(line);
91
- if (this.pendingBytes + bytes > this.maxBufferBytes) {
92
- // buffer 满:统一丢弃新日志(不区分 level)
93
- return;
56
+ this.ensureStreamReady(line.length);
57
+ if (this.stream) {
58
+ this.stream.write(line);
59
+ this.streamSizeBytes += line.length;
94
60
  }
95
-
96
- this.pending.push(line);
97
- this.pendingBytes = this.pendingBytes + bytes;
98
-
99
- this.scheduleFlush();
100
61
  }
101
62
 
102
63
  async flushNow() {
103
- this.clearScheduledFlush();
104
- await this.flush();
64
+ return this.closeStream();
105
65
  }
106
66
 
107
67
  async shutdown() {
108
- this.clearScheduledFlush();
109
- await this.flush();
110
- await this.closeStream();
111
- }
112
-
113
- scheduleFlush() {
114
- if (this.scheduledTimer) return;
115
-
116
- this.scheduledTimer = setTimeout(() => {
117
- // timer 触发时先清空句柄,避免 flush 内再次 schedule 时被认为“已安排”。
118
- this.scheduledTimer = null;
119
- this.flush();
120
- }, this.flushDelayMs);
68
+ return this.closeStream();
121
69
  }
122
70
 
123
- clearScheduledFlush() {
124
- if (!this.scheduledTimer) return;
125
- clearTimeout(this.scheduledTimer);
126
- this.scheduledTimer = null;
71
+ getFilePath(date, index) {
72
+ const suffix = index > 0 ? `.${index}` : "";
73
+ const filename = this.prefix === "app" ? `${date}${suffix}.log` : `${this.prefix}.${date}${suffix}.log`;
74
+ return nodePathJoin(config.dir, filename);
127
75
  }
128
76
 
129
- async flush() {
130
- if (this.flushing || this.pending.length === 0) return;
131
- this.flushing = true;
77
+ ensureStreamReady(nextChunkBytes) {
78
+ const date = formatYmdHms(Date.now(), "date");
132
79
 
133
- try {
134
- while (this.pending.length > 0) {
135
- const batch = shiftBatchFromPending(this.pending, this.maxBatchBytes);
136
- this.pendingBytes = this.pendingBytes - batch.bytes;
137
-
138
- const ok = await this.writeChunk(batch.chunk, batch.bytes);
139
- if (!ok) {
140
- // writer 已禁用/失败:清空剩余 pending
141
- this.pending = [];
142
- this.pendingBytes = 0;
143
- break;
144
- }
145
- }
146
- } finally {
147
- this.flushing = false;
148
- if (this.pending.length > 0) {
149
- this.scheduleFlush();
150
- }
80
+ if (this.stream && this.streamDate && date !== this.streamDate) {
81
+ this.closeStreamSync();
151
82
  }
152
- }
153
83
 
154
- async writeChunk(chunk, chunkBytes) {
155
- if (this.disabled) return false;
84
+ if (!this.stream) {
85
+ this.streamDate = date;
86
+ this.streamIndex = 0;
87
+ this.streamSizeBytes = this.getFileSize(this.getFilePath(date, 0));
156
88
 
157
- try {
158
- await this.ensureStreamReady(chunkBytes);
159
- if (!this.stream) {
160
- // 文件 sink 已被禁用或打开失败
161
- return false;
162
- }
89
+ this.openStream(this.getFilePath(date, 0));
90
+ }
163
91
 
164
- const ok = this.stream.write(chunk);
165
- if (!ok) {
166
- await new Promise((resolve) => {
167
- const stream = this.stream;
168
- if (!stream) {
169
- resolve();
170
- return;
171
- }
172
- stream.once("drain", () => resolve());
173
- });
174
- }
175
- this.streamSizeBytes = this.streamSizeBytes + chunkBytes;
176
- return true;
177
- } catch (error) {
178
- safeWriteStderr(`[Logger] file sink flush error (${this.prefix}): ${error?.message || error}`);
179
- this.disabled = true;
180
- await this.closeStream();
181
- return false;
92
+ if (this.stream && this.streamSizeBytes + nextChunkBytes > MAX_FILE_BYTES) {
93
+ this.closeStreamSync();
94
+ this.streamIndex += 1;
95
+ this.streamSizeBytes = 0;
96
+
97
+ this.openStream(this.getFilePath(date, this.streamIndex));
182
98
  }
183
99
  }
184
100
 
185
- async closeStream() {
186
- if (!this.stream) return;
187
- const st = this.stream;
188
- this.stream = null;
189
-
190
- await new Promise((resolve) => {
191
- try {
192
- st.end(() => resolve());
193
- } catch {
194
- resolve();
195
- }
196
- });
101
+ getFileSize(filePath) {
102
+ try {
103
+ return statSync(filePath).size;
104
+ } catch {
105
+ return 0;
106
+ }
197
107
  }
198
108
 
199
109
  openStream(filePath) {
@@ -202,7 +112,7 @@ class LogFileSink {
202
112
  this.stream.on("error", (error) => {
203
113
  safeWriteStderr(`[Logger] file sink error (${this.prefix}): ${error?.message || error}`);
204
114
  this.disabled = true;
205
- this.closeStream();
115
+ this.closeStreamSync();
206
116
  });
207
117
  } catch (error) {
208
118
  safeWriteStderr(`[Logger] createWriteStream failed (${this.prefix}): ${error?.message || error}`);
@@ -211,91 +121,51 @@ class LogFileSink {
211
121
  }
212
122
  }
213
123
 
214
- getFilePath(date, index) {
215
- const suffix = index > 0 ? `.${index}` : "";
216
- const filename = this.prefix === "app" ? `${date}${suffix}.log` : `${this.prefix}.${date}${suffix}.log`;
217
- return nodePathJoin(RUNTIME_LOG_DIR, filename);
218
- }
219
-
220
- async ensureStreamReady(nextChunkBytes) {
221
- const date = formatYmdHms(Date.now(), "date");
222
-
223
- // 日期变化:切新文件
224
- if (this.stream && this.streamDate && date !== this.streamDate) {
225
- await this.closeStream();
226
- this.streamDate = "";
227
- this.streamIndex = 0;
228
- this.streamSizeBytes = 0;
124
+ closeStreamSync() {
125
+ if (this.stream) {
126
+ this.stream.end();
229
127
  }
128
+ this.stream = null;
129
+ this.streamDate = "";
130
+ this.streamIndex = 0;
131
+ this.streamSizeBytes = 0;
132
+ }
230
133
 
231
- // 首次打开
232
- if (!this.stream) {
233
- const filePath = this.getFilePath(date, 0);
234
- let size = 0;
235
- try {
236
- const st = await stat(filePath);
237
- size = isNumber(st.size) ? st.size : 0;
238
- } catch {
239
- size = 0;
134
+ closeStream() {
135
+ return new Promise((resolve) => {
136
+ const st = this.stream;
137
+ if (!st) {
138
+ resolve();
139
+ return;
240
140
  }
241
-
242
- this.streamDate = date;
141
+ this.stream = null;
142
+ this.streamDate = "";
243
143
  this.streamIndex = 0;
244
- this.streamSizeBytes = size;
245
-
246
- this.openStream(filePath);
247
- if (!this.stream) return;
248
- }
249
-
250
- // 大小滚动
251
- if (this.stream && this.maxFileBytes > 0 && this.streamSizeBytes + nextChunkBytes > this.maxFileBytes) {
252
- await this.closeStream();
253
- this.streamIndex = this.streamIndex + 1;
254
- const filePath = this.getFilePath(date, this.streamIndex);
255
- this.streamDate = date;
256
144
  this.streamSizeBytes = 0;
257
-
258
- this.openStream(filePath);
259
- if (!this.stream) return;
260
- }
145
+ st.end(() => resolve());
146
+ });
261
147
  }
262
148
  }
263
149
 
264
150
  export async function flush() {
265
- // 测试场景:mock logger 不需要 flush
266
151
  if (mockInstance) return;
267
152
 
268
153
  for (const sink of [appFileSink, errorFileSink]) {
269
- if (!sink) {
270
- continue;
271
- }
272
-
273
- try {
154
+ if (sink) {
274
155
  await sink.flushNow();
275
- } catch {
276
- // ignore
277
156
  }
278
157
  }
279
158
  }
280
159
 
281
160
  export async function shutdown() {
282
- // 测试场景:mock logger 不需要 shutdown
283
161
  if (mockInstance) return;
284
162
 
285
- // 重要:shutdown 可能与后续 Logger 调用并发。
286
- // 因此这里捕获“当前的旧 sink/instance 快照”,只关闭这些快照,避免把新创建的 sink 一并清掉。
287
163
  const currentAppFileSink = appFileSink;
288
164
  const currentErrorFileSink = errorFileSink;
289
165
 
290
166
  for (const sink of [currentAppFileSink, currentErrorFileSink]) {
291
- if (!sink) {
292
- continue;
293
- }
294
-
295
- try {
167
+ if (sink) {
296
168
  await sink.shutdown();
297
- } catch {
298
- // ignore
299
169
  }
300
170
  }
301
171
 
@@ -305,23 +175,43 @@ export async function shutdown() {
305
175
  if (errorFileSink === currentErrorFileSink) {
306
176
  errorFileSink = null;
307
177
  }
178
+ }
308
179
 
309
- // shutdown 后允许下一次重新初始化时再次校验/创建目录(测试会清理目录,避免 ENOENT)
310
- // 无需缓存状态:确保目录存在是幂等的。
180
+ function getRuntimeLogDir(runtimeEnv) {
181
+ const cwd = process.cwd();
182
+ if (runtimeEnv === "development") {
183
+ return nodePathResolve(cwd, "logsDev");
184
+ }
185
+ return nodePathResolve(cwd, "logs");
311
186
  }
312
187
 
313
- function ensureLogDirExists() {
188
+ function prepareLogDir(runtimeEnv, shouldClearDev = false) {
189
+ const logDir = getRuntimeLogDir(runtimeEnv);
190
+ const devLogDir = nodePathResolve(process.cwd(), "logsDev");
191
+
314
192
  try {
315
- if (!existsSync(RUNTIME_LOG_DIR)) {
316
- mkdirSync(RUNTIME_LOG_DIR, { recursive: true });
193
+ if (!existsSync(logDir)) {
194
+ mkdirSync(logDir, { recursive: true });
195
+ }
196
+ if (!existsSync(devLogDir)) {
197
+ mkdirSync(devLogDir, { recursive: true });
198
+ }
199
+ if (shouldClearDev && runtimeEnv === "development" && existsSync(devLogDir)) {
200
+ rmSync(devLogDir, { recursive: true, force: true });
201
+ mkdirSync(devLogDir, { recursive: true });
317
202
  }
318
203
  } catch (error) {
319
- // 不能在 Logger 初始化前调用 Logger 本身,直接抛错即可
320
- throw new Error(`创建 logs 目录失败: ${RUNTIME_LOG_DIR}. ${error?.message || error}`, {
204
+ throw new Error(`创建日志目录失败: ${logDir}. ${error?.message || error}`, {
321
205
  cause: error,
322
206
  code: "runtime"
323
207
  });
324
208
  }
209
+
210
+ return logDir;
211
+ }
212
+
213
+ function ensureLogDirExists() {
214
+ config.dir = prepareLogDir(config.runtimeEnv);
325
215
  }
326
216
 
327
217
  // 方案B:删除“启动时清理旧日志”功能(减少 I/O 与复杂度)。
@@ -330,40 +220,23 @@ function ensureLogDirExists() {
330
220
  * 配置日志
331
221
  */
332
222
  export function configure(cfg) {
333
- // 旧实例可能仍持有文件句柄;这里异步关闭(不阻塞主流程)
334
223
  shutdown();
335
224
 
336
- // 方案B:每次 configure 都从默认配置重新构建(避免继承上一次配置造成测试/运行时污染)
337
225
  config = Object.assign(
338
226
  {
339
227
  debug: 0,
340
- dir: RUNTIME_LOG_DIR,
341
- maxSize: 20,
228
+ dir: "",
342
229
  runtimeEnv: "production"
343
230
  },
344
231
  cfg
345
232
  );
346
233
 
347
- // 约束:日志目录始终固定在当前项目根目录 logs,不允许外部覆盖。
348
- config.dir = RUNTIME_LOG_DIR;
349
-
350
- if (cfg && !isString(cfg.runtimeEnv) && isString(cfg.runMode)) {
351
- config.runtimeEnv = cfg.runMode;
352
- }
353
-
354
- // maxSize:仅按 MB 计算,且强制范围 10..100
355
- {
356
- const raw = config.maxSize;
357
- let mb = isFiniteNumber(raw) ? raw : 20;
358
- if (mb < 10) mb = 10;
359
- if (mb > 100) mb = 100;
360
- config.maxSize = mb;
361
- }
234
+ config.dir = getRuntimeLogDir(config.runtimeEnv);
235
+ prepareLogDir(config.runtimeEnv, true);
362
236
 
363
237
  appFileSink = null;
364
238
  errorFileSink = null;
365
239
 
366
- // 运行时清洗上限:只保留深度/节点/对象键数量保护。
367
240
  sanitizeOptions = {
368
241
  sanitizeDepth: normalizePositiveInt(config.sanitizeDepth, 5, 1, 10),
369
242
  sanitizeNodes: normalizePositiveInt(config.sanitizeNodes, 5000, 50, 20000),
@@ -381,87 +254,56 @@ export function setMockLogger(mock) {
381
254
  }
382
255
 
383
256
  function buildJsonLine(level, timeMs, record) {
384
- const base = {
257
+ const out = {
385
258
  level: level,
386
259
  time: formatYmdHms(timeMs),
387
260
  pid: process.pid
388
261
  };
389
262
 
390
- // 目标:让 level/time/timeFormat/pid/hostname 在 JSON 行首出现(更易读),同时保持 JSONL 可解析。
391
- // 约束:record 不允许覆盖基础字段。
392
- // 实现:先写入基础字段,再按 record 的 key 顺序追加其它字段。
393
263
  if (record && isPlainObject(record)) {
394
- const out = {};
395
- out["level"] = base["level"];
396
- out["time"] = base["time"];
397
- out["pid"] = base["pid"];
398
-
399
264
  for (const key of Object.keys(record)) {
400
- if (key === "level") continue;
401
- if (key === "time") continue;
402
- if (key === "pid") continue;
265
+ if (key === "level" || key === "time" || key === "pid") {
266
+ continue;
267
+ }
403
268
  out[key] = record[key];
404
269
  }
405
270
 
406
- // msg 允许保留 record.msg(若存在),否则补齐空字符串。
407
- if (record.msg !== undefined) {
408
- out["msg"] = record.msg;
409
- } else if (out["msg"] === undefined) {
410
- out["msg"] = "";
411
- }
412
-
413
- try {
414
- return `${JSON.stringify(out)}\n`;
415
- } catch {
416
- try {
417
- return `${JSON.stringify({ level: out["level"], time: out["time"], pid: out["pid"], msg: "[Unserializable log record]" })}\n`;
418
- } catch {
419
- return '{"msg":"[Unserializable log record]"}\n';
420
- }
271
+ if (out.msg === undefined) {
272
+ out.msg = "";
421
273
  }
422
- }
423
-
424
- if (base["msg"] === undefined) {
425
- base["msg"] = "";
274
+ } else {
275
+ out.msg = "";
426
276
  }
427
277
 
428
278
  try {
429
- return `${JSON.stringify(base)}\n`;
279
+ return `${JSON.stringify(out)}\n`;
430
280
  } catch {
431
- try {
432
- return `${JSON.stringify({ level: base["level"], time: base["time"], pid: base["pid"], msg: "[Unserializable log record]" })}\n`;
433
- } catch {
434
- return '{"msg":"[Unserializable log record]"}\n';
435
- }
281
+ return `${JSON.stringify({ level: out.level, time: out.time, pid: out.pid, msg: "[Unserializable log record]" })}\n`;
436
282
  }
437
283
  }
438
284
 
439
285
  function ensureSinksReady(kind) {
440
286
  ensureLogDirExists();
441
287
 
442
- const maxSizeMb = isFiniteNumber(config.maxSize) ? config.maxSize : 20;
443
- const maxFileBytes = Math.floor(maxSizeMb * 1024 * 1024);
444
-
445
288
  if (kind === "app") {
446
289
  if (!appFileSink) {
447
- appFileSink = new LogFileSink({ prefix: "app", maxFileBytes: maxFileBytes });
290
+ appFileSink = new LogFileSink("app");
448
291
  }
449
- return { fileSink: appFileSink };
292
+ return appFileSink;
450
293
  }
451
294
 
452
295
  if (!errorFileSink) {
453
- errorFileSink = new LogFileSink({ prefix: "error", maxFileBytes: maxFileBytes });
296
+ errorFileSink = new LogFileSink("error");
454
297
  }
455
-
456
- return { fileSink: errorFileSink };
298
+ return errorFileSink;
457
299
  }
458
300
 
459
301
  function writeJsonl(kind, level, record) {
460
- const sinks = ensureSinksReady(kind);
302
+ const sink = ensureSinksReady(kind);
461
303
  const time = Date.now();
462
304
  const sanitizedRecord = sanitizeLogObject(record, sanitizeOptions);
463
305
  const fileLine = buildJsonLine(level, time, sanitizedRecord);
464
- sinks.fileSink.enqueue(fileLine);
306
+ sink.write(fileLine);
465
307
  }
466
308
 
467
309
  // 对象清洗/脱敏逻辑已下沉到 utils/loggerUtils.js(减少 logger.js 复杂度)。
@@ -507,15 +349,7 @@ function logWrite(level, input) {
507
349
  // 测试场景:mock logger 走同步写入,并在入口进行清洗/脱敏控制
508
350
  if (mockInstance) {
509
351
  const sanitized = sanitizeLogObject(record0, sanitizeOptions);
510
- if (level === "info") {
511
- mockInstance.info(sanitized);
512
- } else if (level === "warn") {
513
- mockInstance.warn(sanitized);
514
- } else if (level === "error") {
515
- mockInstance.error(sanitized);
516
- } else {
517
- mockInstance.debug(sanitized);
518
- }
352
+ mockInstance[level](sanitized);
519
353
  return;
520
354
  }
521
355
 
@@ -527,26 +361,23 @@ function logWrite(level, input) {
527
361
  }
528
362
  }
529
363
 
530
- /**
531
- * 日志实例(延迟初始化)
532
- */
533
- export const Logger = {
534
- info: function (msg, data) {
364
+ function buildLogMethod(level) {
365
+ return function (msg, data) {
535
366
  if (isPlainObject(msg)) {
536
- logWrite("info", msg);
367
+ logWrite(level, msg);
537
368
  return;
538
369
  }
539
370
 
540
- logWrite("info", { msg: msg, data: data });
541
- },
542
- warn: function (msg, data) {
543
- if (isPlainObject(msg)) {
544
- logWrite("warn", msg);
545
- return;
546
- }
371
+ logWrite(level, { msg: msg, data: data });
372
+ };
373
+ }
547
374
 
548
- logWrite("warn", { msg: msg, data: data });
549
- },
375
+ /**
376
+ * 日志实例(延迟初始化)
377
+ */
378
+ export const Logger = {
379
+ info: buildLogMethod("info"),
380
+ warn: buildLogMethod("warn"),
550
381
  error: function (msg, err, data) {
551
382
  if (isPlainObject(msg)) {
552
383
  logWrite("error", msg);
@@ -555,14 +386,7 @@ export const Logger = {
555
386
 
556
387
  logWrite("error", { msg: msg, err: err, data: data });
557
388
  },
558
- debug: function (msg, data) {
559
- if (isPlainObject(msg)) {
560
- logWrite("debug", msg);
561
- return;
562
- }
563
-
564
- logWrite("debug", { msg: msg, data: data });
565
- },
389
+ debug: buildLogMethod("debug"),
566
390
  flush: async function () {
567
391
  await flush();
568
392
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.61.0",
3
+ "version": "3.62.0",
4
4
  "gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
package/plugins/logger.js CHANGED
@@ -10,11 +10,8 @@ import { Logger } from "../lib/logger.js";
10
10
  */
11
11
  export default {
12
12
  order: 2,
13
- handler: async function (befly) {
14
- // 配置 Logger
15
- if (befly.config && befly.config.logger) {
16
- Logger.configure(befly.config.logger);
17
- }
13
+ handler: async function () {
14
+ // 日志已在 createBefly 中按 runMode 初始化,这里只注入 Logger 实例
18
15
  return Logger;
19
16
  }
20
17
  };
@@ -162,9 +162,6 @@ function sanitizeAny(value, options, state, depth, visited) {
162
162
 
163
163
  for (let i = 0; i < limit; i++) {
164
164
  const entry = entries[i];
165
- if (!entry) {
166
- continue;
167
- }
168
165
  const key = entry[0];
169
166
  const child = entry[1];
170
167