Skip to content

日志与轮转

定位:日志是排查的第一现场。
搞清楚"谁在打印 → 打到哪 → 怎么看 → 怎么轮转不撑爆磁盘",生产运维就稳了一半。

日志从哪来、到哪去

PM2 捕获每个被管理进程的 stdout(标准输出)stderr(标准错误),做三件事:

  1. 实时透传pm2 logs 时你能看到;
  2. 内存缓冲:保留最近若干行,供 pm2 logs 快速回看;
  3. 落盘:默认写入 ~/.pm2/logs/ 下的文件;也可用 CLI -l/--log <path> 或 ecosystem log_file 把 stdout+stderr 合并写单个文件。

因此:应用只要正常 console.log / console.error / 写 stdout,PM2 就能拿到;如果你在代码里自己写文件日志(如 winston 配了 file transport),那跟 PM2 无关,走的是你自己的路径。

默认文件(可用自定义覆盖):

输出默认路径内容
stdout~/.pm2/logs/<name>-out-<pid>.logconsole.log 等普通输出
stderr~/.pm2/logs/<name>-error-<pid>.logconsole.error、未捕获异常的堆栈

官方默认文件名带进程标识(<pid> 后缀),因此多实例 / 多次重启可能产生多个文件;想让同一应用始终写同一文件,用 merge_logs 或自定义 out_file/error_file。实际路径以 pm2 describe <name> 输出的 out log path / error log path 为准。PM2 自己的运行日志在 ~/.pm2/pm2.log——那不是应用日志

自定义路径(ecosystem,见 配置文件):

js
{
  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 不会帮你建目录。

查看日志

bash
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 为准)。

时间戳:timelog_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

清空 / 重新打开

bash
pm2 flush        # 清空 PM2 当前持有的应用日志(清后 pm2 logs 不再回放旧内容)
pm2 reloadLogs   # 让进程重新打开日志文件句柄(配合系统 logrotate 用)

删除日志文件要小心:直接 rm 正在被写的日志,进程还握着旧 inode,tail 会看到"文件没了但还在增长",且空间不释放。规范做法:让轮转工具(下节)改名/压缩,而不是手删。 想回收磁盘空间请用轮转/清理历史文件;flush 清的是 PM2 持有的日志视图/缓冲,不等于删除磁盘文件。

日志轮转:不撑爆磁盘

方案 A:pm2-logrotate 模块(官方推荐,纯 PM2 内实现)

bash
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 内部模块),官方提供了生成器:

bash
sudo pm2 logrotate -u deploy

它会在 /etc/logrotate.d/pm2-user 写入覆盖 ~/.pm2/pm2.log~/.pm2/logs/*.log 的配置(官方模板:每周轮转、保留 12 份、compress/delaycompress/copytruncatecreate 0640 user user)。copytruncate 意味着无需重启进程即可轮转,简单稳妥,适合"公司统一走系统 logrotate"的团队。

方案 C:手工配置系统 logrotate(自定义时)

若你公司统一用系统 logrotate 管理 /var/log,可以把自己的 out_file/error_file 指到 /var/log/…,然后写一个 logrotate 配置,并在 postrotate 里调 pm2 reloadLogs 让进程重新打开新文件:

text
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    postrotate
        /usr/bin/sudo -u deploy pm2 reloadLogs
    endscript
}

生产实践建议

  1. 应用只往 stdout/stderr 打日志(结构化 JSON 更好),文件持久化交给 PM2/采集器——避免在代码里维护日志文件。
  2. 日志集中化:filebeat/fluent-bit 等 tail ~/.pm2/logs/ 或你的 /var/log 路径送 Elasticsearch/Loki;别等出事了才 ssh 上去翻。
  3. 记得轮转:宁可多 rotate 几次,也不要让一个日志文件长到几个 GB。
  4. pm2 flush 清的是 PM2 持有的日志(logs 视图/缓冲),不负责回收磁盘空间;磁盘清理交给轮转。
  5. 排查"为什么没日志"顺序:前台 node app.js 复现 → 确认打印走了 stdout → 确认没被自定义 out_file 指走 → 看 ~/.pm2/pm2.log

参考链接