Nginx 日志实战教程:配置、提取与分析
本文系统介绍 Nginx 日志的核心知识与操作方法:日志类型与定位、格式详解与自定义、按时间范围提取、日志切割管理、常用分析命令、导出与传输。
文中命令涉及的路径与日期均为示例,替换为实际值后即可使用。
一、Nginx 日志基础
1.1 日志类型
| 日志 | 作用 | 默认文件名 |
|---|---|---|
| 访问日志(access log) | 记录每一次 HTTP 请求:客户端、时间、请求内容、处理结果 | access.log |
| 错误日志(error log) | 记录 Nginx 自身及请求处理过程中的错误:配置错误、后端连接失败、权限问题等 | error.log |
1.2 日志文件定位
默认路径规律:
- 编译安装:
<安装目录>/logs/,例如/usr/local/nginx/logs/ - 包管理安装(yum/apt):
/var/log/nginx/
定位方法:
# 方法一:查看进程。编译安装的 nginx 通常不在 PATH 中,which 无法找到,
# 可通过 master 进程路径确认安装目录
ps -ef | grep nginx
# 输出示例:nginx: master process /usr/local/nginx/sbin/nginx
# 方法二:查看编译参数中的日志路径
/usr/local/nginx/sbin/nginx -V 2>&1 | tr ' ' '\n' | grep -i log
# 方法三:查看配置文件中的日志指令
grep -rE 'access_log|error_log' /usr/local/nginx/conf/
注意:配置文件中
access_log/error_log全部被#注释时,并不代表日志功能关闭,此时使用编译默认路径(<安装目录>/logs/)。
1.3 确认日志切割情况
ls -lh /usr/local/nginx/logs/
- 目录下仅有
access.log且体积较大 → 日志未切割,历史记录全部保存在该文件中; - 存在
access.log.1、access-20260716.log、access.log.2.gz等文件 → 日志已切割,目标日期的记录可能位于旧文件中,需合并后提取。
二、日志格式详解(log_format)
2.1 默认 combined 格式
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
典型访问日志示例:
10.0.0.21 - - [17/Jul/2026:09:37:07 +0800] "GET /res/icon.ico HTTP/1.1" 304 0 "https://example.cn/" "Mozilla/5.0 ... Chrome/102.0"
字段对照:
| 字段 | 日志中的位置 |
|---|---|
| 源IP | 行首第一个字段($remote_addr) |
| 访问时间 | [17/Jul/2026:09:37:07 +0800]($time_local) |
| 请求方法与路径 | "GET /res/icon.ico HTTP/1.1"($request) |
| 响应码 | 请求引号后的数字($status) |
| 响应大小 | 响应码后的数字($body_bytes_sent) |
| 来源页面 | 倒数第二个引号串($http_referer) |
| UA | 最后一个引号串($http_user_agent) |
2.2 常用日志变量
| 变量 | 含义 |
|---|---|
$remote_addr |
客户端 IP(客户端直连时才为真实 IP) |
$remote_user |
HTTP 基础认证的用户名 |
$time_local |
本地时间 |
$request |
完整请求行(方法 + 路径 + 协议) |
$request_method / $uri / $args |
请求方法 / 路径 / 查询参数 |
$status |
响应码 |
$body_bytes_sent |
发送给客户端的字节数 |
$http_referer |
来源页面 |
$http_user_agent |
客户端 UA |
$http_x_forwarded_for |
代理透传的真实客户端 IP |
$request_time |
请求总耗时(秒,毫秒精度) |
$upstream_response_time |
上游后端响应耗时 |
$request_body |
POST 请求体(默认不记录) |
2.3 自定义日志格式
默认 combined 格式不包含请求耗时与真实客户端 IP,建议按以下方式自定义:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'"$http_x_forwarded_for" rt=$request_time urt=$upstream_response_time';
access_log logs/access.log main;
排查接口问题时,可追加 $request_body 记录 POST 请求体(日志体积会相应增大,且可能包含敏感信息,应评估后开启)。
修改配置后执行 nginx -t && nginx -s reload 使其生效。
2.4 反向代理场景下的真实客户端 IP
现象:日志中源 IP 均为同一网关或代理地址,无法识别真实访问者。
原因:请求经过代理转发,$remote_addr 记录的是代理服务器 IP。
解决方案:在 nginx.conf 的 http 块中配置 real_ip 模块,从 X-Forwarded-For 还原真实 IP:
set_real_ip_from 10.0.0.0/8; # 信任的代理网段,按实际情况修改
real_ip_header X-Forwarded-For;
real_ip_recursive on;
三、access_log 与 error_log 配置
# 按站点/路径分别记录
server {
access_log logs/site1.access.log main;
location /health { access_log off; } # 健康检查不记录日志
}
# 缓冲写入,降低 IO 开销(64k 缓冲,5 秒刷盘)
access_log logs/access.log main buffer=64k flush=5s;
# 写入时压缩(节省磁盘空间,增加少量 CPU 开销)
access_log logs/access.log main gzip;
error_log 日志级别(由低到高):debug | info | notice | warn | error | crit | alert | emerg
error_log logs/error.log warn; # 生产环境建议设置为 warn 或 error
四、日志切割与管理
4.1 切割的必要性
未切割的日志会持续增长(实际环境中存在单文件超过 400MB 的情况),不仅增加磁盘耗尽的风险,也会降低检索效率。切割后的日志可按日期进行管理、归档与清理。
4.2 方式一:logrotate(推荐)
新建 /etc/logrotate.d/nginx:
/usr/local/nginx/logs/*.log {
daily # 每日切割
rotate 30 # 保留 30 份
missingok
notifempty
compress # 旧日志使用 gzip 压缩
delaycompress
sharedscripts
postrotate
[ -f /usr/local/nginx/logs/nginx.pid ] && kill -USR1 $(cat /usr/local/nginx/logs/nginx.pid)
endscript
}
配置完成后可通过 logrotate -d /etc/logrotate.d/nginx 进行演练验证(dry-run)。
4.3 方式二:脚本 + crontab
#!/bin/bash
# cut_nginx_log.sh:每日 0 点执行切割
LOG_DIR=/usr/local/nginx/logs
DATE=$(date -d "yesterday" +%Y%m%d)
mv $LOG_DIR/access.log $LOG_DIR/access-$DATE.log
mv $LOG_DIR/error.log $LOG_DIR/error-$DATE.log
kill -USR1 $(cat $LOG_DIR/nginx.pid) # 通知 nginx 重新打开日志文件
# 配置定时任务
crontab -e
0 0 * * * /bin/bash /root/cut_nginx_log.sh
原理:
mv操作后 nginx 仍持有原文件句柄,会继续向旧文件写入。执行kill -USR1(或nginx -s reopen)使 nginx 重新打开日志文件,后续日志写入新文件。
五、按时间范围提取日志
5.1 常见误区:字符串比较方式不可取
# 错误示例:直接使用字符串比较
awk '$4 >= "[16/Jul/2026:09:00:00" && $4 <= "[17/Jul/2026:12:00:00"' access.log
日志中的月份为英文缩写(Jan/Feb/…/Dec),字符串比较遵循字母序而非时间序,例如 Sep > Jul、Mar > Jul。该方式会将其他年份、其他月份的记录混入提取结果,且结果条数往往无明显异常,不易察觉。若提取结果首条记录的年份或月份与预期不符,即可判定存在该问题。
5.2 推荐方式:gawk + mktime 时间戳比较
gawk 'BEGIN{
m["Jan"]=1;m["Feb"]=2;m["Mar"]=3;m["Apr"]=4;m["May"]=5;m["Jun"]=6;
m["Jul"]=7;m["Aug"]=8;m["Sep"]=9;m["Oct"]=10;m["Nov"]=11;m["Dec"]=12;
s=mktime("2026 07 17 00 00 00"); # 起始时间(按需修改)
e=mktime("2026 07 18 23 59 59") # 结束时间(按需修改)
}
{
split(substr($4,2), a, "[/:]");
t=mktime(a[3]" "m[a[2]]" "a[1]" "a[4]" "a[5]" "a[6]);
if (t>=s && t<=e) print
}' /usr/local/nginx/logs/access.log > /tmp/access_out.log
使用时仅需修改 BEGIN 块中两个 mktime 的日期参数。mktime 按服务器本地时区解析,日志时区与服务器时区一致时无需额外处理。
5.3 错误日志的提取
error.log 的时间格式为 2026/07/17 09:37:07,与访问日志不同,按日期前缀过滤即可:
grep -E '^2026/07/(17|18)' /usr/local/nginx/logs/error.log > /tmp/error_out.log
error.log 仅在发生错误时写入。提取结果为 0 行,说明对应时段无错误发生,属于正常现象。
5.4 已切割日志的合并提取
目标时间段横跨多个日志文件时,需按时间从旧到新的顺序合并后再提取:
# 已压缩的旧文件使用 zcat 解压;注意文件顺序:最旧的在前
(zcat /usr/local/nginx/logs/access.log.2.gz 2>/dev/null; \
cat /usr/local/nginx/logs/access.log.1 \
/usr/local/nginx/logs/access.log) | gawk '5.2节脚本' > /tmp/access_out.log
5.5 提取结果校验
提取完成后必须执行以下校验:
wc -l /tmp/access_out.log # 记录总数
head -1 /tmp/access_out.log # 首条记录时间应不早于起始时间
tail -1 /tmp/access_out.log # 末条记录时间应不晚于结束时间
仅当首尾记录时间均落在目标范围内时,提取结果方为有效。未经校验的数据可能存在时间范围错误,不应直接交付使用。
六、日志分析常用命令
以下命令基于默认 combined 格式,以提取结果 /tmp/access_out.log 为操作对象:
# 总请求数、独立 IP 数
wc -l /tmp/access_out.log
awk '{print $1}' /tmp/access_out.log | sort -u | wc -l
# 响应码分布统计
awk '{print $9}' /tmp/access_out.log | sort | uniq -c | sort -rn
# 请求量 TOP 10 来源 IP
awk '{print $1}' /tmp/access_out.log | sort | uniq -c | sort -rn | head
# 请求量 TOP 10 访问路径
awk -F'"' '{split($2,a," "); print a[2]}' /tmp/access_out.log | sort | uniq -c | sort -rn | head
# 每分钟请求量统计(用于定位流量突增时间点)
awk -F'[' '{print substr($2,1,17)}' /tmp/access_out.log | sort | uniq -c
# 提取全部 4xx/5xx 请求(异常请求排查)
awk '$9 ~ /^[45]/' /tmp/access_out.log
# 按路径过滤(如上传、后台管理等敏感路径)
grep -E '/upload|/admin' /tmp/access_out.log
# 慢请求 TOP 10(要求 log_format 中已配置 rt=$request_time)
awk -F'rt=' 'NF>1{split($2,a," "); print a[1], $0}' /tmp/access_out.log | sort -rn | head
七、导出与传输
7.1 转换为 CSV 格式
需要将日志整理为表格时,可通过以下命令生成"源IP、时间、路径、响应码、UA"五列的 CSV 文件:
awk -F'"' '{
split($1,a," "); split($2,b," "); split($3,c," ");
gsub(/\[|\]/,"",a[4]);
print a[1] "," a[4] "," b[2] "," c[2] "," $6
}' /tmp/access_out.log > /tmp/access_out.csv
使用 Excel 打开时如出现中文乱码,可通过"数据 → 自文本/CSV"导入并选择 UTF-8 编码解决。
7.2 打包与下载
tar czf /tmp/nginx_logs.tar.gz -C /tmp access_out.log error_out.log
ls -lh /tmp/nginx_logs.tar.gz
# 终端支持 lrzsz 时可直接下载:
sz /tmp/nginx_logs.tar.gz
# 也可通过 WinSCP / SFTP 等工具下载
日志为纯文本格式,gzip 压缩比约为 10:1。读取正在写入的日志文件是安全的,不会影响 nginx 正常运行。
八、常见问题
Q1:error.log 长期未更新,是否说明 nginx 异常?
不是。error.log 仅在发生错误时写入,长期未更新说明服务运行正常。
Q2:日志中所有源 IP 均为同一内网地址,原因是什么?
请求经过网关或代理转发,$remote_addr 记录的是代理服务器 IP。可参考 2.4 节配置 real_ip 模块还原真实客户端 IP。
Q3:删除大体积日志文件后,磁盘空间未释放?
nginx 进程仍持有该文件的句柄。可通过 lsof | grep deleted | grep nginx 确认,随后执行 kill -USR1 $(cat nginx.pid) 或重启 nginx 释放空间。规范做法是先切割(mv + USR1),再删除旧文件。
Q4:$request_body 无记录内容?
仅 POST/PUT 请求且存在请求体时该变量才有值,同时需在 log_format 中显式配置该变量。
Q5:mktime 提取结果与时间范围相差 8 小时?
通常由日志时区(如 +0800)与服务器时区不一致导致。应统一时区配置,或在 mktime 计算结果上进行时差换算。
九、经验总结
- 时间过滤应转换为时间戳后比较:日志月份为英文缩写,字符串比较遵循字母序,会产生错误结果,
gawk + mktime为可靠方案。 - 交付前必须校验:通过
head -1与tail -1核对首尾记录时间,可有效排除提取范围错误。 - 空结果不等于异常:错误日志为空表明系统运行正常,如实记录即可。
- 必须配置日志切割:避免单文件持续增长导致磁盘耗尽及检索困难。
- 按需自定义日志格式:建议提前在格式中加入
$request_time、$http_x_forwarded_for,为后续问题排查提供必要信息。

浙公网安备 33010602011771号