@yunzai-ng/core 0.1.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/LICENSE +661 -0
- package/README.md +51 -0
- package/dist/adapter/accounts.d.ts +174 -0
- package/dist/adapter/accounts.d.ts.map +1 -0
- package/dist/adapter/accounts.js +653 -0
- package/dist/adapter/accounts.js.map +1 -0
- package/dist/adapter/bots.d.ts +142 -0
- package/dist/adapter/bots.d.ts.map +1 -0
- package/dist/adapter/bots.js +454 -0
- package/dist/adapter/bots.js.map +1 -0
- package/dist/adapter/host.d.ts +114 -0
- package/dist/adapter/host.d.ts.map +1 -0
- package/dist/adapter/host.js +90 -0
- package/dist/adapter/host.js.map +1 -0
- package/dist/adapter/login.d.ts +174 -0
- package/dist/adapter/login.d.ts.map +1 -0
- package/dist/adapter/login.js +345 -0
- package/dist/adapter/login.js.map +1 -0
- package/dist/adapter/registry.d.ts +102 -0
- package/dist/adapter/registry.d.ts.map +1 -0
- package/dist/adapter/registry.js +150 -0
- package/dist/adapter/registry.js.map +1 -0
- package/dist/config/core-config.d.ts +151 -0
- package/dist/config/core-config.d.ts.map +1 -0
- package/dist/config/core-config.js +338 -0
- package/dist/config/core-config.js.map +1 -0
- package/dist/config/schema.d.ts +449 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +871 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.d.ts +184 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +428 -0
- package/dist/config/store.js.map +1 -0
- package/dist/config/yaml.d.ts +23 -0
- package/dist/config/yaml.d.ts.map +1 -0
- package/dist/config/yaml.js +108 -0
- package/dist/config/yaml.js.map +1 -0
- package/dist/http/client.d.ts +82 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +658 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/app.d.ts +244 -0
- package/dist/kernel/app.d.ts.map +1 -0
- package/dist/kernel/app.js +654 -0
- package/dist/kernel/app.js.map +1 -0
- package/dist/kernel/kv-sink.d.ts +34 -0
- package/dist/kernel/kv-sink.d.ts.map +1 -0
- package/dist/kernel/kv-sink.js +25 -0
- package/dist/kernel/kv-sink.js.map +1 -0
- package/dist/kernel/policy.d.ts +77 -0
- package/dist/kernel/policy.d.ts.map +1 -0
- package/dist/kernel/policy.js +99 -0
- package/dist/kernel/policy.js.map +1 -0
- package/dist/kernel/runtime.d.ts +117 -0
- package/dist/kernel/runtime.d.ts.map +1 -0
- package/dist/kernel/runtime.js +197 -0
- package/dist/kernel/runtime.js.map +1 -0
- package/dist/kernel/sql-sink.d.ts +36 -0
- package/dist/kernel/sql-sink.d.ts.map +1 -0
- package/dist/kernel/sql-sink.js +114 -0
- package/dist/kernel/sql-sink.js.map +1 -0
- package/dist/logger/format.d.ts +67 -0
- package/dist/logger/format.d.ts.map +1 -0
- package/dist/logger/format.js +218 -0
- package/dist/logger/format.js.map +1 -0
- package/dist/logger/index.d.ts +97 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +363 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/rotate.d.ts +47 -0
- package/dist/logger/rotate.d.ts.map +1 -0
- package/dist/logger/rotate.js +242 -0
- package/dist/logger/rotate.js.map +1 -0
- package/dist/message/segment.d.ts +255 -0
- package/dist/message/segment.d.ts.map +1 -0
- package/dist/message/segment.js +510 -0
- package/dist/message/segment.js.map +1 -0
- package/dist/message/split.d.ts +34 -0
- package/dist/message/split.d.ts.map +1 -0
- package/dist/message/split.js +79 -0
- package/dist/message/split.js.map +1 -0
- package/dist/message/target.d.ts +29 -0
- package/dist/message/target.d.ts.map +1 -0
- package/dist/message/target.js +34 -0
- package/dist/message/target.js.map +1 -0
- package/dist/pipeline/cooldown.d.ts +84 -0
- package/dist/pipeline/cooldown.d.ts.map +1 -0
- package/dist/pipeline/cooldown.js +94 -0
- package/dist/pipeline/cooldown.js.map +1 -0
- package/dist/pipeline/dispatch.d.ts +98 -0
- package/dist/pipeline/dispatch.d.ts.map +1 -0
- package/dist/pipeline/dispatch.js +323 -0
- package/dist/pipeline/dispatch.js.map +1 -0
- package/dist/pipeline/event.d.ts +130 -0
- package/dist/pipeline/event.d.ts.map +1 -0
- package/dist/pipeline/event.js +538 -0
- package/dist/pipeline/event.js.map +1 -0
- package/dist/pipeline/middleware.d.ts +63 -0
- package/dist/pipeline/middleware.d.ts.map +1 -0
- package/dist/pipeline/middleware.js +174 -0
- package/dist/pipeline/middleware.js.map +1 -0
- package/dist/pipeline/prompt.d.ts +80 -0
- package/dist/pipeline/prompt.d.ts.map +1 -0
- package/dist/pipeline/prompt.js +155 -0
- package/dist/pipeline/prompt.js.map +1 -0
- package/dist/pipeline/router.d.ts +98 -0
- package/dist/pipeline/router.d.ts.map +1 -0
- package/dist/pipeline/router.js +489 -0
- package/dist/pipeline/router.js.map +1 -0
- package/dist/platform/detect.d.ts +24 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +178 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/paths.d.ts +38 -0
- package/dist/platform/paths.d.ts.map +1 -0
- package/dist/platform/paths.js +129 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/system.d.ts +71 -0
- package/dist/platform/system.d.ts.map +1 -0
- package/dist/platform/system.js +242 -0
- package/dist/platform/system.js.map +1 -0
- package/dist/plugin/context.d.ts +92 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +573 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +92 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +82 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/discover.d.ts +106 -0
- package/dist/plugin/discover.d.ts.map +1 -0
- package/dist/plugin/discover.js +254 -0
- package/dist/plugin/discover.js.map +1 -0
- package/dist/plugin/events.d.ts +109 -0
- package/dist/plugin/events.d.ts.map +1 -0
- package/dist/plugin/events.js +209 -0
- package/dist/plugin/events.js.map +1 -0
- package/dist/plugin/hooks.d.ts +266 -0
- package/dist/plugin/hooks.d.ts.map +1 -0
- package/dist/plugin/hooks.js +84 -0
- package/dist/plugin/hooks.js.map +1 -0
- package/dist/plugin/host.d.ts +160 -0
- package/dist/plugin/host.d.ts.map +1 -0
- package/dist/plugin/host.js +546 -0
- package/dist/plugin/host.js.map +1 -0
- package/dist/plugin/market.d.ts +264 -0
- package/dist/plugin/market.d.ts.map +1 -0
- package/dist/plugin/market.js +700 -0
- package/dist/plugin/market.js.map +1 -0
- package/dist/plugin/services.d.ts +116 -0
- package/dist/plugin/services.d.ts.map +1 -0
- package/dist/plugin/services.js +213 -0
- package/dist/plugin/services.js.map +1 -0
- package/dist/plugin/tar.d.ts +65 -0
- package/dist/plugin/tar.d.ts.map +1 -0
- package/dist/plugin/tar.js +277 -0
- package/dist/plugin/tar.js.map +1 -0
- package/dist/render/registry.d.ts +127 -0
- package/dist/render/registry.d.ts.map +1 -0
- package/dist/render/registry.js +306 -0
- package/dist/render/registry.js.map +1 -0
- package/dist/scheduler/index.d.ts +67 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +279 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/server/api.d.ts +179 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +602 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/auth.d.ts +83 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +178 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/browse.d.ts +60 -0
- package/dist/server/browse.d.ts.map +1 -0
- package/dist/server/browse.js +187 -0
- package/dist/server/browse.js.map +1 -0
- package/dist/server/files.d.ts +27 -0
- package/dist/server/files.d.ts.map +1 -0
- package/dist/server/files.js +76 -0
- package/dist/server/files.js.map +1 -0
- package/dist/server/index.d.ts +189 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1123 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/table.d.ts +105 -0
- package/dist/server/table.d.ts.map +1 -0
- package/dist/server/table.js +289 -0
- package/dist/server/table.js.map +1 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +120 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.d.ts +73 -0
- package/dist/store/json.d.ts.map +1 -0
- package/dist/store/json.js +174 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/kv.d.ts +126 -0
- package/dist/store/kv.d.ts.map +1 -0
- package/dist/store/kv.js +232 -0
- package/dist/store/kv.js.map +1 -0
- package/dist/store/level.d.ts +103 -0
- package/dist/store/level.d.ts.map +1 -0
- package/dist/store/level.js +119 -0
- package/dist/store/level.js.map +1 -0
- package/dist/store/memory.d.ts +61 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +94 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/sql.d.ts +142 -0
- package/dist/store/sql.d.ts.map +1 -0
- package/dist/store/sql.js +246 -0
- package/dist/store/sql.js.map +1 -0
- package/dist/testing/fake.d.ts +109 -0
- package/dist/testing/fake.d.ts.map +1 -0
- package/dist/testing/fake.js +196 -0
- package/dist/testing/fake.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-adapter.d.ts +205 -0
- package/dist/testing/mock-adapter.d.ts.map +1 -0
- package/dist/testing/mock-adapter.js +309 -0
- package/dist/testing/mock-adapter.js.map +1 -0
- package/dist/util/deep.d.ts +108 -0
- package/dist/util/deep.d.ts.map +1 -0
- package/dist/util/deep.js +275 -0
- package/dist/util/deep.js.map +1 -0
- package/dist/util/defer.d.ts +107 -0
- package/dist/util/defer.d.ts.map +1 -0
- package/dist/util/defer.js +163 -0
- package/dist/util/defer.js.map +1 -0
- package/dist/util/dispose.d.ts +88 -0
- package/dist/util/dispose.d.ts.map +1 -0
- package/dist/util/dispose.js +143 -0
- package/dist/util/dispose.js.map +1 -0
- package/dist/util/duration.d.ts +28 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +77 -0
- package/dist/util/duration.js.map +1 -0
- package/dist/util/fs.d.ts +110 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +269 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/id.d.ts +49 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +87 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/lru.d.ts +74 -0
- package/dist/util/lru.d.ts.map +1 -0
- package/dist/util/lru.js +156 -0
- package/dist/util/lru.js.map +1 -0
- package/dist/util/queue.d.ts +64 -0
- package/dist/util/queue.d.ts.map +1 -0
- package/dist/util/queue.js +147 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/text.d.ts +86 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +182 -0
- package/dist/util/text.js.map +1 -0
- package/package.json +44 -0
- package/src/adapter/accounts.ts +758 -0
- package/src/adapter/bots.ts +582 -0
- package/src/adapter/host.ts +224 -0
- package/src/adapter/login.ts +481 -0
- package/src/adapter/registry.ts +202 -0
- package/src/config/core-config.test.ts +60 -0
- package/src/config/core-config.ts +401 -0
- package/src/config/schema.test.ts +179 -0
- package/src/config/schema.ts +1036 -0
- package/src/config/store.test.ts +245 -0
- package/src/config/store.ts +506 -0
- package/src/config/yaml.ts +119 -0
- package/src/http/client.test.ts +568 -0
- package/src/http/client.ts +777 -0
- package/src/index.ts +106 -0
- package/src/kernel/app.test.ts +267 -0
- package/src/kernel/app.ts +843 -0
- package/src/kernel/kv-sink.ts +58 -0
- package/src/kernel/policy.ts +120 -0
- package/src/kernel/runtime.test.ts +542 -0
- package/src/kernel/runtime.ts +307 -0
- package/src/kernel/sql-sink.ts +154 -0
- package/src/logger/format.ts +259 -0
- package/src/logger/index.ts +427 -0
- package/src/logger/rotate.ts +265 -0
- package/src/message/segment.test.ts +335 -0
- package/src/message/segment.ts +569 -0
- package/src/message/split.ts +97 -0
- package/src/message/target.ts +48 -0
- package/src/pipeline/cooldown.ts +136 -0
- package/src/pipeline/dispatch.ts +419 -0
- package/src/pipeline/event.ts +735 -0
- package/src/pipeline/middleware.test.ts +426 -0
- package/src/pipeline/middleware.ts +199 -0
- package/src/pipeline/prompt.ts +212 -0
- package/src/pipeline/router.test.ts +432 -0
- package/src/pipeline/router.ts +575 -0
- package/src/platform/detect.ts +178 -0
- package/src/platform/paths.ts +147 -0
- package/src/platform/system.test.ts +102 -0
- package/src/platform/system.ts +289 -0
- package/src/plugin/context.test.ts +459 -0
- package/src/plugin/context.ts +757 -0
- package/src/plugin/define.test.ts +120 -0
- package/src/plugin/define.ts +142 -0
- package/src/plugin/discover.test.ts +253 -0
- package/src/plugin/discover.ts +353 -0
- package/src/plugin/events.test.ts +162 -0
- package/src/plugin/events.ts +266 -0
- package/src/plugin/hooks.ts +373 -0
- package/src/plugin/host.test.ts +696 -0
- package/src/plugin/host.ts +738 -0
- package/src/plugin/market.test.ts +781 -0
- package/src/plugin/market.ts +870 -0
- package/src/plugin/services.test.ts +135 -0
- package/src/plugin/services.ts +261 -0
- package/src/plugin/tar.test.ts +158 -0
- package/src/plugin/tar.ts +309 -0
- package/src/render/registry.test.ts +127 -0
- package/src/render/registry.ts +439 -0
- package/src/scheduler/index.ts +357 -0
- package/src/server/api.test.ts +802 -0
- package/src/server/api.ts +869 -0
- package/src/server/auth.ts +201 -0
- package/src/server/browse.test.ts +193 -0
- package/src/server/browse.ts +240 -0
- package/src/server/files.ts +97 -0
- package/src/server/index.test.ts +566 -0
- package/src/server/index.ts +1386 -0
- package/src/server/table.ts +351 -0
- package/src/store/index.ts +156 -0
- package/src/store/json.ts +198 -0
- package/src/store/kv.test.ts +307 -0
- package/src/store/kv.ts +256 -0
- package/src/store/level.ts +167 -0
- package/src/store/memory.ts +109 -0
- package/src/store/sql.test.ts +204 -0
- package/src/store/sql.ts +324 -0
- package/src/testing/fake.ts +297 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/mock-adapter.ts +588 -0
- package/src/util/deep.ts +272 -0
- package/src/util/defer.ts +211 -0
- package/src/util/dispose.ts +169 -0
- package/src/util/duration.ts +82 -0
- package/src/util/fs.ts +279 -0
- package/src/util/id.ts +94 -0
- package/src/util/lru.ts +185 -0
- package/src/util/queue.ts +167 -0
- package/src/util/text.ts +189 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:日志行的解析与彩色格式化
|
|
3
|
+
* 依赖方向:依赖 util/text
|
|
4
|
+
* 生命周期:纯函数
|
|
5
|
+
* 注意事项:自行实现而不使用 `pino-pretty`,理由与 rotate.ts 相同:pino-pretty 作为
|
|
6
|
+
* transport 运行于 worker 线程,单文件打包后无法定位入口;作为同步
|
|
7
|
+
* prettifier 又会引入一系列依赖。此处以 150 行完成,且可按需对齐中日韩
|
|
8
|
+
* 全角字符(`visualWidth`)。
|
|
9
|
+
*
|
|
10
|
+
* 颜色遵循约定:`NO_COLOR` 有值即禁用,`FORCE_COLOR` 有值即启用,
|
|
11
|
+
* 否则依据 stdout 是否为 TTY 判定。
|
|
12
|
+
*/
|
|
13
|
+
import { inspect } from "node:util"
|
|
14
|
+
import type { LogLevel } from "@yunzai-ng/types"
|
|
15
|
+
import { padVisualEnd, visualWidth } from "../util/text.js"
|
|
16
|
+
|
|
17
|
+
/** pino 的数字级别 → 级别名 */
|
|
18
|
+
const LEVEL_NAMES: Record<number, LogLevel> = {
|
|
19
|
+
10: "trace",
|
|
20
|
+
20: "debug",
|
|
21
|
+
30: "info",
|
|
22
|
+
40: "warn",
|
|
23
|
+
50: "error",
|
|
24
|
+
60: "fatal"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 级别名 → pino 数字级别 */
|
|
28
|
+
export const LEVEL_VALUES: Record<Exclude<LogLevel, "silent">, number> = {
|
|
29
|
+
trace: 10,
|
|
30
|
+
debug: 20,
|
|
31
|
+
info: 30,
|
|
32
|
+
warn: 40,
|
|
33
|
+
error: 50,
|
|
34
|
+
fatal: 60
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 控制台里显示的级别标签(定宽 5 列,便于竖向对齐) */
|
|
38
|
+
const LEVEL_LABELS: Record<LogLevel, string> = {
|
|
39
|
+
trace: "TRACE",
|
|
40
|
+
debug: "DEBUG",
|
|
41
|
+
info: "INFO ",
|
|
42
|
+
warn: "WARN ",
|
|
43
|
+
error: "ERROR",
|
|
44
|
+
fatal: "FATAL",
|
|
45
|
+
silent: " "
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** ANSI 前景色码 */
|
|
49
|
+
const LEVEL_COLORS: Record<LogLevel, string> = {
|
|
50
|
+
trace: "90", // 亮黑(灰)
|
|
51
|
+
debug: "36", // 青
|
|
52
|
+
info: "32", // 绿
|
|
53
|
+
warn: "33", // 黄
|
|
54
|
+
error: "31", // 红
|
|
55
|
+
fatal: "35", // 洋红
|
|
56
|
+
silent: "0"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** pino 注入的、不该当作业务字段打印出来的键 */
|
|
60
|
+
const RESERVED_KEYS = new Set(["level", "time", "msg", "pid", "hostname", "v", "err", "scope"])
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 判断当前是否应输出颜色
|
|
64
|
+
* @returns 是否启用颜色
|
|
65
|
+
*/
|
|
66
|
+
export function supportsColor(): boolean {
|
|
67
|
+
if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== "") return false
|
|
68
|
+
if (process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== "") return true
|
|
69
|
+
return process.stdout.isTTY === true
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 给文本加 ANSI 颜色
|
|
74
|
+
* @param code 颜色码,如 `"32"`
|
|
75
|
+
* @param text 文本
|
|
76
|
+
* @param enabled 是否启用;false 时原样返回
|
|
77
|
+
* @returns 带颜色的文本
|
|
78
|
+
*/
|
|
79
|
+
export function paint(code: string, text: string, enabled: boolean): string {
|
|
80
|
+
return enabled ? `\u001b[${code}m${text}\u001b[0m` : text
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 解析后的日志记录 */
|
|
84
|
+
export interface LogRecord {
|
|
85
|
+
/** 级别名 */
|
|
86
|
+
level: LogLevel
|
|
87
|
+
/** 毫秒时间戳 */
|
|
88
|
+
time: number
|
|
89
|
+
/** 主消息 */
|
|
90
|
+
msg: string
|
|
91
|
+
/** 作用域(插件名 / 适配器名 / 账号 id) */
|
|
92
|
+
scope?: string
|
|
93
|
+
/** 错误堆栈 */
|
|
94
|
+
stack?: string
|
|
95
|
+
/** 其余业务字段 */
|
|
96
|
+
fields?: Record<string, unknown>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 解析 pino 输出的一行 JSON
|
|
101
|
+
* @param line 日志行(含尾部换行)
|
|
102
|
+
* @returns 解析结果;不是合法 JSON 时 undefined
|
|
103
|
+
*/
|
|
104
|
+
export function parseRecord(line: string): LogRecord | undefined {
|
|
105
|
+
const text = line.trim()
|
|
106
|
+
if (text === "" || text[0] !== "{") return undefined
|
|
107
|
+
|
|
108
|
+
let raw: Record<string, unknown>
|
|
109
|
+
try {
|
|
110
|
+
raw = JSON.parse(text) as Record<string, unknown>
|
|
111
|
+
} catch {
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const levelNum = typeof raw.level === "number" ? raw.level : 30
|
|
116
|
+
const err = raw.err as { stack?: unknown; message?: unknown } | undefined
|
|
117
|
+
|
|
118
|
+
const fields: Record<string, unknown> = {}
|
|
119
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
120
|
+
if (!RESERVED_KEYS.has(key)) fields[key] = value
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
level: LEVEL_NAMES[levelNum] ?? "info",
|
|
125
|
+
time: typeof raw.time === "number" ? raw.time : Date.now(),
|
|
126
|
+
msg: typeof raw.msg === "string" ? raw.msg : typeof err?.message === "string" ? err.message : "",
|
|
127
|
+
scope: typeof raw.scope === "string" ? raw.scope : undefined,
|
|
128
|
+
stack: typeof err?.stack === "string" ? err.stack : undefined,
|
|
129
|
+
fields: Object.keys(fields).length > 0 ? fields : undefined
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 格式化时间为 `HH:mm:ss.SSS`
|
|
135
|
+
* @param time 毫秒时间戳
|
|
136
|
+
* @returns 时间字符串
|
|
137
|
+
*/
|
|
138
|
+
function formatTime(time: number): string {
|
|
139
|
+
const d = new Date(time)
|
|
140
|
+
const hh = String(d.getHours()).padStart(2, "0")
|
|
141
|
+
const mm = String(d.getMinutes()).padStart(2, "0")
|
|
142
|
+
const ss = String(d.getSeconds()).padStart(2, "0")
|
|
143
|
+
const ms = String(d.getMilliseconds()).padStart(3, "0")
|
|
144
|
+
return `${hh}:${mm}:${ss}.${ms}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 控制台格式化选项 */
|
|
148
|
+
export interface FormatOptions {
|
|
149
|
+
/** 是否着色 */
|
|
150
|
+
color: boolean
|
|
151
|
+
/** 作用域列的对齐宽度,缺省 14;设 0 不对齐 */
|
|
152
|
+
scopeWidth?: number
|
|
153
|
+
/** 是否打印错误堆栈,缺省 true */
|
|
154
|
+
stack?: boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 把日志记录格式化为一行(或多行,含堆栈)控制台文本
|
|
159
|
+
* @param rec 日志记录
|
|
160
|
+
* @param opts 格式化选项
|
|
161
|
+
* @returns 带尾部换行的文本
|
|
162
|
+
*/
|
|
163
|
+
export function formatRecord(rec: LogRecord, opts: FormatOptions): string {
|
|
164
|
+
const { color } = opts
|
|
165
|
+
const levelColor = LEVEL_COLORS[rec.level]
|
|
166
|
+
|
|
167
|
+
const time = paint("90", formatTime(rec.time), color)
|
|
168
|
+
const level = paint(levelColor, LEVEL_LABELS[rec.level], color)
|
|
169
|
+
|
|
170
|
+
let scope = ""
|
|
171
|
+
if (rec.scope) {
|
|
172
|
+
const width = opts.scopeWidth ?? 14
|
|
173
|
+
// 作用域超宽时不截断(宁可错行也不让用户看不出是哪个插件),
|
|
174
|
+
// 只在未超宽时补齐
|
|
175
|
+
const label = width > 0 && visualWidth(rec.scope) < width ? padVisualEnd(rec.scope, width) : rec.scope
|
|
176
|
+
scope = ` ${paint("36", label, color)}`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let extra = ""
|
|
180
|
+
if (rec.fields) {
|
|
181
|
+
const parts: string[] = []
|
|
182
|
+
for (const [key, value] of Object.entries(rec.fields)) {
|
|
183
|
+
parts.push(`${paint("90", `${key}=`, color)}${stringifyField(value)}`)
|
|
184
|
+
}
|
|
185
|
+
if (parts.length > 0) extra = ` ${parts.join(" ")}`
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let out = `${time} ${level}${scope} ${rec.msg}${extra}\n`
|
|
189
|
+
|
|
190
|
+
if (rec.stack && opts.stack !== false) {
|
|
191
|
+
out += `${paint("90", indent(rec.stack), color)}\n`
|
|
192
|
+
}
|
|
193
|
+
return out
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 缩进多行文本
|
|
198
|
+
* @param text 文本
|
|
199
|
+
* @returns 每行前加四空格
|
|
200
|
+
*/
|
|
201
|
+
function indent(text: string): string {
|
|
202
|
+
return text
|
|
203
|
+
.split("\n")
|
|
204
|
+
.map(line => ` ${line}`)
|
|
205
|
+
.join("\n")
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 将字段值转换为紧凑的单行文本
|
|
210
|
+
* @param value 字段值
|
|
211
|
+
* @returns 单行字符串
|
|
212
|
+
*/
|
|
213
|
+
function stringifyField(value: unknown): string {
|
|
214
|
+
if (typeof value === "string") return value
|
|
215
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return String(value)
|
|
216
|
+
return inspect(value, { depth: 2, breakLength: Number.POSITIVE_INFINITY, compact: true, colors: false })
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 将任意日志参数拼接为消息文本
|
|
221
|
+
*
|
|
222
|
+
* 不直接交由 pino 处理:pino 仅将第一个参数视为消息,其余参数仅在第一个参数含
|
|
223
|
+
* `%s`/`%d` 占位符时才被消费,否则**静默丢弃**。而 `logger.info("查询", uid, data)`
|
|
224
|
+
* 这类 console.log 写法很常见,故此处统一按 console.log 语义拼接。
|
|
225
|
+
* @param args 日志参数
|
|
226
|
+
* @returns 拼接后的消息与提取出的错误
|
|
227
|
+
*/
|
|
228
|
+
export function joinArgs(args: readonly unknown[]): { msg: string; err?: Error } {
|
|
229
|
+
const parts: string[] = []
|
|
230
|
+
let err: Error | undefined
|
|
231
|
+
|
|
232
|
+
for (const arg of args) {
|
|
233
|
+
if (arg instanceof Error) {
|
|
234
|
+
// 第一个 Error 单独提出来,让 pino 的 err 序列化器保留堆栈
|
|
235
|
+
if (!err) err = arg
|
|
236
|
+
parts.push(arg.message)
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
if (typeof arg === "string") {
|
|
240
|
+
parts.push(arg)
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
if (arg === undefined) {
|
|
244
|
+
parts.push("undefined")
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
if (arg === null) {
|
|
248
|
+
parts.push("null")
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
if (typeof arg === "object") {
|
|
252
|
+
parts.push(inspect(arg, { depth: 3, breakLength: 120, compact: 3, colors: false }))
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
parts.push(String(arg))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { msg: parts.join(" "), err }
|
|
259
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:日志枢纽(pino 实例、控制台/文件双路输出、运行时改级别、尾部缓冲)
|
|
3
|
+
* 依赖方向:依赖 logger/format、logger/rotate、util
|
|
4
|
+
* 生命周期:应用级单例,`close()` 时刷盘
|
|
5
|
+
* 注意事项:三个不显然的设计决定 ——
|
|
6
|
+
*
|
|
7
|
+
* 1) **pino 实例常驻 trace,级别过滤放在包装层做**。pino 的 child
|
|
8
|
+
* logger 在创建时固化 level,改父级不会传播给已有子级;而 WebUI
|
|
9
|
+
* 上"把日志级别调成 debug"必须立刻对所有插件生效。所以过滤前移到
|
|
10
|
+
* `#emit`,顺带连 `joinArgs` 的字符串拼接都省掉了,比 pino 自带
|
|
11
|
+
* 的级别检查更省。
|
|
12
|
+
*
|
|
13
|
+
* 2) **参数按 console.log 语义拼接**(见 format.ts 的 `joinArgs`)。
|
|
14
|
+
* pino 原生仅识别第一个参数,`logger.info("查询", uid)` 将丢弃 uid。
|
|
15
|
+
*
|
|
16
|
+
* 3) **不采用 pino.multistream**。控制台与文件的级别过滤在自有的
|
|
17
|
+
* dispatcher 中完成,同时将日志写入 WebUI 的环形缓冲,一次解析三处使用。
|
|
18
|
+
*/
|
|
19
|
+
import pino from "pino"
|
|
20
|
+
import type { Disposer, LogBindings, Logger, LogLevel } from "@yunzai-ng/types"
|
|
21
|
+
import { LEVEL_VALUES, type LogRecord, formatRecord, joinArgs, parseRecord, supportsColor } from "./format.js"
|
|
22
|
+
import { RotatingFileWriter } from "./rotate.js"
|
|
23
|
+
|
|
24
|
+
/** 日志配置 */
|
|
25
|
+
export interface LoggerConfig {
|
|
26
|
+
/** 全局级别,缺省 `"info"` */
|
|
27
|
+
level?: LogLevel
|
|
28
|
+
/** 文件输出级别,缺省与 `level` 相同 */
|
|
29
|
+
fileLevel?: LogLevel
|
|
30
|
+
/** 控制台输出级别,缺省与 `level` 相同 */
|
|
31
|
+
consoleLevel?: LogLevel
|
|
32
|
+
/** 日志目录;不给则只输出到控制台 */
|
|
33
|
+
dir?: string
|
|
34
|
+
/** 日志文件名前缀,缺省 `"app"` */
|
|
35
|
+
basename?: string
|
|
36
|
+
/** 是否输出到控制台,缺省 true */
|
|
37
|
+
console?: boolean
|
|
38
|
+
/** 是否着色,缺省自动探测 */
|
|
39
|
+
color?: boolean
|
|
40
|
+
/** 单文件字节上限 */
|
|
41
|
+
maxSize?: number
|
|
42
|
+
/** 日志保留天数 */
|
|
43
|
+
keepDays?: number
|
|
44
|
+
/** 日志文件数上限 */
|
|
45
|
+
maxFiles?: number
|
|
46
|
+
/** 尾部缓冲条数(供 WebUI 回看),缺省 500 */
|
|
47
|
+
tailSize?: number
|
|
48
|
+
/** 控制台作用域列宽,缺省 14 */
|
|
49
|
+
scopeWidth?: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 尾部日志查询条件 */
|
|
53
|
+
export interface TailQuery {
|
|
54
|
+
/** 最多返回条数 */
|
|
55
|
+
limit?: number
|
|
56
|
+
/** 最低级别 */
|
|
57
|
+
level?: LogLevel
|
|
58
|
+
/** 作用域精确匹配 */
|
|
59
|
+
scope?: string
|
|
60
|
+
/** 消息子串匹配(不区分大小写) */
|
|
61
|
+
keyword?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 默认尾部缓冲条数 */
|
|
65
|
+
const DEFAULT_TAIL = 500
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 级别名转数字,`silent` 视为最高
|
|
69
|
+
* @param level 级别名
|
|
70
|
+
* @returns 数字级别
|
|
71
|
+
*/
|
|
72
|
+
function levelValue(level: LogLevel): number {
|
|
73
|
+
return level === "silent" ? Number.POSITIVE_INFINITY : LEVEL_VALUES[level]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 日志枢纽
|
|
78
|
+
*
|
|
79
|
+
* 内核只创建一个;插件通过 `ctx.logger`(即 `hub.root.child({ scope })`)取用。
|
|
80
|
+
*/
|
|
81
|
+
export class LoggerHub {
|
|
82
|
+
/** pino 实例(级别恒为 trace,过滤在包装层做) */
|
|
83
|
+
readonly #pino: pino.Logger
|
|
84
|
+
/** 文件写入器 */
|
|
85
|
+
readonly #writer: RotatingFileWriter | undefined
|
|
86
|
+
/** 环形缓冲 */
|
|
87
|
+
readonly #ring: (LogRecord | undefined)[]
|
|
88
|
+
/** 环形缓冲写指针 */
|
|
89
|
+
#ringPos = 0
|
|
90
|
+
/** 环形缓冲是否已绕回(用于判断有效长度) */
|
|
91
|
+
#ringFull = false
|
|
92
|
+
/** 实时订阅者 */
|
|
93
|
+
readonly #subscribers = new Set<(rec: LogRecord) => void>()
|
|
94
|
+
/** 防止订阅者内部再打日志导致无限递归 */
|
|
95
|
+
#notifying = false
|
|
96
|
+
|
|
97
|
+
/** 全局级别 */
|
|
98
|
+
#level: LogLevel
|
|
99
|
+
/** 控制台级别数字 */
|
|
100
|
+
#consoleValue: number
|
|
101
|
+
/** 文件级别数字 */
|
|
102
|
+
#fileValue: number
|
|
103
|
+
/** 生效阈值:两路里更低的那个,低于它的日志直接不生成 */
|
|
104
|
+
#threshold: number
|
|
105
|
+
|
|
106
|
+
/** 是否输出控制台 */
|
|
107
|
+
readonly #console: boolean
|
|
108
|
+
/** 是否着色 */
|
|
109
|
+
readonly #color: boolean
|
|
110
|
+
/** 作用域列宽 */
|
|
111
|
+
readonly #scopeWidth: number
|
|
112
|
+
|
|
113
|
+
/** 根日志器 */
|
|
114
|
+
readonly root: Logger
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param config 日志配置
|
|
118
|
+
*/
|
|
119
|
+
constructor(config: LoggerConfig = {}) {
|
|
120
|
+
this.#level = config.level ?? "info"
|
|
121
|
+
this.#console = config.console !== false
|
|
122
|
+
this.#color = config.color ?? supportsColor()
|
|
123
|
+
this.#scopeWidth = config.scopeWidth ?? 14
|
|
124
|
+
this.#ring = new Array<LogRecord | undefined>(Math.max(50, config.tailSize ?? DEFAULT_TAIL))
|
|
125
|
+
|
|
126
|
+
this.#consoleValue = this.#console ? levelValue(config.consoleLevel ?? this.#level) : Number.POSITIVE_INFINITY
|
|
127
|
+
this.#fileValue = config.dir ? levelValue(config.fileLevel ?? this.#level) : Number.POSITIVE_INFINITY
|
|
128
|
+
this.#threshold = Math.min(this.#consoleValue, this.#fileValue)
|
|
129
|
+
|
|
130
|
+
this.#writer = config.dir
|
|
131
|
+
? new RotatingFileWriter({
|
|
132
|
+
dir: config.dir,
|
|
133
|
+
basename: config.basename,
|
|
134
|
+
maxSize: config.maxSize,
|
|
135
|
+
keepDays: config.keepDays,
|
|
136
|
+
maxFiles: config.maxFiles
|
|
137
|
+
})
|
|
138
|
+
: undefined
|
|
139
|
+
|
|
140
|
+
this.#pino = pino(
|
|
141
|
+
{
|
|
142
|
+
level: "trace",
|
|
143
|
+
// 去掉 pid/hostname:单机机器人用不上,白占日志体积
|
|
144
|
+
base: undefined,
|
|
145
|
+
serializers: { err: pino.stdSerializers.err }
|
|
146
|
+
},
|
|
147
|
+
{ write: (line: string) => this.#dispatch(line) }
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
this.root = new HubLogger(this, this.#pino)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 当前全局级别 */
|
|
154
|
+
get level(): LogLevel {
|
|
155
|
+
return this.#level
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 当前日志文件路径;未启用文件输出时 undefined */
|
|
159
|
+
get file(): string | undefined {
|
|
160
|
+
return this.#writer?.file
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 判断某级别当前是否会被输出
|
|
165
|
+
*
|
|
166
|
+
* 由 `HubLogger` 在每次打日志前调用;命中 false 时连消息都不拼。
|
|
167
|
+
* @param level 目标级别
|
|
168
|
+
* @returns 是否启用
|
|
169
|
+
*/
|
|
170
|
+
enabled(level: LogLevel): boolean {
|
|
171
|
+
return levelValue(level) >= this.#threshold
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 运行时修改级别
|
|
176
|
+
*
|
|
177
|
+
* 立刻对所有已创建的子日志器生效 —— 这正是把过滤放在包装层的原因。
|
|
178
|
+
* @param level 新级别
|
|
179
|
+
* @param target 只改某一路:`"console"` / `"file"`;缺省两路都改
|
|
180
|
+
*/
|
|
181
|
+
setLevel(level: LogLevel, target?: "console" | "file"): void {
|
|
182
|
+
const value = levelValue(level)
|
|
183
|
+
if (target === "console") {
|
|
184
|
+
if (this.#console) this.#consoleValue = value
|
|
185
|
+
} else if (target === "file") {
|
|
186
|
+
if (this.#writer) this.#fileValue = value
|
|
187
|
+
} else {
|
|
188
|
+
this.#level = level
|
|
189
|
+
if (this.#console) this.#consoleValue = value
|
|
190
|
+
if (this.#writer) this.#fileValue = value
|
|
191
|
+
}
|
|
192
|
+
this.#threshold = Math.min(this.#consoleValue, this.#fileValue)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 取尾部日志
|
|
197
|
+
* @param query 查询条件
|
|
198
|
+
* @returns 按时间正序的日志记录
|
|
199
|
+
*/
|
|
200
|
+
tail(query: TailQuery = {}): LogRecord[] {
|
|
201
|
+
const size = this.#ring.length
|
|
202
|
+
const count = this.#ringFull ? size : this.#ringPos
|
|
203
|
+
const minLevel = query.level ? levelValue(query.level) : 0
|
|
204
|
+
const keyword = query.keyword?.toLowerCase()
|
|
205
|
+
|
|
206
|
+
const out: LogRecord[] = []
|
|
207
|
+
for (let i = 0; i < count; i++) {
|
|
208
|
+
// 从最旧的一条开始按环形顺序读
|
|
209
|
+
const index = this.#ringFull ? (this.#ringPos + i) % size : i
|
|
210
|
+
const rec = this.#ring[index]
|
|
211
|
+
if (!rec) continue
|
|
212
|
+
if (levelValue(rec.level) < minLevel) continue
|
|
213
|
+
if (query.scope && rec.scope !== query.scope) continue
|
|
214
|
+
if (keyword && !rec.msg.toLowerCase().includes(keyword)) continue
|
|
215
|
+
out.push(rec)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const limit = query.limit ?? out.length
|
|
219
|
+
return limit >= out.length ? out : out.slice(out.length - limit)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 订阅实时日志(WebUI 的 SSE / WebSocket 推送用)
|
|
224
|
+
* @param fn 回调;**不要在里面打日志**,会被静默丢弃以防递归
|
|
225
|
+
* @returns 取消订阅
|
|
226
|
+
*/
|
|
227
|
+
subscribe(fn: (rec: LogRecord) => void): Disposer {
|
|
228
|
+
this.#subscribers.add(fn)
|
|
229
|
+
return () => void this.#subscribers.delete(fn)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** 同步刷盘 */
|
|
233
|
+
flush(): void {
|
|
234
|
+
this.#writer?.flushSync()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** 刷盘并关闭文件 */
|
|
238
|
+
close(): void {
|
|
239
|
+
this.#subscribers.clear()
|
|
240
|
+
this.#writer?.close()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 分发一行 pino 输出
|
|
245
|
+
*
|
|
246
|
+
* 一次解析,三处使用:控制台、文件、环形缓冲/订阅者。
|
|
247
|
+
* @param line pino 生成的 JSON 行
|
|
248
|
+
*/
|
|
249
|
+
#dispatch(line: string): void {
|
|
250
|
+
const rec = parseRecord(line)
|
|
251
|
+
if (!rec) {
|
|
252
|
+
// 无法解析时原样输出至控制台,优于直接丢弃
|
|
253
|
+
if (this.#console) process.stdout.write(line)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const value = levelValue(rec.level)
|
|
258
|
+
|
|
259
|
+
if (value >= this.#consoleValue) {
|
|
260
|
+
const text = formatRecord(rec, { color: this.#color, scopeWidth: this.#scopeWidth })
|
|
261
|
+
// warn 及以上走 stderr,便于 `yzng start 2> error.log` 单独收集
|
|
262
|
+
if (value >= LEVEL_VALUES.warn) process.stderr.write(text)
|
|
263
|
+
else process.stdout.write(text)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.#writer && value >= this.#fileValue) {
|
|
267
|
+
this.#writer.write(line)
|
|
268
|
+
// fatal 立即落盘:这行之后进程很可能就没了
|
|
269
|
+
if (value >= LEVEL_VALUES.fatal) this.#writer.flushSync()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
this.#ring[this.#ringPos] = rec
|
|
273
|
+
this.#ringPos = (this.#ringPos + 1) % this.#ring.length
|
|
274
|
+
if (this.#ringPos === 0) this.#ringFull = true
|
|
275
|
+
|
|
276
|
+
this.#notify(rec)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 通知订阅者
|
|
281
|
+
* @param rec 日志记录
|
|
282
|
+
*/
|
|
283
|
+
#notify(rec: LogRecord): void {
|
|
284
|
+
if (this.#subscribers.size === 0 || this.#notifying) return
|
|
285
|
+
this.#notifying = true
|
|
286
|
+
try {
|
|
287
|
+
for (const fn of this.#subscribers) {
|
|
288
|
+
try {
|
|
289
|
+
fn(rec)
|
|
290
|
+
} catch {
|
|
291
|
+
// 订阅者出错不能影响日志本身
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} finally {
|
|
295
|
+
this.#notifying = false
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 面向插件的日志器实现
|
|
302
|
+
*
|
|
303
|
+
* 只是 pino 的薄包装:加上级别前置过滤、console.log 风格参数拼接、`mark`。
|
|
304
|
+
*/
|
|
305
|
+
class HubLogger implements Logger {
|
|
306
|
+
/** 所属枢纽 */
|
|
307
|
+
readonly #hub: LoggerHub
|
|
308
|
+
/** 底层 pino 实例(已绑定 scope 等字段) */
|
|
309
|
+
readonly #pino: pino.Logger
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param hub 日志枢纽
|
|
313
|
+
* @param instance pino 实例
|
|
314
|
+
*/
|
|
315
|
+
constructor(hub: LoggerHub, instance: pino.Logger) {
|
|
316
|
+
this.#hub = hub
|
|
317
|
+
this.#pino = instance
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** 当前生效级别 */
|
|
321
|
+
get level(): LogLevel {
|
|
322
|
+
return this.#hub.level
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 最细粒度的追踪日志
|
|
327
|
+
* @param msg 消息
|
|
328
|
+
* @param args 附加参数
|
|
329
|
+
*/
|
|
330
|
+
trace(msg: unknown, ...args: unknown[]): void {
|
|
331
|
+
this.#emit("trace", msg, args)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* 调试信息
|
|
336
|
+
* @param msg 消息
|
|
337
|
+
* @param args 附加参数
|
|
338
|
+
*/
|
|
339
|
+
debug(msg: unknown, ...args: unknown[]): void {
|
|
340
|
+
this.#emit("debug", msg, args)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* 常规信息
|
|
345
|
+
* @param msg 消息
|
|
346
|
+
* @param args 附加参数
|
|
347
|
+
*/
|
|
348
|
+
info(msg: unknown, ...args: unknown[]): void {
|
|
349
|
+
this.#emit("info", msg, args)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 需要留意但不影响运行
|
|
354
|
+
* @param msg 消息
|
|
355
|
+
* @param args 附加参数
|
|
356
|
+
*/
|
|
357
|
+
warn(msg: unknown, ...args: unknown[]): void {
|
|
358
|
+
this.#emit("warn", msg, args)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 出错但可继续
|
|
363
|
+
* @param msg 消息
|
|
364
|
+
* @param args 附加参数
|
|
365
|
+
*/
|
|
366
|
+
error(msg: unknown, ...args: unknown[]): void {
|
|
367
|
+
this.#emit("error", msg, args)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 致命错误
|
|
372
|
+
* @param msg 消息
|
|
373
|
+
* @param args 附加参数
|
|
374
|
+
*/
|
|
375
|
+
fatal(msg: unknown, ...args: unknown[]): void {
|
|
376
|
+
this.#emit("fatal", msg, args)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* 重要信息,等价于 `info`
|
|
381
|
+
* @param msg 消息
|
|
382
|
+
* @param args 附加参数
|
|
383
|
+
*/
|
|
384
|
+
mark(msg: unknown, ...args: unknown[]): void {
|
|
385
|
+
this.#emit("info", msg, args)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 派生子日志器
|
|
390
|
+
* @param bindings 固定字段
|
|
391
|
+
* @returns 新日志器
|
|
392
|
+
*/
|
|
393
|
+
child(bindings: LogBindings): Logger {
|
|
394
|
+
return new HubLogger(this.#hub, this.#pino.child(bindings))
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 判断级别是否启用
|
|
399
|
+
* @param level 目标级别
|
|
400
|
+
* @returns 是否启用
|
|
401
|
+
*/
|
|
402
|
+
isLevelEnabled(level: LogLevel): boolean {
|
|
403
|
+
return this.#hub.enabled(level)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* 实际写日志
|
|
408
|
+
* @param level 级别
|
|
409
|
+
* @param msg 首个参数
|
|
410
|
+
* @param args 其余参数
|
|
411
|
+
*/
|
|
412
|
+
#emit(level: Exclude<LogLevel, "silent">, msg: unknown, args: readonly unknown[]): void {
|
|
413
|
+
if (!this.#hub.enabled(level)) return
|
|
414
|
+
const joined = joinArgs(args.length === 0 ? [msg] : [msg, ...args])
|
|
415
|
+
if (joined.err) this.#pino[level]({ err: joined.err }, joined.msg)
|
|
416
|
+
else this.#pino[level](joined.msg)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 创建日志枢纽
|
|
422
|
+
* @param config 日志配置
|
|
423
|
+
* @returns 日志枢纽
|
|
424
|
+
*/
|
|
425
|
+
export function createLoggerHub(config: LoggerConfig = {}): LoggerHub {
|
|
426
|
+
return new LoggerHub(config)
|
|
427
|
+
}
|