06 PM2 与 Nginx 日志

1. 先分清三类日志

应用日志
  Express/Pino 输出的业务与错误信息

PM2 日志
  收集应用 stdout/stderr,并记录 PM2 daemon 自身事件

Nginx 日志
  记录公网请求、状态码、代理连接和配置错误

它们观察的是不同层次,不能互相替代。

2. PM2 默认日志在哪里

PM2 官方默认目录:

$HOME/.pm2/logs/

部署用户为 deploy 时通常是:

/home/deploy/.pm2/logs/

应用名为 nloop-api,常见文件类似:

nloop-api-out.log
nloop-api-error.log

实际文件名可能带实例 ID,应以 pm2 show nloop-api 和目录内容为准。

3. stdout 与 stderr

console.log("server started");
console.error("database connection failed");

大致流向:

console.log / Pino info
  → stdout
  → PM2 out log

console.error / Pino error
  → stderr
  → PM2 error log(取决于 logger 输出配置)

如果 Pino 全部以 JSON 输出到 stdout,错误也可能出现在 out log。日志级别属于 JSON 字段,不一定等于操作系统 stderr。

4. 查看 PM2 日志

pm2 logs
pm2 logs nloop-api
pm2 logs nloop-api --lines 200
pm2 logs nloop-api --err
pm2 logs nloop-api --out
pm2 logs nloop-api --lines 200 --nostream

实时追踪日志使用 pm2 logs;只想打印最近内容并退出,使用 --nostream

PM2 daemon 自身问题还可以查看:

$HOME/.pm2/pm2.log

5. ecosystem 中的最小日志配置

第一阶段保留 PM2 默认目录,只加时间:

module.exports = {
  apps: [
    {
      name: "nloop-api",
      script: "./dist/server.js",
      cwd: "/var/www/nloop/current",
      instances: 1,
      exec_mode: "fork",
      time: true,
    },
  ],
};

这样不需要先处理 /var/log 权限。

需要自定义文件时:

{
  out_file: "/var/log/nloop/app-out.log",
  error_file: "/var/log/nloop/app-error.log",
  merge_logs: true,
  time: true,
}

必须先创建目录并授予部署用户明确权限。不要为了省事执行:

chmod -R 777 /var/log/nloop

6. 结构化应用日志

当前项目已经包含 Pino 和 pino-http,可以让日志以数据形式输出:

{
  "level": 30,
  "time": 1787000000000,
  "requestId": "req-123",
  "method": "GET",
  "route": "/api/users",
  "statusCode": 200,
  "responseTime": 18
}

推荐原则:

7. PM2 日志轮转

PM2 默认日志文件会持续增长。官方提供 pm2-logrotate 模块:

pm2 install pm2-logrotate

安装后应查看实际配置:

pm2 conf pm2-logrotate
pm2 list

也可以使用 Linux 原生 logrotate。新人阶段先选择一种方案,不要让 PM2 模块和系统 logrotate 同时对同一文件执行不一致策略。

上线初期至少要做到:

8. pm2 flush 的边界

pm2 flush

会清空 PM2 当前管理的日志。它不是日志轮转方案,也不应该成为磁盘满了之后的日常补救。

执行前确认:

9. Nginx 访问日志和错误日志

可以在 server 中配置:

server {
    access_log /var/log/nginx/nloop-access.log;
    error_log /var/log/nginx/nloop-error.log warn;

    # location ...
}

access log

主要回答:

谁请求了什么
状态码是多少
返回了多少字节
使用什么 User-Agent
请求花了多久(需要格式中包含耗时变量)

error log

主要记录:

10. 增加一个实用访问日志格式

log_format 通常定义在 http 上下文:

log_format api_main
    '$remote_addr - $host [$time_local] '
    '"$request" $status $body_bytes_sent '
    'request_time=$request_time '
    'upstream_time=$upstream_response_time '
    'upstream_status=$upstream_status '
    '"$http_user_agent"';

server 中使用:

access_log /var/log/nginx/nloop-access.log api_main;

两个耗时字段很有帮助:

request_time          Nginx 处理整个请求的时间
upstream_response_time 等待上游 Express 的时间

它们可以帮助判断时间主要消耗在代理上游还是其他阶段。

11. Nginx 日志轮转

Linux 发行版通常已经为 /var/log/nginx/*.log 提供 logrotate 配置。应先检查:

sudo cat /etc/logrotate.d/nginx

Nginx 支持重新打开日志文件;发行版的 logrotate 配置通常会在轮转后通知 Nginx。不要只手工重命名正在写入的文件,然后假设 worker 会自动切换到新文件。

12. 502 的日志排查路线

浏览器收到:

502 Bad Gateway

按顺序检查:

sudo tail -n 100 /var/log/nginx/nloop-error.log
pm2 list
pm2 logs nloop-api --lines 100 --nostream
curl -i http://127.0.0.1:8080/health
ss -lntp | grep 8080

常见原因:

13. 日志隐私和安全

不要记录:

URL query 也可能进入 Nginx access log,所以不要把 Token 和密码放入 URL。

复盘题

  1. PM2 out/error log 与日志级别是什么关系?
  2. 为什么第一版推荐使用 $HOME/.pm2/logs
  3. request_timeupstream_response_time 分别表示什么?
  4. 为什么 pm2 flush 不能代替日志轮转?
  5. Nginx 返回 502 时应先看哪些日志和状态?

官方参考