Pino 和 Bunyan 对比:Node.js 结构化日志之争
Bunyan 开创了 Node.js 结构化日志,Pino 用极致性能将其发扬。本文从架构、性能、功能生态与生产部署全面对比两大日志库。
新项目选 Pino——支持低开销日志与异步传输、生态活跃(Fastify 默认)、传输器生态丰富。Bunyan 是 Pino 的思想先驱,但项目已进入维护状态,存量项目可继续用,新投资选 Pino。
基本盘
Bunyan(2012):Node.js 结构化 JSON 日志的开创者——日志即数据(可查询、可分析),配套 CLI 美化工具,企业级应用长期信赖。
Pino(2016):同理念的性能优先重写——开销最低的 Node 日志库,Fastify 的默认 logger,生态活跃度全面超越。
架构与性能
- Bunyan:同步写日志,序列化开销在主线程;
- Pino:三个优化——最小化主线程序列化、worker 线程异步传输(pino.transport)、sonic-boom 高速写流。
比较开销时需统一输出目标、字段量、序列化及同步/异步设置。高并发服务中日志库的开销是真实成本——QPS 越高差距越值钱。
功能对比
| 能力 | Bunyan | Pino |
|---|---|---|
| 结构化 JSON | ✓ | ✓ |
| 子 logger | ✓ | ✓(child) |
| 级别 | 六级 | 六级+自定义 |
| 传输器 | 内置 stream | pino.transport(异步) |
| 美化 | bunyan CLI | pino-pretty |
| 字段脱敏 | 有限 | redact 内建 |
Pino 的 redact(日志中自动打码密码/token 字段)是合规场景的刚需级特性。
开发体验
// Pino 的现代用法
const pino = require('pino');
const logger = pino({
level: 'info',
redact: ['req.headers.authorization', '*.password'],
transport: process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty' } // 开发期美化,生产纯 JSON
: undefined,
});
logger.info({ userId: 123 }, '用户登录');
// {"level":30,"time":...,"userId":123,"msg":"用户登录"}
生产部署
两家的共同哲学:日志写 stdout,采集交给基础设施(容器/系统日志收集),不要在应用里直连日志后端。Pino 的 transport worker 模式让"进程内转发"也可行且不阻塞主线程。
让结构化日志能够关联请求
选择 Pino 或 Bunyan 后,先统一时间、级别、服务名和错误堆栈的字段,再配置 DataKit 的文件或容器日志采集与 Pipeline 解析。在观测云中,日志与链路关联依赖匹配的 trace_id、span_id、service、env、version,不能只增加一个名称相似的字段。日志库与 tracing SDK 的注入方式要按版本验证,并过滤令牌、密码和个人信息。
常见问题(FAQ)
Q:Winston 的位置?
生态同样庞大,性能介于两者之间。已有 Winston 的项目不必强迁;新项目按性能与生态选 Pino。
Q:日志级别怎么规划?
fatal/error:需要告警的异常;warn:降级与异常路径;info:关键业务事件;debug:排障细节(生产默认关闭);trace:框架级追踪。生产跑 info,排查时动态调 debug(Pino 支持运行时改级别)。
Q:requestId 怎么注入日志?
Pino 的 child logger:中间件里 req.log = logger.child({ requestId }),后续该请求的所有日志自动带 requestId——日志串联的标准模式。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。