Nginx日志分析工具

文章导读
判断该用哪种工具分析 Nginx 访问日志,先不要看工具对比,应先确认两件事:access_log 的实际路径,以及 log_format 里有没有耗时字段。默认 combined 格式只有状态码、字节数和 UA,能回答“谁在访问、哪些 URL 出错”,但回答不了“慢在 Nginx 还是慢在后端”;工具能给出什么,取决于这两个前提。
📋 目录
  1. A 日志分析:先把能核对的事实摆清楚
  2. B 日志分析最容易搞错的一点
  3. C 日志分析:一个照着就能做完的做法
  4. D 日志分析:怎么验证结果对不对
A A

判断该用哪种工具分析 Nginx 访问日志,先不要看工具对比,应先确认两件事:access_log 的实际路径,以及 log_format 里有没有耗时字段。默认 combined 格式只有状态码、字节数和 UA,能回答“谁在访问、哪些 URL 出错”,但回答不了“慢在 Nginx 还是慢在后端”;工具能给出什么,取决于这两个前提。

先用 nginx -T 核出 log_format 与 access_log 路径,再用 awk 做状态码和慢请求初筛,最后用 goaccess 出报表,并与行数、5xx 计数交叉核对。默认 combined 格式没有 $request_time 和 $upstream_response_time,区分不了 Nginx 自身慢与后端慢,需要补字段并 reload。日志轮转、gz 归档、按固定字段位置取值,是这类分析最常见的误差来源。

日志分析:先把能核对的事实摆清楚

分析对象通常是两个文件:access.log 记录每条请求,error.log 记录 Nginx 自身的报错和 upstream 异常。先确认它们在哪里、按什么格式写:

sudo nginx -T 2>/dev/null | grep -nE 'log_format|access_log|error_log'
sudo tail -n 3 /var/log/nginx/access.log
sudo awk '{print NF}' /var/log/nginx/access.log | sort -u | head

第一条命令打印合并后的运行配置,从输出里能直接看到 log_format 定义了哪些变量、access_log 指向哪个文件;如果 access_log 用变量或 include 引入,要顺着 include 的文件继续看。第二条看真实一行的样子,确认时间格式、是否带 rt= 这类自定义字段。第三条看字段数:同一份日志里 NF 应当稳定,出现多个不同值说明有不同格式的内容混写进同一个文件,后面对字段位置做假设就会出错。

还要确认日志会不会被轮转移走:ls -l /var/log/nginx/ 看有没有 access.log.1、.gz 文件,/etc/logrotate.d/nginx 看轮转周期。只读 access.log 得到的观察只覆盖当前这个文件的时间范围,不要当成全量。

日志分析最容易搞错的一点

最常见的误解是把 $request_time 当成后端耗时。实际上 $request_time 是 Nginx 从收到第一个字节到发完响应的总时间,包含客户端慢读;$upstream_response_time 才是转发到后端的往返时间。两个值一起看才有意义:request_time 大而 upstream_response_time 小,多半出在客户端网络或大文件下传;两个都大,才更可能是后端慢。

更前置的问题是默认 combined 格式根本没有这两个字段。判断分界只有一个动作:查配置里有没有定义,再看日志里有没有对应字段。

sudo nginx -T 2>/dev/null | grep -n 'request_time'
sudo grep -m1 -o 'rt=[^ ]*' /var/log/nginx/access.log

第一条有输出、第二条也能抓到 rt=,才可以用耗时排序;两条都没有输出,说明当前日志只能做状态码和流量统计,想做耗时分析必须先改 log_format。改格式后只有新写入的行才带字段,历史日志补不回来,这一点在说明结果范围时要写清楚。

Nginx日志分析工具

日志分析:一个照着就能做完的做法

如果确认需要耗时字段,建议新增一个独立格式,不要直接改被其他配置引用的旧名字:

log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" rt=$request_time '
                    'urt=$upstream_response_time uh=$upstream_addr';

在对应 server 或 http 块的 access_log 里引用 main_ext,然后 sudo nginx -t 通过后执行 sudo nginx -s reload。reload 之后新请求才会按新格式写入日志,验证方式是再执行一次 sudo tail -n 1 /var/log/nginx/access.log,能看到 rt= 字段才算生效。

接着按顺序做三步筛选。第一步看整体状态码分布,正常站点 2xx/3xx 应占多数:

sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

第二步定位高频 URL 和 5xx 来源:

sudo awk '$9 ~ /^5/ {print $9, $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

第三步按耗时排序,只保留 rt 超过 2 秒的请求:

sudo awk '{for (i=1; i<=NF; i++) {
  if ($i ~ /^rt=/)  rt  = substr($i, 4)
  if ($i ~ /^urt=/) urt = substr($i, 5)
}
if (rt+0 > 2) print rt, urt, $7, $1}' /var/log/nginx/access.log | sort -rn | head -30

$7 是按 combined 字段顺序取的 URL,如果自定义格式里 $request 换了位置,要改成实际序号,或者干脆把 URL 也用 key=value 写进日志。这一步的输出应当是 rt 从大到小排列,第二列 urt 若是 “-”,表示请求没有走 upstream(静态文件或直接返回),这类慢与后端无关。

Nginx日志分析工具

需要出报表时再上 goaccess,它适合把同一份日志渲染成可翻看的 HTML 面板:

goaccess --version
sudo goaccess /var/log/nginx/access.log --log-format=COMBINED \
  --date-format=%d/%b/%Y --time-format=%T -o /tmp/report.html

用 main_ext 这类自定义格式时,--log-format 要按字段顺序改写,不能沿用 COMBINED。不同版本对参数名和面板字段的处理有差异,具体支持哪些参数以本机 goaccess --help 的输出为准。

日志分析:怎么验证结果对不对

先做行数核对:wc -l /var/log/nginx/access.log 的结果应当与 goaccess 面板里的 Total Requests 接近,差值是空行或解析失败的行。差距明显时,多半是 --log-format 与真实格式不匹配。

再做 5xx 计数核对,用两种不同写法互相比对:

sudo awk '$9 ~ /^5/ {c++} END {print c+0}' /var/log/nginx/access.log
sudo grep -c ' 5[0-9][0-9] ' /var/log/nginx/access.log

两条命令的数字应基本一致,少量差异通常来自 UA 或 referer 字符串里恰好出现类似 “ 500 ” 的片段被 grep 匹配到。如果差出很多,说明 awk 里 $9 并不是状态码,字段位置要重新确认,此时可以拿 goaccess 面板上 Status Codes 一栏的数字做第三方对照。

正常结果的样子:状态码分布里 2xx/3xx 为主,4xx 集中在少数几个 URL(常见是 favicon、扫描路径),5xx 数量少且集中在个别 upstream;耗时排序里 rt 分布连续,没有大量集中在同一个整数秒上。如果 5xx 数量与 error.log 里 upstream 报错的条数大致对得上,说明两份日志指向同一时段、同一实例,可以继续往下排查;对不上就先确认是否存在多实例或多份日志文件,不要急着改配置。