1
0

feat: 运行时日志系统,Pino + pino-pretty + pino-roll,console/file 双输出,敏感信息 redaction

This commit is contained in:
2026-05-21 12:21:59 +08:00
parent 0d709c7681
commit 007d74934d
26 changed files with 1713 additions and 114 deletions

View File

@@ -48,9 +48,10 @@ DiAL 使用 `package.json.version` 作为应用版本号的唯一来源,遵循
```text
src/
server/
bootstrap.ts 后端统一启动引导loadConfig → store → engine → startServer → shutdown
bootstrap.ts 后端统一启动引导loadConfig → logger → store → engine → startServer → shutdown
config.ts CLI 参数解析(仅提取配置文件路径)
dev.ts 开发模式启动入口mode: "development",仅 API server
logger.ts 日志模块Logger 接口、Pino 运行时封装、NoopLogger、MemoryLogger、ConsoleFallbackLogger
main.ts 生产模式启动入口mode: "production",安全头启用)
server.ts HTTP server 启动工厂Bun.serve routes 声明式路由 + fetch fallback 静态资源服务)
helpers.ts 共享响应格式化工具(见下方函数清单)
@@ -139,11 +140,12 @@ probe-config.schema.json 用户配置 JSON Schema 导出物(用于 IDE 自动
dev.ts / main.ts → readRuntimeConfig(cli args, 仅提取 configPath)
→ bootstrap({ configPath, mode })
→ loadConfig(yamlYAML 解析 → 变量替换 → 契约校验 → 语义校验 → resolve)
→ ResolvedConfig{ host, port, dataDir, maxConcurrentChecks, retentionMs, targets }
→ ResolvedConfig{ host, port, dataDir, maxConcurrentChecks, retentionMs, targets, logging }
→ createRuntimeLogger(logging) → Logger配置加载失败时使用 ConsoleFallbackLogger
→ ProbeStore(db) → store.syncTargets(targets)
→ ProbeEngine(store, targets, maxConcurrentChecks, retentionMs) → engine.start()
→ startServer({ config, mode, store })
→ 注册 SIGINT/SIGTERM shutdownengine.stop + store.close
→ ProbeEngine(store, targets, maxConcurrentChecks, retentionMs, logger) → engine.start()
→ startServer({ config, mode, store, logger })
→ 注册 SIGINT/SIGTERM shutdownengine.stop + store.close + logger.flush
运行时:
定时器(tick) → ProbeEngine.probeGroup()
@@ -257,6 +259,7 @@ export function handleMetrics(idStr: string, url: URL, store: ProbeStore, mode:
| `configDir` | 配置文件所在目录 | — |
| `dataDir` | `server.dataDir`(基于配置文件目录解析为绝对路径) | `configDir/data` |
| `host` | `server.host` | `127.0.0.1` |
| `logging` | `logging`(等级继承、路径解析、滚动参数) | 见 logging 配置 |
| `port` | `server.port` | `3000` |
| `maxConcurrentChecks` | `runtime.maxConcurrentChecks` | `20` |
| `retentionMs` | `runtime.retention` | `7d` |
@@ -566,8 +569,63 @@ if (r.body) {
- **异常可观测**`probeGroup()``Promise.allSettled` 的 rejected 结果通过索引关联 target并写入 `phase:"internal"` 的失败记录
- **数据清理**:当 `retentionMs > 0`engine 启动时立即执行一次 `store.prune()`,之后每小时定时执行,按 `timestamp` 清理过期数据
- **生命周期**`start()`/`stop()` 管理定时器(含调度定时器和清理定时器),`stop()` 清理所有 `setInterval`
- **日志集成**engine 构造时接收 `Logger` 实例(可选,默认 NoopLogger通过 `initStateCache()` 从 store 加载最新状态状态变化时记录日志UP→DOWN `warn`、DOWN→UP `info`、首次检查 DOWN `warn`、稳态无日志);每次检查产出 `debug` 级别结构化摘要
### 1.10 expect 断言系统
### 1.10 日志模块
日志模块位于 `src/server/logger.ts`,定义项目内部最小 `Logger` 接口,后端运行时代码统一通过此接口输出日志。
**Logger 接口**
| 方法 | 说明 |
| ------- | ---------------------------------------- |
| `trace` | 级别 trace开发调试 |
| `debug` | 级别 debug检查摘要、状态详情 |
| `info` | 级别 info启动、恢复、正常操作 |
| `warn` | 级别 warn状态变化 UP→DOWN、首次 DOWN |
| `error` | 级别 errorchecker 执行异常) |
| `fatal` | 级别 fatal启动失败 |
| `child` | 创建子 logger附加 bindings 上下文) |
| `flush` | 刷新缓冲(用于关机前确保日志落盘) |
每个方法支持两种签名:`(msg: string)``(obj: Record<string, unknown>, msg?: string)`
**实现**
| 实现 | 用途 |
| ----------------------- | ----------------------------------------------- |
| `PinoLoggerWrapper` | 生产运行时,封装 Pino + pino-pretty + pino-roll |
| `NoopLogger` | 静默丢弃所有日志,用于不需要日志输出的场景 |
| `MemoryLogger` | 测试替身,将日志条目收集到 `entries` 数组供断言 |
| `ConsoleFallbackLogger` | 配置加载失败前的降级日志,直接输出到 console |
**日志输出**
- **控制台**:始终开启,使用 pino-pretty 格式化(彩色、单行、时间戳 `yyyy-mm-dd HH:MM:ss.l`
- **文件**始终开启JSONL 格式,通过 pino-roll 支持按大小和频率滚动
- **根等级**:取 console 和 file 中的最低等级,确保两个流都能收到所需日志
- **敏感信息**:自动 redact `authorization``cookie``set-cookie``authToken``key``password``token``apiKey` 及其嵌套路径,替换为 `[Redacted]`
**测试用法**
```typescript
import { createMemoryLogger } from "../logger";
const logger = createMemoryLogger();
const engine = new ProbeEngine(store, targets, 20, 0, logger);
// 断言日志
expect(logger.entries.filter((e) => e.level === "warn")).toHaveLength(1);
expect(logger.entries[0]!.msg).toContain("UP → DOWN");
```
**运行时规范**
- `src/server/` 下的运行时代码禁止直接使用 `console.*`,必须通过注入的 `Logger` 实例输出
- 配置加载失败logger 尚未初始化)时使用 `ConsoleFallbackLogger`
- `bootstrap.ts` 在 shutdown 时调用 `logger.flush()` 确保缓冲日志写入磁盘
### 1.11 expect 断言系统
两层模型:**观测值收集** → **规则校验**。共享断言基础设施位于 `checker/expect/`checker 专属状态断言位于各自目录。
@@ -645,14 +703,14 @@ expect 字段
7. **实现时参考 [1.7.5 五层管线](#175-步骤四实现-checker-类) 中的对应表**。决策树解决"选哪种模型",五层管线表解决"每种模型从类型定义到执行分别调哪个函数"。
### 1.11 错误模式
### 1.12 错误模式
- **API 错误**`{ error: "描述", status: <code> }`,状态码 400/404/503
- **CheckFailure**`{ kind: "error"|"mismatch", phase, path, expected?, actual?, message }`
- **错误处理**expect 校验失败记录首个失败原因;网络/超时/进程崩溃统一为 `kind:"error"`,请求/TLS/timeout 错误归属 `phase:"request"`body 超限/解码/解析错误归属 `phase:"body"`
- **日志**解析失败等非致命异常用 `console.warn`,启动失败用 `console.error` + `process.exit(1)`
- **日志**运行时日志通过 `Logger` 接口统一输出Pino 运行时、Noop/Memory/ConsoleFallback 测试替身),配置加载失败前使用 ConsoleFallbackLogger禁止在 `src/server/` 运行时代码中直接使用 `console.*`
### 1.12 测试规范
### 1.13 测试规范
- 测试目录 `tests/` 镜像 `src/` 目录结构,但共享 expect 模块的测试集中放在 `tests/server/checker/runner/shared/` 下,覆盖 `failure.ts``value.ts`operator`content.ts`body/text`keyed.ts`headers/duplicate-key`validate.ts`shorthand`redos.ts`
- 使用 `bun:test` 框架(`describe`/`test`/`expect`),测试数据库用临时目录 + `tmpdir()`