3、Winston入门:Winston安装与配置、创建Logger实例、Transport概念与使用、格式化输出

日志系统是Node.js应用的「黑匣子」。线上出了Bug,第一反应就是翻日志。我见过太多项目,日志打得乱七八糟,出问题根本没法排查。今天我们就来搞定Winston——Node.js生态里最成熟的日志库。

3.1 为什么选Winston?

说实话,Node.js的日志库不少。有log4js、pino、bunyan。但我个人习惯用Winston,原因有三:

  • 插件化架构:Transport机制让输出变得极其灵活
  • 生产验证:我在好几个日活百万的项目里用过,稳得很
  • 社区活跃:遇到坑,StackOverflow上基本都有答案

你想想看,一个日志库要是没人维护了,你敢用在生产环境吗?Winston从2010年到现在,一直有人在更新。

3.2 安装与基础配置

安装很简单,一行命令搞定:

npm install winston

嗯,这里要注意。Winston的v3版本和v2版本API差别很大。如果你看网上的老教程,很可能是v2的写法。我现在讲的都是v3+的用法。

创建一个最基本的Logger实例:

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console()
  ]
});

logger.info('Hello, Winston!');
logger.warn('这是一条警告');
logger.error('这是一条错误');

跑一下,控制台会输出JSON格式的日志。为什么默认是JSON?因为生产环境里,日志是要被日志收集系统(比如ELK)解析的,JSON最友好。

3.3 日志级别:从沉默到话痨

Winston的日志级别,说白了就是优先级。数字越小越重要:

级别 数值 说明
error 0 致命错误,必须立即处理
warn 1 警告,不影响运行但需要注意
info 2 常规信息,记录关键流程
http 3 HTTP请求相关
verbose 4 详细信息
debug 5 调试信息,开发时用
silly 6 最啰嗦的级别,基本不用

设置level: 'info',意味着只记录info及以上级别的日志。debug和silly会被忽略。我在项目中习惯这样配:

  • 开发环境:level设为debug,方便调试
  • 生产环境:level设为info,避免日志刷爆磁盘
我的小技巧:用环境变量控制日志级别。比如level: process.env.LOG_LEVEL || 'info',这样部署时灵活调整。

3.4 Transport:日志要去哪?

Transport是Winston最核心的概念。它决定了日志的「目的地」。你可以把日志同时输出到多个地方。

常用的Transport有:

  • Console:控制台输出,开发时必备
  • File:写入文件,生产环境标配
  • Http:发送到远程API
  • Stream:写入任意可写流

来看一个实际配置:

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    // 控制台输出
    new winston.transports.Console({
      format: winston.format.simple()
    }),
    // 所有日志写入一个文件
    new winston.transports.File({
      filename: 'logs/combined.log'
    }),
    // 错误日志单独写一个文件
    new winston.transports.File({
      filename: 'logs/error.log',
      level: 'error'
    })
  ]
});

这里有个细节:错误日志单独存一份。为什么?因为线上排查问题时,你通常只想看error级别的日志。如果所有日志混在一起,找起来太痛苦了。

避坑指南:我曾经在生产环境遇到过日志文件把磁盘撑爆的情况。记得配置maxsizemaxFiles参数,做日志轮转。
new winston.transports.File({
  filename: 'logs/app.log',
  maxsize: 5242880, // 5MB
  maxFiles: 5,      // 保留5个文件
  tailable: true    // 滚动写入
})

3.5 格式化输出:让日志更好看

默认的JSON格式虽然机器友好,但人眼看着费劲。Winston提供了丰富的格式化工具。

我最常用的组合:

const { combine, timestamp, printf, colorize } = winston.format;

const myFormat = printf(({ level, message, timestamp, ...meta }) => {
  return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ''}`;
});

const logger = winston.createLogger({
  format: combine(
    colorize(),      // 给日志上色
    timestamp(),     // 加时间戳
    myFormat         // 自定义格式
  ),
  transports: [
    new winston.transports.Console()
  ]
});

logger.info('用户登录成功', { userId: 123, ip: '192.168.1.1' });
// 输出: 2024-01-15T10:30:00.000Z [info]: 用户登录成功 {"userId":123,"ip":"192.168.1.1"}

嗯,这里要注意colorize。它只在Console Transport里生效。写入文件时不要加颜色,否则文件里全是乱码的ANSI转义字符。

3.6 实际项目中的配置模板

最后,分享一个我在项目中实际使用的配置模板。你可以直接拿去用:

const winston = require('winston');
const path = require('path');

const logDir = path.join(__dirname, '../logs');

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp({
      format: 'YYYY-MM-DD HH:mm:ss'
    }),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.printf(({ timestamp, level, message, stack }) => {
          return `${timestamp} [${level}]: ${stack || message}`;
        })
      )
    }),
    new winston.transports.File({
      filename: path.join(logDir, 'error.log'),
      level: 'error',
      maxsize: 5242880,
      maxFiles: 5
    }),
    new winston.transports.File({
      filename: path.join(logDir, 'combined.log'),
      maxsize: 5242880,
      maxFiles: 10
    })
  ],
  exceptionHandlers: [
    new winston.transports.File({
      filename: path.join(logDir, 'exceptions.log')
    })
  ]
});

module.exports = logger;

这个配置有几个亮点:

  • 错误堆栈:通过winston.format.errors({ stack: true })自动捕获错误堆栈
  • 异常处理exceptionHandlers能捕获未处理的异常
  • 目录分离:日志文件统一放在logs目录下,方便管理

核心要点回顾

  • Winston通过Transport机制实现多路输出
  • 日志级别控制输出粒度,生产环境建议info
  • File Transport务必配置日志轮转,防止磁盘爆满
  • 格式化输出要区分开发环境和生产环境

好了,Winston的基础用法就这些。下一章我们会深入Winston的高级特性,包括自定义Transport、日志切割、以及如何与Express集成。到时候我会分享更多实战中的坑和技巧。