← 中文资源

简体中文版本

在测试中断言结构化日志

在测试中捕获日志输出,断言必需字段与脱敏结果,让缺失请求上下文的问题在生产排障之前暴露。

阅读英文原文

概述

事故期间,日志是系统与排障人员之间的主要接口。自由文本只能靠 grep 猜测;结构化日志则可以按 tenantId、requestId、status 在几秒内过滤。结构本身就是契约,和其他契约一样需要测试:字段改名或上下文丢失,对每个排查问题的人都是破坏性变更。

步骤

  1. 让 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);
  1. 每个请求输出一行带稳定字段的日志:
logger.info({ requestId, tenantId, route, status, durationMs }, 'request completed');
  1. 断言字段而不是句子:tenantId、status、durationMs 是否存在且类型正确。
  2. 断言脱敏:序列化后的全部日志中不得出现 Bearer 或用户邮箱。
  3. 为失败路径补一个用例,断言 status、errorCode 与 requestId 同时存在。

验证

正常请求应产生恰好一条 request completed 日志,包含五个字段;把 tenantId 从 logger 中删除后测试应立即失败。脱敏测试中故意传入一个真实格式的令牌与邮箱,序列化结果里都不应出现。422 响应应带 errorCode=validation_failed。

注意事项

  • 不要使用全局单例 logger 或 monkey-patch console,否则测试之间会互相干扰。
  • msg 只作为稳定的事件名,断言其值即可,不要断言整句描述。
  • 脱敏路径必须匹配真实对象结构,嵌套字段与数组也要覆盖。
  • 日志采样、聚合与保留策略不在该测试范围内,需要单独的运行验证。
  • 同步内存流会掩盖背压与序列化开销,压测时应使用真实目标验证。