befly 3.16.1 → 3.16.2
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/dist/befly.js +103 -133
- package/dist/befly.min.js +19 -17
- package/dist/index.js +11 -16
- package/dist/lib/logger.d.ts +12 -6
- package/dist/lib/logger.js +102 -115
- package/dist/sync/syncTable.js +16 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -195,21 +195,6 @@ export class Befly {
|
|
|
195
195
|
noLog = true;
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
const writePlainLinesToConsole = (prefix, errName, message) => {
|
|
199
|
-
const text = String(message || "");
|
|
200
|
-
const parts = text.split("\n");
|
|
201
|
-
const firstLine = parts.length > 0 ? String(parts[0] || "") : "";
|
|
202
|
-
const head = `[${prefix}] 启动失败: ${errName}: ${firstLine}`;
|
|
203
|
-
try {
|
|
204
|
-
process.stderr.write(`${head}\n`);
|
|
205
|
-
for (let i = 1; i < parts.length; i = i + 1) {
|
|
206
|
-
process.stderr.write(`${parts[i]}\n`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
// ignore
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
198
|
// 注意:测试要求 start() 失败时必须 Logger.error 一次,所以这里始终调用 error。
|
|
214
199
|
// 但如果下层已经记录过详细堆栈,则入口只输出摘要,避免重复。
|
|
215
200
|
if (!alreadyLogged && multiline) {
|
|
@@ -221,7 +206,17 @@ export class Befly {
|
|
|
221
206
|
errorKind: kind,
|
|
222
207
|
errorMessage: errMessage
|
|
223
208
|
}, { truncate: false, console: false });
|
|
224
|
-
|
|
209
|
+
{
|
|
210
|
+
const text = String(errMessage || "");
|
|
211
|
+
const parts = text.split("\n");
|
|
212
|
+
const firstLine = parts.length > 0 ? String(parts[0] || "") : "";
|
|
213
|
+
const lines = [];
|
|
214
|
+
lines.push(`[${appName}] 启动失败: ${errName}: ${firstLine}`);
|
|
215
|
+
for (let i = 1; i < parts.length; i = i + 1) {
|
|
216
|
+
lines.push(String(parts[i] || ""));
|
|
217
|
+
}
|
|
218
|
+
Logger.printPlainLines(lines, { stream: "stderr" });
|
|
219
|
+
}
|
|
225
220
|
}
|
|
226
221
|
else if (alreadyLogged) {
|
|
227
222
|
Logger.error({
|
package/dist/lib/logger.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 日志系统 - Bun 环境自定义实现(替换 pino / pino-roll)
|
|
3
3
|
*/
|
|
4
|
-
import type { LoggerConfig, LoggerSink } from "../types/logger";
|
|
4
|
+
import type { LoggerConfig, LoggerSink, LoggerWriteOptions } from "../types/logger";
|
|
5
5
|
type SinkLogger = LoggerSink;
|
|
6
|
-
type LoggerWriteOptions = {
|
|
7
|
-
truncate?: boolean;
|
|
8
|
-
console?: boolean;
|
|
9
|
-
};
|
|
10
6
|
export declare function flush(): Promise<void>;
|
|
11
7
|
export declare function shutdown(): Promise<void>;
|
|
12
8
|
/**
|
|
@@ -23,11 +19,21 @@ export declare function setMockLogger(mock: SinkLogger | null): void;
|
|
|
23
19
|
*/
|
|
24
20
|
export declare function getLogger(): SinkLogger;
|
|
25
21
|
declare class LoggerFacade {
|
|
26
|
-
private
|
|
22
|
+
private write;
|
|
27
23
|
info(input: unknown, options?: LoggerWriteOptions): void;
|
|
28
24
|
warn(input: unknown, options?: LoggerWriteOptions): void;
|
|
29
25
|
error(input: unknown, options?: LoggerWriteOptions): void;
|
|
30
26
|
debug(input: unknown, options?: LoggerWriteOptions): void;
|
|
27
|
+
/**
|
|
28
|
+
* 打印多行纯文本到控制台(stdout/stderr)。
|
|
29
|
+
*
|
|
30
|
+
* 说明:
|
|
31
|
+
* - 该方法不写入 JSONL 文件、不做清洗/脱敏/截断;仅用于“人类可读”的多行输出。
|
|
32
|
+
* - 若需要落盘/采集:请先调用 Logger.info/warn/error/debug 写入 JSONL(可选 console:false),再调用本方法输出多行。
|
|
33
|
+
*/
|
|
34
|
+
printPlainLines(input: string | string[], options?: {
|
|
35
|
+
stream?: "stdout" | "stderr";
|
|
36
|
+
}): void;
|
|
31
37
|
flush(): Promise<void>;
|
|
32
38
|
configure(cfg: LoggerConfig): void;
|
|
33
39
|
setMock(mock: SinkLogger | null): void;
|
package/dist/lib/logger.js
CHANGED
|
@@ -21,11 +21,8 @@ let sanitizeOptions = {
|
|
|
21
21
|
sanitizeObjectKeys: 500,
|
|
22
22
|
sensitiveKeyMatcher: buildSensitiveKeyMatcher({ builtinPatterns: BUILTIN_SENSITIVE_KEYS, userPatterns: [] })
|
|
23
23
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!writeOptions)
|
|
27
|
-
return sanitizeOptions;
|
|
28
|
-
if (writeOptions.truncate !== false)
|
|
24
|
+
function buildSanitizeOptionsForWriteOptions(writeOptions) {
|
|
25
|
+
if (!writeOptions || writeOptions.truncate !== false)
|
|
29
26
|
return sanitizeOptions;
|
|
30
27
|
// 仅关闭“截断”,仍保留敏感字段掩码与结构化清洗(避免泄露敏感信息)。
|
|
31
28
|
return {
|
|
@@ -45,8 +42,6 @@ const HOSTNAME = (() => {
|
|
|
45
42
|
return "unknown";
|
|
46
43
|
}
|
|
47
44
|
})();
|
|
48
|
-
let instance = null;
|
|
49
|
-
let errorInstance = null;
|
|
50
45
|
let mockInstance = null;
|
|
51
46
|
let appFileSink = null;
|
|
52
47
|
let errorFileSink = null;
|
|
@@ -369,8 +364,6 @@ export async function shutdown() {
|
|
|
369
364
|
return;
|
|
370
365
|
// 重要:shutdown 可能与后续 Logger.getLogger() 并发。
|
|
371
366
|
// 因此这里捕获“当前的旧 sink/instance 快照”,只关闭这些快照,避免把新创建的 sink 一并清掉。
|
|
372
|
-
const currentInstance = instance;
|
|
373
|
-
const currentErrorInstance = errorInstance;
|
|
374
367
|
const currentAppFileSink = appFileSink;
|
|
375
368
|
const currentErrorFileSink = errorFileSink;
|
|
376
369
|
const currentAppConsoleSink = appConsoleSink;
|
|
@@ -398,12 +391,6 @@ export async function shutdown() {
|
|
|
398
391
|
if (appConsoleSink === currentAppConsoleSink) {
|
|
399
392
|
appConsoleSink = null;
|
|
400
393
|
}
|
|
401
|
-
if (instance === currentInstance) {
|
|
402
|
-
instance = null;
|
|
403
|
-
}
|
|
404
|
-
if (errorInstance === currentErrorInstance) {
|
|
405
|
-
errorInstance = null;
|
|
406
|
-
}
|
|
407
394
|
// shutdown 后允许下一次重新初始化时再次校验/创建目录(测试会清理目录,避免 ENOENT)
|
|
408
395
|
// 无需缓存状态:确保目录存在是幂等的。
|
|
409
396
|
}
|
|
@@ -450,8 +437,6 @@ export function configure(cfg) {
|
|
|
450
437
|
mb = 100;
|
|
451
438
|
config.maxSize = mb;
|
|
452
439
|
}
|
|
453
|
-
instance = null;
|
|
454
|
-
errorInstance = null;
|
|
455
440
|
appFileSink = null;
|
|
456
441
|
errorFileSink = null;
|
|
457
442
|
appConsoleSink = null;
|
|
@@ -476,32 +461,20 @@ function getSink(kind) {
|
|
|
476
461
|
// 优先返回 mock 实例(用于测试)
|
|
477
462
|
if (mockInstance)
|
|
478
463
|
return mockInstance;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (!appFileSink) {
|
|
492
|
-
appFileSink = new LogFileSink({ prefix: "app", maxFileBytes: maxFileBytes });
|
|
493
|
-
}
|
|
494
|
-
if (config.console === 1 && !appConsoleSink) {
|
|
495
|
-
appConsoleSink = createStreamSink("stdout");
|
|
464
|
+
return {
|
|
465
|
+
info(record) {
|
|
466
|
+
writeJsonl(kind, "info", record, undefined);
|
|
467
|
+
},
|
|
468
|
+
warn(record) {
|
|
469
|
+
writeJsonl(kind, "warn", record, undefined);
|
|
470
|
+
},
|
|
471
|
+
error(record) {
|
|
472
|
+
writeJsonl(kind, "error", record, undefined);
|
|
473
|
+
},
|
|
474
|
+
debug(record) {
|
|
475
|
+
writeJsonl(kind, "debug", record, undefined);
|
|
496
476
|
}
|
|
497
|
-
|
|
498
|
-
return instance;
|
|
499
|
-
}
|
|
500
|
-
if (!errorFileSink) {
|
|
501
|
-
errorFileSink = new LogFileSink({ prefix: "error", maxFileBytes: maxFileBytes });
|
|
502
|
-
}
|
|
503
|
-
errorInstance = createSinkLogger({ fileSink: errorFileSink, consoleSink: null });
|
|
504
|
-
return errorInstance;
|
|
477
|
+
};
|
|
505
478
|
}
|
|
506
479
|
/**
|
|
507
480
|
* 获取 Logger 实例(延迟初始化)
|
|
@@ -570,46 +543,39 @@ function safeJsonStringify(obj) {
|
|
|
570
543
|
}
|
|
571
544
|
}
|
|
572
545
|
}
|
|
573
|
-
function
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const base = buildBaseFields(level, time);
|
|
581
|
-
const input = isPlainObject(record) ? record : { value: record };
|
|
582
|
-
let writeOptions = null;
|
|
583
|
-
if (input && typeof input === "object") {
|
|
584
|
-
writeOptions = recordWriteOptions.get(input) ?? null;
|
|
585
|
-
if (writeOptions) {
|
|
586
|
-
recordWriteOptions.delete(input);
|
|
587
|
-
}
|
|
546
|
+
function ensureSinksReady(kind) {
|
|
547
|
+
ensureLogDirExists();
|
|
548
|
+
const maxSizeMb = typeof config.maxSize === "number" ? config.maxSize : 20;
|
|
549
|
+
const maxFileBytes = Math.floor(maxSizeMb * 1024 * 1024);
|
|
550
|
+
if (kind === "app") {
|
|
551
|
+
if (!appFileSink) {
|
|
552
|
+
appFileSink = new LogFileSink({ prefix: "app", maxFileBytes: maxFileBytes });
|
|
588
553
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const fileLine = buildLogLineWithBase(base, sanitizedRecord);
|
|
592
|
-
fileSink.enqueue(fileLine);
|
|
593
|
-
if (consoleSink) {
|
|
594
|
-
if (!writeOptions || writeOptions.console !== false) {
|
|
595
|
-
consoleSink.enqueue(fileLine);
|
|
596
|
-
}
|
|
554
|
+
if (config.console === 1 && !appConsoleSink) {
|
|
555
|
+
appConsoleSink = createStreamSink("stdout");
|
|
597
556
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
557
|
+
return { fileSink: appFileSink, consoleSink: config.console === 1 ? appConsoleSink : null };
|
|
558
|
+
}
|
|
559
|
+
if (!errorFileSink) {
|
|
560
|
+
errorFileSink = new LogFileSink({ prefix: "error", maxFileBytes: maxFileBytes });
|
|
561
|
+
}
|
|
562
|
+
return { fileSink: errorFileSink, consoleSink: null };
|
|
563
|
+
}
|
|
564
|
+
function writeJsonl(kind, level, record, options) {
|
|
565
|
+
if (level === "debug" && config.debug !== 1)
|
|
566
|
+
return;
|
|
567
|
+
const sinks = ensureSinksReady(kind);
|
|
568
|
+
const time = Date.now();
|
|
569
|
+
const base = buildBaseFields(level, time);
|
|
570
|
+
const effectiveSanitizeOptions = buildSanitizeOptionsForWriteOptions(options);
|
|
571
|
+
const sanitizedRecord = sanitizeLogObject(record, effectiveSanitizeOptions);
|
|
572
|
+
const fileLine = buildLogLineWithBase(base, sanitizedRecord);
|
|
573
|
+
sinks.fileSink.enqueue(fileLine);
|
|
574
|
+
if (sinks.consoleSink) {
|
|
575
|
+
if (!options || options.console !== false) {
|
|
576
|
+
sinks.consoleSink.enqueue(fileLine);
|
|
611
577
|
}
|
|
612
|
-
}
|
|
578
|
+
}
|
|
613
579
|
}
|
|
614
580
|
// 对象清洗/脱敏/截断逻辑已下沉到 utils/loggerUtils.ts(减少 logger.ts 复杂度)。
|
|
615
581
|
function metaToObject() {
|
|
@@ -679,51 +645,72 @@ function withRequestMetaRecord(record) {
|
|
|
679
645
|
return mergeMetaIntoObject(record, meta);
|
|
680
646
|
}
|
|
681
647
|
class LoggerFacade {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
return sanitizeLogObject(record, effective);
|
|
687
|
-
}
|
|
688
|
-
info(input, options) {
|
|
648
|
+
write(level, input, options) {
|
|
649
|
+
// debug!=1 则完全不记录 debug 日志(包括文件与控制台)
|
|
650
|
+
if (level === "debug" && config.debug !== 1)
|
|
651
|
+
return;
|
|
689
652
|
const record0 = withRequestMetaRecord(toRecord(input));
|
|
690
|
-
|
|
691
|
-
|
|
653
|
+
// 测试场景:mock logger 走同步写入,并在 facade 层进行清洗/脱敏/截断控制
|
|
654
|
+
if (mockInstance) {
|
|
655
|
+
const effective = buildSanitizeOptionsForWriteOptions(options);
|
|
656
|
+
const sanitized = sanitizeLogObject(record0, effective);
|
|
657
|
+
if (level === "info") {
|
|
658
|
+
mockInstance.info(sanitized);
|
|
659
|
+
}
|
|
660
|
+
else if (level === "warn") {
|
|
661
|
+
mockInstance.warn(sanitized);
|
|
662
|
+
}
|
|
663
|
+
else if (level === "error") {
|
|
664
|
+
mockInstance.error(sanitized);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
mockInstance.debug(sanitized);
|
|
668
|
+
}
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
writeJsonl("app", level, record0, options);
|
|
672
|
+
if (level === "error") {
|
|
673
|
+
// error 专属文件:始终镜像一份
|
|
674
|
+
writeJsonl("error", "error", record0, options);
|
|
692
675
|
}
|
|
693
|
-
|
|
694
|
-
|
|
676
|
+
}
|
|
677
|
+
info(input, options) {
|
|
678
|
+
this.write("info", input, options);
|
|
695
679
|
}
|
|
696
680
|
warn(input, options) {
|
|
697
|
-
|
|
698
|
-
if (!mockInstance && options && isPlainObject(record0)) {
|
|
699
|
-
recordWriteOptions.set(record0, options);
|
|
700
|
-
}
|
|
701
|
-
const record = this.maybeSanitizeForMock(record0, options);
|
|
702
|
-
getSink("app").warn(record);
|
|
681
|
+
this.write("warn", input, options);
|
|
703
682
|
}
|
|
704
683
|
error(input, options) {
|
|
705
|
-
|
|
706
|
-
if (!mockInstance && options && isPlainObject(record0)) {
|
|
707
|
-
recordWriteOptions.set(record0, options);
|
|
708
|
-
}
|
|
709
|
-
const record = this.maybeSanitizeForMock(record0, options);
|
|
710
|
-
getSink("app").error(record);
|
|
711
|
-
// 测试场景:启用 mock 时不做镜像,避免调用次数翻倍
|
|
712
|
-
if (mockInstance)
|
|
713
|
-
return;
|
|
714
|
-
// error 专属文件:始终镜像一份
|
|
715
|
-
getSink("error").error(record);
|
|
684
|
+
this.write("error", input, options);
|
|
716
685
|
}
|
|
717
686
|
debug(input, options) {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
687
|
+
this.write("debug", input, options);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 打印多行纯文本到控制台(stdout/stderr)。
|
|
691
|
+
*
|
|
692
|
+
* 说明:
|
|
693
|
+
* - 该方法不写入 JSONL 文件、不做清洗/脱敏/截断;仅用于“人类可读”的多行输出。
|
|
694
|
+
* - 若需要落盘/采集:请先调用 Logger.info/warn/error/debug 写入 JSONL(可选 console:false),再调用本方法输出多行。
|
|
695
|
+
*/
|
|
696
|
+
printPlainLines(input, options) {
|
|
697
|
+
const kind = options && options.stream === "stderr" ? "stderr" : "stdout";
|
|
698
|
+
const stream = kind === "stderr" ? process.stderr : process.stdout;
|
|
699
|
+
try {
|
|
700
|
+
if (Array.isArray(input)) {
|
|
701
|
+
for (const line of input) {
|
|
702
|
+
stream.write(`${String(line)}\n`);
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const parts = String(input).split("\n");
|
|
707
|
+
for (let i = 0; i < parts.length; i = i + 1) {
|
|
708
|
+
stream.write(`${parts[i]}\n`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
// ignore
|
|
724
713
|
}
|
|
725
|
-
const record = this.maybeSanitizeForMock(record0, options);
|
|
726
|
-
getSink("app").debug(record);
|
|
727
714
|
}
|
|
728
715
|
async flush() {
|
|
729
716
|
await flush();
|
package/dist/sync/syncTable.js
CHANGED
|
@@ -188,6 +188,7 @@ export class SyncTable {
|
|
|
188
188
|
// plan 阶段补充的兼容性检查:保证“遇到第一条 throw”不会中止汇总。
|
|
189
189
|
SyncTable.throwIfIncompatibleTypeChanges(incompatibleTypeChanges);
|
|
190
190
|
// 预检通过后,再执行实际同步(DDL)。
|
|
191
|
+
const createdTables = [];
|
|
191
192
|
for (const task of tableTasks) {
|
|
192
193
|
const item = task.item;
|
|
193
194
|
const tableName = task.tableName;
|
|
@@ -229,6 +230,7 @@ export class SyncTable {
|
|
|
229
230
|
}
|
|
230
231
|
else {
|
|
231
232
|
await SyncTable.createTable(this.db, tableName, tableFields);
|
|
233
|
+
createdTables.push(tableName);
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
236
|
catch (error) {
|
|
@@ -257,6 +259,20 @@ export class SyncTable {
|
|
|
257
259
|
throw error;
|
|
258
260
|
}
|
|
259
261
|
}
|
|
262
|
+
// 创建表汇总日志:单条输出(避免“一个表一行”刷屏)。
|
|
263
|
+
if (createdTables.length > 0) {
|
|
264
|
+
const lines = [];
|
|
265
|
+
lines.push(`创建表列表(共${createdTables.length}张):`);
|
|
266
|
+
for (const tableName of createdTables) {
|
|
267
|
+
lines.push(`- ${tableName}`);
|
|
268
|
+
}
|
|
269
|
+
const text = lines.join("\n");
|
|
270
|
+
// 说明:
|
|
271
|
+
// - 参考 start() 对 CoreError.multiline 的处理:文件仍写 JSONL,控制台改为原样多行文本。
|
|
272
|
+
// - 这样既不破坏 JSONL(文件/采集端仍可解析),也避免“每个表一条 JSON”刷屏。
|
|
273
|
+
Logger.debug(text, { truncate: false, console: false });
|
|
274
|
+
Logger.printPlainLines(text, { stream: "stdout" });
|
|
275
|
+
}
|
|
260
276
|
}
|
|
261
277
|
catch (error) {
|
|
262
278
|
// 若已在表级 catch 打印过更详细上下文,则这里避免重复打印。
|
|
@@ -1063,7 +1079,6 @@ export class SyncTable {
|
|
|
1063
1079
|
const { ENGINE, CHARSET, COLLATE } = SyncTable.MYSQL_TABLE_CONFIG;
|
|
1064
1080
|
const createSQL = `CREATE TABLE ${tableQuoted} (\n ${cols}\n ) ENGINE=${ENGINE} DEFAULT CHARSET=${CHARSET} COLLATE=${COLLATE}`;
|
|
1065
1081
|
await db.unsafe(createSQL);
|
|
1066
|
-
Logger.debug(`[表 ${tableName}] + 创建表(系统字段 + 业务字段)`);
|
|
1067
1082
|
const indexClauses = [];
|
|
1068
1083
|
for (const sysField of systemIndexFields) {
|
|
1069
1084
|
const indexName = `idx_${sysField}`;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.16.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.16.2",
|
|
4
|
+
"gitHead": "0e9cfa6f2979b12725e06e34936737738a219747",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|