日志与轮转
定位:日志是排查的第一现场。
搞清楚"谁在打印 → 打到哪 → 怎么看 → 怎么轮转不撑爆磁盘",生产运维就稳了一半。
日志从哪来、到哪去
PM2 捕获每个被管理进程的 stdout(标准输出) 与 stderr(标准错误),做三件事:
- 实时透传:
pm2 logs时你能看到; - 内存缓冲:保留最近若干行,供
pm2 logs快速回看; - 落盘:默认写入
~/.pm2/logs/下的文件;也可用 CLI-l/--log <path>或 ecosystemlog_file把 stdout+stderr 合并写单个文件。
因此:应用只要正常 console.log / console.error / 写 stdout,PM2 就能拿到;如果你在代码里自己写文件日志(如 winston 配了 file transport),那跟 PM2 无关,走的是你自己的路径。
默认文件(可用自定义覆盖):
| 输出 | 默认路径 | 内容 |
|---|---|---|
| stdout | ~/.pm2/logs/<name>-out-<pid>.log | console.log 等普通输出 |
| stderr | ~/.pm2/logs/<name>-error-<pid>.log | console.error、未捕获异常的堆栈 |
官方默认文件名带进程标识(
<pid>后缀),因此多实例 / 多次重启可能产生多个文件;想让同一应用始终写同一文件,用merge_logs或自定义out_file/error_file。实际路径以pm2 describe <name>输出的out log path/error log path为准。PM2 自己的运行日志在~/.pm2/pm2.log——那不是应用日志。
自定义路径(ecosystem,见 配置文件):
{
name: 'api',
script: 'app.js',
out_file: '/var/log/myapp/out.log',
error_file: '/var/log/myapp/err.log',
}⚠️ 自定义路径的上级目录必须先手动创建(如
mkdir -p /var/log/myapp),PM2 不会帮你建目录。
查看日志
pm2 logs # 实时跟随所有进程(Ctrl+C 退出,不影响进程)
pm2 logs api # 只看名为 api 的进程
pm2 logs api --lines 200 # 先回放最近 200 行,再进入跟随(默认只回放最后 15 行)
pm2 logs api --err # 只看 stderr
pm2 logs api --out # 只看 stdout
pm2 logs api --timestamp # 显示时附带时间戳(默认格式 YYYY-MM-DD-HH:mm:ss)
pm2 logs api --json # JSON 结构化输出(供脚本/日志采集)
pm2 logs api --nostream # 打印缓冲后直接退出(不跟随),适合脚本里取日志
pm2 logs 0 # 也接受进程 id提醒:
pm2 logs里的内容可能来自内存缓冲,回放行数有限;想保留现场就去翻磁盘文件(路径以pm2 describe <name>的 out/error log path 为准)。
时间戳:time 与 log_date_format
- 启动时加
--time,或在 ecosystem 中time: true:之后落盘与显示的每行日志前自动加时间戳(YYYY-MM-DD HH:mm:ss GMT+0800这类格式)。 - 旧版惯用的
log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS'仍常见于存量配置;新项目直接用time(两者关系与取舍以官方文档为准)。
时间基准是系统时区;容器里若看到 UTC 时间,请在进程环境里配
TZ(见 故障排查与最佳实践)。
多实例/多应用的日志组织
| 场景 | 表现 | 想要"合并"怎么办 |
|---|---|---|
| 单应用单实例 | <name>-out-<pid>.log / <name>-error-<pid>.log | —— |
| 单应用多实例(cluster) | 默认各自带 pid 后缀、独立成文件 | ecosystem 设 merge_logs: true(去 pid 后缀,合写同一文件) |
| 多应用 | 每个 name 各自文件,互不干扰 | 各配各的,无需合并 |
判断实际文件:
pm2 describe <name>会直接给出 out/error 日志路径,也可ls ~/.pm2/logs查看;多实例要合写请配merge_logs。
清空 / 重新打开
pm2 flush # 清空 PM2 当前持有的应用日志(清后 pm2 logs 不再回放旧内容)
pm2 reloadLogs # 让进程重新打开日志文件句柄(配合系统 logrotate 用)删除日志文件要小心:直接
rm正在被写的日志,进程还握着旧 inode,tail会看到"文件没了但还在增长",且空间不释放。规范做法:让轮转工具(下节)改名/压缩,而不是手删。 想回收磁盘空间请用轮转/清理历史文件;flush清的是 PM2 持有的日志视图/缓冲,不等于删除磁盘文件。
日志轮转:不撑爆磁盘
方案 A:pm2-logrotate 模块(官方推荐,纯 PM2 内实现)
pm2 install pm2-logrotate # 安装
pm2 get pm2-logrotate # 查看配置
pm2 set pm2-logrotate:max_size 10M # 改配置:超过 10MB 就轮转常用配置项(pm2 set <键> <值> 修改;括号内为常见默认值,以 pm2 get pm2-logrotate 实际输出为准):
| 键 | 作用 |
|---|---|
max_size | 单文件超过该大小触发轮转(默认 10M,可写 10K/10M/10G) |
retain | 保留的轮转文件个数(默认 30,超出删除最旧的) |
compress | 是否 gzip 压缩旧文件(默认 false) |
dateFormat | 轮转文件名的日期格式 |
workerInterval | 后台检查间隔(秒) |
rotateInterval | 按 cron 时间每天强制轮转一次(默认 '0 0 * * *',每日 0 点) |
TZ | 轮转时间戳使用的时区 |
rotateModule | 是否也轮转 PM2 模块自身的日志 |
它按 size(超限) 或 时间(每天) 触发,属于"PM2 内部自治",不依赖系统 cron/logrotate,适合大多数单机场景。
方案 B:原生 pm2 logrotate(一键生成系统配置)
如果你想要系统级 logrotate(而不是 PM2 内部模块),官方提供了生成器:
sudo pm2 logrotate -u deploy它会在 /etc/logrotate.d/pm2-user 写入覆盖 ~/.pm2/pm2.log 与 ~/.pm2/logs/*.log 的配置(官方模板:每周轮转、保留 12 份、compress/delaycompress/copytruncate、create 0640 user user)。copytruncate 意味着无需重启进程即可轮转,简单稳妥,适合"公司统一走系统 logrotate"的团队。
方案 C:手工配置系统 logrotate(自定义时)
若你公司统一用系统 logrotate 管理 /var/log,可以把自己的 out_file/error_file 指到 /var/log/…,然后写一个 logrotate 配置,并在 postrotate 里调 pm2 reloadLogs 让进程重新打开新文件:
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 7
compress
missingok
notifempty
postrotate
/usr/bin/sudo -u deploy pm2 reloadLogs
endscript
}生产实践建议
- 应用只往 stdout/stderr 打日志(结构化 JSON 更好),文件持久化交给 PM2/采集器——避免在代码里维护日志文件。
- 日志集中化:filebeat/fluent-bit 等 tail
~/.pm2/logs/或你的/var/log路径送 Elasticsearch/Loki;别等出事了才 ssh 上去翻。 - 记得轮转:宁可多 rotate 几次,也不要让一个日志文件长到几个 GB。
pm2 flush清的是 PM2 持有的日志(logs 视图/缓冲),不负责回收磁盘空间;磁盘清理交给轮转。- 排查"为什么没日志"顺序:前台
node app.js复现 → 确认打印走了 stdout → 确认没被自定义 out_file 指走 → 看~/.pm2/pm2.log。
参考链接
- 官方日志管理:https://pm2.keymetrics.io/docs/usage/log-management/
- pm2-logrotate 模块:https://github.com/pm2-hive/pm2-logrotate
