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.1access-20260716.logaccess.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 > JulMar > 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 计算结果上进行时差换算。

九、经验总结

  1. 时间过滤应转换为时间戳后比较:日志月份为英文缩写,字符串比较遵循字母序,会产生错误结果,gawk + mktime 为可靠方案。
  2. 交付前必须校验:通过 head -1tail -1 核对首尾记录时间,可有效排除提取范围错误。
  3. 空结果不等于异常:错误日志为空表明系统运行正常,如实记录即可。
  4. 必须配置日志切割:避免单文件持续增长导致磁盘耗尽及检索困难。
  5. 按需自定义日志格式:建议提前在格式中加入 $request_time$http_x_forwarded_for,为后续问题排查提供必要信息。
posted @ 2026-07-20 11:53  RK5123153  阅读(25)  评论(0)    收藏  举报