• 微信:WANCOME
  • 扫码加微信,提供专业咨询
  • 服务热线
  • 13215191218
    13027920428

  • 微信扫码访问本页
desc-2
环企首页

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 在路由之前注册