Node.js 应用一旦跑到 Ubuntu 生产环境,日志就不只是“能打印出来”这么简单。真正影响排障效率和运维稳定性的,是日志有没有分级、是否便于持久化、文件会不会失控膨胀,以及多进程或多节点之后还能不能统一检索。
这篇文章按从轻到重的部署路径来整理:先看内置 console 适合解决什么问题,再比较 Winston 和 Bunyan 的定位,接着补上日志轮转与 PM2 管理,最后说明什么情况下该把日志送进 ELK。读完之后,你可以根据应用规模判断一套足够用、又不至于过度设计的日志方案。
先用内置 console,适合开发调试阶段
Node.js 自带的 console 模块,是最容易上手的日志入口。像 console.log() 可以输出普通信息,console.error() 用来打印错误,默认直接写到终端,也可以通过重定向保存到文件。
如果你现在只是本地开发、接口联调,或者临时确认请求是否进入某段逻辑,这一层通常已经够用。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
console.log('Received request at /');
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});但它的问题也很直接:
- 没有明确的日志级别控制;
- 不具备统一格式化能力;
- 缺少成熟的持久化与多目标输出方案。
所以,console 更适合作为开发期的基础工具,而不是生产环境里的最终答案。
生产环境怎么选:Winston 还是 Bunyan
进入生产环境后,日志通常至少要满足三件事:分级、可落盘、便于后续分析。这个阶段就该引入第三方日志库了。Node.js 里最常见的两类方案,就是 Winston 和 Bunyan。

Winston:传输方式灵活,适合通用场景
Winston 的特点是功能完整、扩展空间大。它支持控制台、文件、HTTP 等多种传输方式,也支持自定义格式化输出。安装命令如下:
npm install winston一个常见的配置方式如下:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' }),
new winston.transports.Console({ format: winston.format.simple() })
]
});
logger.info('Server started on port 3000');
logger.error('Database connection failed');这套配置的价值在于分流:错误日志可以单独写到 logs/error.log,全部日志写到 logs/combined.log,同时终端还能保留简洁输出。对大多数中小型服务来说,这已经是一套很实用的生产基线。
Bunyan:结构化 JSON 输出,更适合分析链路
如果你的重点不是“怎么打印得更灵活”,而是“怎么让日志更容易被机器消费”,Bunyan 会更合适。它天然强调结构化 JSON 输出,对接 ELK Stack 这类分析平台时会省很多事。
安装命令:
npm install bunyan示例配置如下:
const bunyan = require('bunyan');
const logger = bunyan.createLogger({
name: 'my-app',
level: 'info',
streams: [
{ level: 'info', stream: process.stdout },
{ level: 'error', path: 'logs/app-error.log' }
]
});
logger.info('User logged in', { userId: 123 });
logger.error('Invalid request', { requestId: 'abc123', error: 'Invalid input' });它的优势主要体现在两点:一是字段结构统一,二是后续接入检索、聚合、告警系统时更顺手。如果你的系统已经准备做集中化日志分析,Bunyan 这种“先结构化、后处理”的路线会更自然。
日志轮转必须补上,不然文件迟早失控
无论你选择哪种日志库,只要开始把日志写入文件,就要尽早处理轮转问题。否则单个日志文件会持续变大,最终影响磁盘空间、检索效率,甚至拖慢运维处理。

