1. 我们为什么需要结构化日志?

凌晨三点,服务器告警突然响起。当你挣扎着打开日志文件试图找到错误线索时,却发现满屏都是类似这样的记录:

[2023-08-15] ERROR: 未预期错误 @ middleware/auth.js:48 UserID=15893

这种传统日志就像侦探小说中被撕掉的页码,需要开发者用肉眼在大量文本中"人工解析"。结构化日志应运而生,它通过JSON格式保留完整的上下文信息:

{
  "timestamp": "2023-08-15T03:15:42.123Z",
  "level": "error",
  "message": "认证中间件异常",
  "module": "middleware/auth",
  "line": 48,
  "userId": 15893,
  "stack": "TypeError: Cannot read property 'token' of undefined..."
}

2. Winston模块实战(技术栈:Node.js + Winston)

2.1 基础日志配置

const winston = require('winston');

// 创建分层日志系统
const logger = winston.createLogger({
  level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({
      filename: 'combined.log',
      handleExceptions: true // 自动捕获未处理异常
    }),
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    })
  ]
});

// 接口访问日志示例
app.get('/api/users', (req, res) => {
  logger.info('接口请求接收', {
    path: req.path,
    method: req.method,
    params: req.query,
    clientIP: req.ip
  });
  // 业务逻辑处理...
});

2.2 自定义传输扩展

实现日志自动归档功能:

const { LogstashTransport } = require('winston-logstash-transport');

// ElasticSearch日志管道
logger.add(new LogstashTransport({
  host: 'log-server.prod',
  port: 5044,
  sslEnabled: true,
  maxConnectRetries: 5 // 网络异常重试机制
}));

// 错误报警集成
const SlackTransport = require('winston-slack-webhook-transport');
logger.add(new SlackTransport({
  webhookUrl: process.env.SLACK_WEBHOOK,
  formatter: ({ level, message, ...meta }) => ({
    text: `[${level.toUpperCase()}] ${message}`,
    attachments: [{
      fields: Object.entries(meta).map(([title, value]) => ({
        title,
        value: JSON.stringify(value),
        short: true
      }))
    }]
  })
}));

3. Bunyan模块深度解析(技术栈:Node.js + Bunyan)

3.1 灵活的日志流配置

const bunyan = require('bunyan');

const logger = bunyan.createLogger({
  name: 'order-service',
  serializers: bunyan.stdSerializers, // 内置请求序列化器
  streams: [
    {
      level: 'error',
      path: '/var/log/errors.log',
      type: 'rotating-file', // 日志轮转功能
      period: '1d',         // 每天归档
      count: 7              // 保留7天
    },
    {
      level: 'trace',
      stream: process.stdout
    }
  ]
});

// 带性能指标的日志记录
app.post('/checkout', async (req, res) => {
  const start = Date.now();
  logger.info({ req }, '订单创建请求开始');
  
  try {
    // 业务处理...
    logger.info({
      orderId: newOrder.id,
      duration: Date.now() - start
    }, '订单处理完成');
  } catch (error) {
    logger.error({
      err: error,
      paymentMethod: req.body.paymentType
    }, '支付处理失败');
  }
});

3.2 调试神器:日志追踪

// 使用child logger创建请求上下文
app.use((req, res, next) => {
  req.log = logger.child({
    reqId: uuid.v4(),
    sessionId: req.cookies.sessionId,
    route: req.originalUrl
  });
  next();
});

// 在任意位置记录关联日志
function processPayment(paymentInfo) {
  req.log.debug({ paymentInfo }, '开始处理支付');
  // 支付逻辑...
  req.log.info('支付验证通过');
}

4. 性能之王Pino实战(技术栈:Node.js + Pino)

4.1 极简配置

const pino = require('pino');

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  formatters: {
    level(label) {
      return { severity: label.toUpperCase() } // 适配云平台日志等级
    }
  },
  transport: {
    targets: [
      {
        target: 'pino-pretty', // 开发环境友好输出
        options: { colorize: true }
      },
      {
        target: 'pino/file',
        options: { destination: '/var/log/app.log' }
      }
    ]
  }
});

// 异步日志实践
app.post('/upload', async (req, res) => {
  const file = await parseUpload(req);
  logger.info({ fileSize: file.size }, '文件接收成功');
  
  try {
    const result = await processFile(file);
    logger.info({ resultId: result.id }, '文件处理完成');
  } catch (error) {
    logger.error(error, '文件处理失败');
  }
});

4.2 性能优化技巧

// 启用极速模式
const logger = pino({
  browser: {
    write: {
      // 禁用高开销方法
      trace: () => {},
      debug: () => {}
    }
  }
});

// 敏感信息过滤
const redaction = pino.redact({
  paths: ['password', 'creditCard.*', 'user.address'],
  censor: '**REDACTED**'
});

logger.info({
  user: {
    name: '张三',
    password: '123456',
    creditCard: { number: '1234-5678-9012-3456' }
  }
}, '用户登录成功');
// 输出: {"user":{"name":"张三","password":"**REDACTED**"...}}

5. 应用场景深度剖析

5.1 Winston适用场景

  • 需要同时输出到多个目标的混合环境
  • 旧系统改造时兼容多种日志格式
  • 需要自定义传输通道的场景(如推送到Kafka)

5.2 Bunyan优势领域

  • 需要根据请求上下文追踪日志链
  • 需要细粒度日志轮转配置
  • 与ELK等日志分析系统深度集成

5.3 Pino最佳实践

  • 高并发、高性能要求的服务端应用
  • Serverless函数等需要快速启动的环境
  • 需要精细控制日志性能开销的场景

6. 三剑客核心对比

特性 Winston Bunyan Pino
日志格式 高度可定制 JSON标准 优化JSON
性能 中等 中等 极快
学习曲线 陡峭 中等 平缓
生态插件 最丰富 较丰富 持续增长中
内存消耗 较高 中等 极低
异步支持 插件实现 原生支持 原生优化

7. 注意事项精要

  1. 日志切割策略:生产环境必须配置日志轮转,避免单个文件过大
// Winston日志轮转示例
const DailyRotateFile = require('winston-daily-rotate-file');
logger.add(new DailyRotateFile({
  filename: 'application-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  zippedArchive: true,   // 自动压缩旧日志
  maxSize: '20m',        // 单个文件不超过20MB
  maxFiles: '30d'        // 保留30天
}));
  1. 敏感数据处理:需配置字段过滤规则,避免记录密码等隐私信息

  2. 上下文信息:建议每个请求生成唯一ID贯穿所有关联日志

  3. 日志分级:生产环境慎用debug级别日志,可能暴露系统细节

  4. 错误处理:异步操作必须包含错误日志,但需避免重复记录

8. 总结与选择建议

  • 新项目启动:优先考虑Pino,其性能优势在微服务架构中表现突出
  • 现有系统维护:Winston的灵活性适合复杂的老系统改造
  • 全链路追踪需求:Bunyan的child logger机制是分布式系统的利器
  • 终极方案:大型项目可采用分层策略,用Pino处理高频日志,Winston管理报警日志