概述
事故期间,日志是系统与排障人员之间的主要接口。自由文本只能靠 grep 猜测;结构化日志则可以按 tenantId、requestId、status 在几秒内过滤。结构本身就是契约,和其他契约一样需要测试:字段改名或上下文丢失,对每个排查问题的人都是破坏性变更。
步骤
- 让 handler 接收注入的 logger,测试时绑定到内存流:
const stream = new Writable({
write(chunk, _enc, cb) { lines.push(JSON.parse(chunk.toString())); cb(); },
});
const logger = pino({ redact: ['req.headers.authorization', 'user.email'] }, stream);
- 每个请求输出一行带稳定字段的日志:
logger.info({ requestId, tenantId, route, status, durationMs }, 'request completed');
- 断言字段而不是句子:
tenantId、status、durationMs是否存在且类型正确。 - 断言脱敏:序列化后的全部日志中不得出现
Bearer或用户邮箱。 - 为失败路径补一个用例,断言
status、errorCode与requestId同时存在。
验证
正常请求应产生恰好一条 request completed 日志,包含五个字段;把 tenantId 从 logger 中删除后测试应立即失败。脱敏测试中故意传入一个真实格式的令牌与邮箱,序列化结果里都不应出现。422 响应应带 errorCode=validation_failed。
注意事项
- 不要使用全局单例 logger 或 monkey-patch console,否则测试之间会互相干扰。
msg只作为稳定的事件名,断言其值即可,不要断言整句描述。- 脱敏路径必须匹配真实对象结构,嵌套字段与数组也要覆盖。
- 日志采样、聚合与保留策略不在该测试范围内,需要单独的运行验证。
- 同步内存流会掩盖背压与序列化开销,压测时应使用真实目标验证。