FORMA

访问日志与错误日志

日志是 Nginx 排错的“第一现场”。一个优秀的日志策略既能完整保留请求上下文,又不会撑爆磁盘。下面从格式定制、存储轮转、错误分级三个维度详细讲解。

一、日志格式定制(log_format

默认的 combined 格式包含基本字段,但往往不够用。你需要通过自定义 log_format 组合变量,记录下真实客户端 IP、上游响应时间、负载均衡节点等关键信息。

1. 语法与使用

nginx
http {
    log_format  main_ext  '$remote_addr - $remote_user [$time_local] '
                          '"$request" $status $body_bytes_sent '
                          '"$http_referer" "$http_user_agent" '
                          'rt=$request_time uct=$upstream_connect_time '
                          'uht=$upstream_header_time urt=$upstream_response_time '
                          'upstream_addr=$upstream_addr';

    access_log  /var/log/nginx/access.log  main_ext  buffer=64k flush=5s;
}
  • log_format 必须在 http 块中定义,之后在 access_log 指令中引用指定格式。
  • 每个变量之间有空格,末尾可用 \ 换行续写。

2. 常用变量速查

变量名含义注意事项
$remote_addr直接连接的客户端 IP(可能是代理服务器的 IP)要获取真实 IP,需配合 real_ip_header 或记录 $http_x_forwarded_for
$http_x_forwarded_for代理链中原始客户端的 IP 列表,通常取第一个该头可被客户端伪造,须在可信代理处清除并追加
$request原始的请求行,如 GET /index.html HTTP/1.1包含方法和协议版本
$status响应状态码
$body_bytes_sent发送给客户端的 Body 字节数,不含响应头
$request_time从接收客户端第一个字节到发送完最后一个字节的总耗时(秒)包含网络和 Nginx 内部处理时间
$upstream_connect_time与上游建立连接耗时(秒)需要反向代理模块
$upstream_header_time从上游接收到响应头的耗时(秒)同上
$upstream_response_time从上游接收完整响应的总耗时(秒)同上
$upstream_addr上游服务器的地址和端口记录请求实际发给了哪个后端,用于排查负载不均
$upstream_status上游返回的状态码判断后端是否健康
$http_referer请求头 Referer
$http_user_agent请求头 User-Agent
$ssl_cipher / $ssl_protocol当前连接使用的加密套件和协议排查 SSL 相关问题

记录真实 IP 的最佳实践:在反向代理处设置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Real-IP $remote_addr;,然后在日志中记录 $http_x_forwarded_for 或使用 real_ip_header 指令覆盖 $remote_addr。这样即使经过多层代理,也能找到原始客户端。

3. 示例:排查性能瓶颈的增强日志

nginx
log_format perf '$remote_addr [$time_local] '
                '"$request" $status $body_bytes_sent '
                'ref="$http_referer" '
                'rt=$request_time '
                'ups_resp_time=$upstream_response_time '
                'ups_addr=$upstream_addr '
                'ups_status=$upstream_status';

通过 rtups_resp_time 的对比,可以快速判断耗时究竟在 Nginx 本身还是上游服务。

二、日志路径与轮转

1. access_logerror_log 配置

访问日志 access_log
可用在 http, server, location 上下文,支持以下功能:

nginx
access_log /var/log/nginx/access.log main_ext;           # 基本用法
access_log /var/log/nginx/access.log main_ext buffer=64k flush=5s;  # 缓冲与刷新
access_log /var/log/nginx/access.log main_ext gzip=9;    # gzip 压缩(需编译支持)
access_log off;                                           # 关闭日志(提升性能)
  • buffer:日志先写入内存缓冲区,达到大小或到达 flush 时间后才刷入磁盘,减少 I/O。
  • gzip:使用 zlib 动态压缩,适合大流量站点,注意对 CPU 的影响。
  • 多日志:可以用不同格式记录不同类型的请求,例如限制日志只记录 5xx 错误:
    nginx
    map $status $loggable {
        ~^[5]  1;
        default 0;
    }
    access_log /var/log/nginx/error_traffic.log main_ext if=$loggable;
    

错误日志 error_log
记录服务器运行期间的错误、警告及调试信息,支持 main, http, server, location 多级覆盖。

nginx
error_log /var/log/nginx/error.log warn;
  • 支持输出到文件、stderr 或 syslog。
  • 级别越宽泛(如 debug),信息越多,但性能开销越大。

2. 日志轮转:防止磁盘被吃满

Nginx 本身不会自动切割日志,需借助外部工具。

(1) 使用 logrotate

这是 Linux 下最标准的方式。创建一个配置文件 /etc/logrotate.d/nginx

text
/var/log/nginx/*.log {
    daily                    # 每天切割
    missingok                # 日志文件不存在时不报错
    rotate 30                # 保留 30 份
    compress                 # 压缩旧日志
    delaycompress            # 延迟压缩,让刚切割的文件不被压缩(方便查看)
    notifempty               # 日志为空则不切割
    create 640 nginx adm     # 创建新文件的权限和属主
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}

关键点:

  • postrotate 中的 kill -USR1 通知 Nginx 重新打开日志文件。Nginx 收到 USR1 信号后,会关闭并重新打开所有日志文件,避免写入被删除的文件描述符。
  • 可以通过执行 logrotate -d /etc/logrotate.d/nginx 进行调试测试。
  • 实际执行:logrotate -f /etc/logrotate.d/nginx 可强制立即切割一次。
(2) 使用 cron 脚本(不推荐,除非无 logrotate)

可以写一个简单的 bash 脚本,利用 mvkill -USR1,但需要处理好竞争和清空问题,logrotate 更可靠。

3. 日志管理高级建议

  • 搭配 PID 文件:确保 pid /run/nginx.pid; 路径正确。
  • 如果 Nginx 运行在容器中,通常将日志重定向到标准输出(/dev/stdout/dev/stderr),由容器日志驱动统一收集和轮转。

三、错误日志级别

Nginx 错误日志级别从低到高分为:debug, info, notice, warn, error, crit, alert, emerg。设置越高,记录越少。

级别说明
debug最详细,包含内部处理流程、正则匹配等,用于开发调试
info一般信息,如配置加载、worker 进程启动等
notice需要注意但非错误的事件,如重载配置、优雅关闭
warn警告信息,如客户端超时、某些限制值被触及
error处理请求时的错误,如 500 错误、上游连接失败、SSL 握手失败
crit较高严重级问题,如 socket 创建失败、共享内存不足
alert需要立即动作的严重问题
emerg系统级不可用,如内存分配失败

配置示例

nginx
error_log /var/log/nginx/error.log warn;   # 生产环境推荐

部分模块的调试信息(如 rewrite)需在编译时指定 --with-debug,并在运行时使用 error_log 设置 debug 级别配合 debug_connection 过滤。

何时开启 debug 日志?

仅在以下场景短暂启用,且最好针对特定 IP

  • 复杂的 rewritelocation 不按预期匹配,需要知道内部跳转细节。
  • 排查反向代理缓存不生效的问题。
  • SSL 握手失败,需要了解具体算法协商过程。
  • 第三方模块的行为异常。

开启方法(假设已经编译了 debug 支持):

nginx
error_log /var/log/nginx/debug.log debug;   # 全局开启,极耗性能,不推荐

# 更好的方式:仅记录特定客户端的调试信息
events {
    debug_connection 192.168.1.100;   # 仅对该 IP 输出 debug 日志
    debug_connection 10.0.0.0/8;
}
  • debug_connection 需配合 error_log 级别设为 debug 起效。
  • 调试结束后务必立即关闭或恢复为 warn,并清理 debug 日志文件,否则磁盘和 CPU 会迅速耗尽。

把格式设得恰到好处,将轮转配置到位,并熟练掌握错误日志级别的切换,你的 Nginx 就能始终保持“透明”——任何请求的来龙去脉、任何错误的蛛丝马迹都有据可查。这既是运维的基本功,也是快速止损的关键能力。

参考文献

资料说明
nginx 文档官方
运维导读学习路径