Node.js 日志记录最佳实践与方案选型

发布于2026-08-26 16:52 阅读23次 梳理 Node.js 生产环境日志记录的最佳实践,涵盖日志分级、结构化日志、winston 和 pino 两大主流日志库的对比与配置、日志切割与归档、日志采集等,附代码示例和常见踩坑。
# Node.js 日志记录最佳实践与方案选型
## 前言
日志是排查问题的第一手资料,但很多项目要么 `console.log` 到处飞,要么日志格式混乱、级别缺失。本文系统梳理 Node.js 日志的正确做法。
## 为什么不直接用 console.log
- 无法区分日志级别(debug/info/error)
- 无法结构化输出(JSON),不利于采集分析
- 无法控制输出目的地(文件/控制台/远程)
- 无法按需切割、归档
生产环境应使用成熟的日志库。
## 日志级别
```javascript
// 从低到高
// error: 错误,需要立即关注
// warn: 警告,潜在问题
// info: 关键业务信息
// debug: 调试信息,生产环境关闭
```
环境区分:开发环境输出 debug,生产环境只输出 info 及以上。
## 方案一:winston
### 安装与基础配置
```bash
npm install winston
```
```javascript
const winston = require("winston");
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "logs/error.log", level: "error" }),
new winston.transports.File({ filename: "logs/combined.log" }),
],
});
logger.info("用户登录成功", { userId: 123 });
logger.error("数据库连接失败", { err: error.message });
```
### 日志切割(按天/按大小)
```bash
npm install winston-daily-rotate-file
```
```javascript
const DailyRotateFile = require("winston-daily-rotate-file");
const transport = new DailyRotateFile({
filename: "logs/app-%DATE%.log",
datePattern: "YYYY-MM-DD",
maxSize: "20m",
maxFiles: "14d",
});
const logger = winston.createLogger({
transports: [transport],
});
```
## 方案二:pino
### 安装与使用
```bash
npm install pino
```
```javascript
const pino = require("pino");
const logger = pino({
level: process.env.LOG_LEVEL || "info",
base: { pid: process.pid },
timestamp: pino.stdTimeFunctions.isoTime,
});
logger.info({ userId: 123 }, "用户登录成功");
logger.error({ err }, "数据库连接失败");
```
pino 以极高性能著称,适合高吞吐场景。
## winston vs pino 对比
| 特性 | winston | pino |
|------|---------|------|
| 性能 | 中等 | 极高 |
| 生态 | 丰富(transport 多) | 轻量高效 |
| 学习成本 | 稍高 | 低 |
| 适用场景 | 功能全面 | 高性能 |
追求性能选 pino,需要丰富 transport 选 winston。
## Express 集成
```javascript
// 请求日志中间件
app.use((req, res, next) => {
const start = Date.now();
res.on("finish", () => {
logger.info({
method: req.method,
url: req.originalUrl,
status: res.statusCode,
duration: Date.now() - start,
}, "请求完成");
});
next();
});
// 全局错误日志
app.use((err, req, res, next) => {
logger.error({ err, url: req.url }, "未捕获错误");
res.status(500).json({ error: "Internal Server Error" });
});
```
## 结构化日志规范
```javascript
// 好的做法:字段化,方便检索
logger.error({
error: err.message,
stack: err.stack,
userId: req.user?.id,
traceId: req.headers["x-trace-id"],
}, "订单处理失败");
// 避免:把变量拼进字符串
// logger.error("订单处理失败: " + err.message);
```
结构化日志(JSON 字段)方便用 ELK、Loki 等工具检索分析。
## 常见踩坑
### 坑1:日志里打印敏感信息
```javascript
// 避免记录密码、token、身份证等敏感信息
// 脱敏后再记录
logger.info({ phone: maskPhone(phone) }, "注册成功");
```
### 坑2:日志量过大拖垮性能
- 生产环境关闭 debug 级别
- 避免在循环里高频打日志
- 日志异步写入,不阻塞主流程
### 坑3:日志文件无限增长
务必配置日志切割和保留天数,防止磁盘占满。
## 小结
日志记录的要点:分级输出、结构化 JSON、按天切割、敏感信息脱敏。选型上高性能用 pino,功能全面用 winston。配合请求日志中间件和全局错误捕获,排查问题会轻松很多。