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级别的日志。如果所有日志混在一起,找起来太痛苦了。
maxsize和maxFiles参数,做日志轮转。
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集成。到时候我会分享更多实战中的坑和技巧。