环企首页
pino
概述
pino 是目前最快的 Node.js 日志库,比 winston 快约 10 倍,比 log4js 快约 5 倍。核心设计思想是结构化日志:输出 JSON 格式而非人类可读文本,便于机器解析(ELK、Loki、Datadog 等日志系统),同时提供 pino-pretty 插件在开发时输出人类可读的格式。
核心特点:
- 极致的性能:异步写入,几乎零阻塞
- 结构化 JSON 输出:每个日志字段都是独立的 key,便于检索
- 极低的日志开销:在压测场景下对 QPS 影响极小
- pino-http:自动记录每个 HTTP 请求的耗时和状态
典型使用场景:
- 高并发 API 服务
- 生产环境日志收集系统
- 需要结构化日志的微服务架构
核心函数
pino(options)
创建 logger 实例。
const pino = require('pino')
// 默认输出到 stdout(JSON 格式)
const logger = pino()
// 完整配置
const logger = pino({
level: 'info', // 最低记录级别(trace/debug/info/warn/error/fatal)
// 输出目标(可传字符串路径或流对象)
transport: {
target: 'pino-pretty', // 开发时美化输出(需安装 pino-pretty)
options: {
colorize: true, // 彩色输出
translateTime: 'SYS:standard',
ignore: 'pid,hostname' // 忽略的字段
}
},
// 生产环境不用 transport,直接写到文件
// transport: '/path/to/logfile'
// 自定义级别
customLevels: {
notice: 35 // 新增 notice 级别(35 介于 info(30) 和 warn(40)之间)
},
// 静默某些模块的日志
serializers: {
req: () => '[Redacted]',
res: () => '[Redacted]',
err: () => '[Redacted]'
},
// 基础字段(每个日志行都会带上)
base: {
service: 'user-api',
version: process.env.APP_VERSION
},
// 时间戳格式
timestamp: pino.stdTimeFunctions.isoTime, // 默认:ISO 格式
// timestamp: pino.stdTimeFunctions.unixTime, // Unix 时间戳
// timestamp: false, // 关闭时间戳
})
日志级别方法
logger.trace('这是一条 trace 日志') // 10,最详细(调试用)
logger.debug('这是一条 debug 日志') // 20,调试信息
logger.info('这是一条 info 日志') // 30,一般信息(默认级别)
logger.warn('这是一条 warn 日志') // 40,警告
logger.error('这是一条 error 日志') // 50,错误
logger.fatal('这是一条 fatal 日志') // 60,致命错误(程序即将退出)
// 带对象(结构化日志)
logger.info({ userId: 123, action: 'login' }, '用户登录')
// 输出: { "level": 30, "time": ..., "userId": 123, "action": "login", "msg": "用户登录" }
// 带错误对象
try {
throw new Error('数据库连接失败')
} catch (err) {
logger.error({ err, operation: 'db.connect' }, '操作失败')
}
// 带额外字段
logger.warn({ count: 42, threshold: 100 }, '超出阈值')
// 条件日志(不满足条件不记录,减少开销)
logger.debug({ expensive: true }, '性能分析数据') // NODE_ENV=production 时自动跳过
logger.child(bindings)
创建子 logger,继承父级配置并附加额外字段。
// 为每个请求创建独立的子 logger
app.use((req, res, next) => {
req.log = logger.child({ requestId: generateId(), ip: req.ip })
next()
})
// 在路由中使用
app.get('/users/:id', (req, res) => {
req.log.info({ userId: req.params.id }, '查询用户')
// 所有该请求的日志都带 requestId 和 ip
})
// 子 logger 继承父级 level
const childLogger = logger.child({ module: 'auth' })
childLogger.info('认证模块日志') // 继承父级 level
logger.level
动态修改日志级别。
logger.level = 'debug' // 运行时改为 debug 级别
logger.isLevelEnabled(level)
检查某级别是否会被记录。
if (logger.isLevelEnabled('debug')) {
logger.debug('这是调试信息') // 只在 debug 级别时执行
}
logger.flush()
刷新缓冲区(确保日志写入后再退出程序)。
process.on('SIGTERM', () => {
logger.info('收到 SIGTERM,正在关闭...')
logger.flush() // 确保日志写入
process.exit(0)
})
输出格式
生产环境 JSON 输出(默认)
{"level":30,"time":1704067200000,"pid":1234,"hostname":"server-1","msg":"用户登录成功","userId":123,"service":"user-api"}
{"level":40,"time":1704067201000,"pid":1234,"hostname":"server-1","msg":"请求超时","duration":5001}
{"level":50,"time":1704067202000,"pid":1234,"hostname":"server-1","err":{"type":"Error","message":"数据库连接失败","stack":"..."},"operation":"db.connect"}
开发环境美化输出(pino-pretty)
[2024-01-01 12:00:00] INFO (1234): 用户登录成功
userId: 123
service: user-api
[2024-01-01 12:00:01] WARN (1234): 请求超时
duration: 5001
[2024-01-01 12:00:02] ERROR (1234): 操作失败
err: Error: 数据库连接失败
operation: db.connect
文件轮转(pino-rolling)
需要配合 pino-rolling 或系统工具(logrotate)实现日志轮转。
const pino = require('pino')
const { rollingFile } = require('pino-rolling')
const logger = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime
}, rollingFile({
filename: './logs/app',
size: '100m', // 单文件最大 100MB
interval: '1d', // 每天一个新文件
maxFiles: 7 // 保留 7 个文件
}))
与 Express/Fastify 集成
Express(pino-http)
const express = require('express')
const pinoHttp = require('pino-http')
const app = express()
app.use(pinoHttp({
logger: logger,
autoLogging: {
ignore: (req) => req.url === '/health' // 忽略健康检查
},
customLogLevel: (req, res, err) => {
if (res.statusCode >= 500) return 'error'
if (res.statusCode >= 400) return 'warn'
return 'info'
},
customSuccessMessage: (req, res) => `${req.method} ${req.url}`,
customErrorMessage: (req, res, err) => `${req.method} ${req.url} - ${err.message}`,
customAttributeKeys: {
req: 'request',
res: 'response',
err: 'error',
responseTime: 'duration'
}
}))
// 每个请求自动记录:方法、路径、状态码、响应时间
app.get('/users', (req, res) => {
res.json([])
})
// 在路由中用 logger
app.get('/users/:id', (req, res) => {
req.log.info({ userId: req.params.id }) // 自动带请求追踪字段
res.json({ id: req.params.id })
})
Fastify(内置 pino)
const fastify = require('fastify')({
logger: {
level: 'info',
// pino-pretty 开发时美化
transport: process.env.NODE_ENV === 'development' ? {
target: 'pino-pretty',
options: { colorize: true }
} : undefined
}
})
fastify.get('/users', async (req, reply) => {
req.log.info({ query: req.query })
return []
})
与日志收集系统集成
输出到文件(生产)
const pino = require('pino')
const fs = require('fs')
// 多目标输出(stdout + 文件)
const logger = pino({
level: 'info'
}, pino.multistream([
{ stream: process.stdout }, // 控制台
{ stream: fs.createWriteStream('./app.log') } // 文件
]))
// 压缩写入(pino-roll)
const { createWriteStream } = require('pino-roll')
const logger = pino({
level: 'info',
transport: {
target: 'pino-roll',
options: {
filename: './logs/app',
size: '100m',
maxFiles: 10
}
}
})
常见问题
| 问题 | 解决方案 |
|---|---|
| 生产日志全是 JSON 无法阅读 | pino-pretty 只用于开发,生产保持 JSON |
| 想在 JSON 中看到堆栈但被截断 | err.serializer 自定义序列化,或 pino -o json 工具转换 |
| 日志丢失(进程崩溃前未写入) | logger.flush() + 优雅退出处理 |
| 想过滤掉某些字段 | logger.child({ serializers: { req: false } }) |
| pino-http 不生效 | 确保 pino-http 在路由之前注册 |