方案一:用 Ubuntu 自带的 logrotate
如果你希望日志轮转由系统统一接管,logrotate 是最直接的办法。可以为 Node.js 应用创建一个配置文件,比如 /etc/logrotate.d/nodejs-app:
/var/log/nodejs/*.log {
daily
missingok
rotate 7
compress
notifempty
create 0640 root adm
}这份配置表示:
- 按天轮转;
- 缺失日志文件时不报错;
- 保留 7 份历史日志;
- 旧日志压缩保存;
- 空文件不轮转;
- 新文件权限为
0640,属主为root,属组为adm。
配置完可以先手动验证一次:
sudo logrotate -f /etc/logrotate.d/nodejs-app如果测试正常,后续就能由系统自动执行。这种方式适合传统 Ubuntu 服务器部署,配置集中,应用本身也不需要感知轮转细节。
方案二:在 Winston 里直接使用 DailyRotateFile
如果你不想额外维护系统级配置,也可以把轮转逻辑直接放进应用层。Winston 生态里常见的做法,就是使用 winston-daily-rotate-file。
const winston = require('winston');
require('winston-daily-rotate-file');
const transport = new winston.transports.DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
});
const logger = winston.createLogger({ transports: [transport] });这里保留了几个关键信息:
- 文件名按日期拆分,格式是
application-%DATE%.log; datePattern为YYYY-MM-DD;- 归档日志自动压缩;
- 单文件上限为
20m; - 最多保留
14d。
它的优势是部署迁移更简单,应用带着配置走,不用在每台机器上重复维护 logrotate 规则。适合容器化环境,或者由研发团队直接掌控运行配置的项目。
用 PM2 把进程管理和日志管理绑在一起
如果你的 Node.js 服务已经使用 PM2 托管,那么日志管理就不必拆成完全独立的一套。PM2 本身就提供了日志查看与日志文件配置能力,适合希望快速落地生产部署的小团队。

安装与启动
sudo npm install pm2 -g
pm2 start app.js --name my-app这样应用会以 my-app 的名字运行,后续查看日志和运维操作都更直观。
查看日志与配置轮转
常用日志查看命令有两个:
pm2 logs my-app:实时查看日志;pm2 logs my-app --lines 100:查看最近 100 行日志。
如果要进一步配置输出文件、时间格式以及轮转规则,可以使用 ecosystem.config.js:
module.exports = {
apps: [{
name: 'my-app',
script: 'app.js',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
out_file: './logs/out.log',
error_file: './logs/err.log',
merge_logs: true,
log_rotation: true,
log_rotation_interval: '1d',
log_rotation_size: '10M'
}]
};然后用下面的命令启动:
pm2 start ecosystem.config.js这套配置的核心意义,是把进程启动、日志文件位置、时间格式和轮转策略放在同一份文件里维护。对只有几台服务器、但又想减少手工操作的团队来说,这种方式很省心。
什么时候该上 ELK 做集中式日志
当应用进入分布式架构,或者你需要统一检索、统计分析、可视化和告警时,单机日志文件就开始不够用了。这时候该考虑集中式日志平台,常见方案就是 ELK Stack。
以 ELK 为例,落地路径通常包括三步:
- 安装 Elasticsearch、Logstash、Kibana,并参考官方文档完成基础部署。
- 配置 Logstash 接收 Node.js 日志,例如创建
logstash.conf,加入输入插件(如 TCP/UDP)和输出插件(Elasticsearch)。 - 在 Node.js 应用中通过
winston-logstash把日志发送出去。
发送日志的示例配置如下:
const winston = require('winston');
require('winston-logstash');
const logger = winston.createLogger({
transports: [
new winston.transports.Logstash({
port: 5000,
host: 'localhost',
node_name: 'my-app'
})
]
});这里的关键信息很明确:日志被发送到 localhost 的 5000 端口,节点名是 my-app。一旦接入成功,日志就能集中存储并进入分析与可视化链路,适合服务数量多、排障链路长、对告警响应要求更高的系统。
实际部署怎么组合更合适
从实际使用看,不同规模的 Node.js 项目,对日志方案的需求差异很大,没有必要一上来就堆满所有组件。
- 开发和调试阶段:
console基本够用; - 常规生产环境:Winston 配合文件输出,是最常见的起点;
- 需要控盘和保留策略:补上
logrotate或winston-daily-rotate-file; - 希望进程与日志一起托管:加入 PM2;
- 涉及多节点检索、分析、告警:再考虑 ELK。
如果要给出几个比较实用的组合,通常可以这样理解:
- Winston +
logrotate:适合经典 Ubuntu 服务器部署; - PM2 + Winston + DailyRotateFile:适合小团队快速上线并保持可维护性;
- Bunyan 或 Winston 对接 ELK:适合需要结构化分析和集中运维的复杂系统。
日志管理的关键,不在于工具越多越专业,而在于每一层都解决了当前环境里的真实问题:能看清、能保存、不会爆盘、出了故障能快速定位。把这几件事做稳,Node.js 服务的生产可维护性就已经上了一个台阶。







