feat: 运行时日志系统,Pino + pino-pretty + pino-roll,console/file 双输出,敏感信息 redaction
This commit is contained in:
@@ -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(yaml:YAML 解析 → 变量替换 → 契约校验 → 语义校验 → 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 shutdown(engine.stop + store.close)
|
||||
→ ProbeEngine(store, targets, maxConcurrentChecks, retentionMs, logger) → engine.start()
|
||||
→ startServer({ config, mode, store, logger })
|
||||
→ 注册 SIGINT/SIGTERM shutdown(engine.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` | 级别 error(checker 执行异常) |
|
||||
| `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()`
|
||||
|
||||
Reference in New Issue
Block a user