Nginx的访问日志记录了每一个请求的关键信息,是排查线上问题、分析流量分布的重要依据。而日志能记录哪些内容、以什么格式呈现,完全取决于log_format指令的配置。很多人拿到一份别人写好的日志格式配置,却看不懂里面的变量含义,出了问题想加个字段又不知道从哪下手。这篇文章就把log_format的语法、位置和常用参数一次讲清楚,并给出可以直接复制使用的配置模板。

log_format的语法与配置位置
log_format是Nginx的http段指令,只能写在http块中,不能放到server或location里。它的基本语法如下:
log_format name [escape=default|json|none] string ...;
其中name是格式的名称,后面跟一段由变量和普通字符组成的字符串。同一个Nginx可以定义多个格式,在access_log指令中通过名称引用。例如:
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
}这里定义了一个名为main的格式,并在access_log中引用它。需要注意两个细节:一是格式字符串通常用单引号包裹,变量用$符号引用;二是如果格式太长想换行写,每一段都要独立用引号包起来,Nginx会自动拼接。
escape参数从1.11.0版本开始支持,默认值是default,会把变量值中的特殊字符转义为十六进制形式,避免日志被注入破坏。如果希望日志输出为合法的JSON,建议使用escape=json,它会按JSON规范转义引号、换行符等字符,这对后续用日志系统解析非常友好。
常用参数变量详解
log_format中可用的变量非常多,下面按用途分类列出最常用的一批,理解这些基本能覆盖日常排查的绝大多数场景。
第一类是客户端与请求信息。$remote_addr记录客户端IP,如果前面有CDN或负载均衡,这个值可能是代理的IP,真实客户端IP需要看$http_x_forwarded_for。$remote_user是经过Basic认证的用户名,没有认证时为空。$time_local是本地时间,$request是完整的请求行,包含方法、URI和协议版本。$request_method和$request_uri则是拆开的方法和原始URI,做统计时用它们更方便。
第二类是响应信息。$status是响应状态码,$body_bytes_sent是发送给客户端的响应体字节数,不含响应头。如果想统计总流量,可以用$bytes_sent,它包含头部的总传输量。另一个容易混淆的变量是$request_length,它表示请求的总长度,包含请求行、请求头和请求体,常用来排查大请求。
第三类是性能相关变量,也是排查慢请求最关键的一组:
$request_time:请求的总耗时,从收到客户端第一个字节到发送完响应,单位秒,精确到毫秒。$upstream_response_time:后端 upstream 处理请求耗费的时间,如果请求经过多次重试,会依次列出每个后端的时间。$upstream_connect_time:与后端建立连接的时间,如果这个值偏高,说明后端连接存在问题。$upstream_addr:实际处理请求的后端地址和端口,排查负载不均时很有用。$upstream_status:后端返回的状态码,可能与最终返回给客户端的$status不同。
第四类是请求头相关变量。任何请求头都可以通过$http_名字的形式引用,例如$http_user_agent对应User-Agent,$http_referer对应Referer,$http_host对应Host。同理,响应头可以用$sent_http_名字的形式引用,比如$sent_http_content_type。此外$gzip_ratio可以记录压缩率,方便观察gzip效果。
实用的JSON日志格式配置
传统文本格式对人类阅读友好,但机器解析时要处理各种分隔符和转义问题。如果日志要接入ELK、Loki等日志平台,强烈建议直接输出JSON格式。下面是一份生产环境可以直接使用的配置:
log_format json_log escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"x_forwarded_for":"$http_x_forwarded_for",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"referer":"$http_referer",'
'"user_agent":"$http_user_agent"'
'}';
access_log /var/log/nginx/access.log json_log;这里有几点值得注意。时间字段推荐用$time_iso8601代替$time_local,它输出ISO 8601格式,各种日志系统都能直接识别。数值型字段如status可以不加引号写成数字,但$upstream_response_time在多次重试时可能包含逗号,所以稳妥起见用字符串包裹。escape=json必须加上,否则User-Agent中的特殊字符会破坏JSON结构。
另外分享一个排查慢接口的技巧。当用户反馈接口变慢时,可以先看$request_time确认整体耗时,再对比$upstream_response_time。两者差距大说明时间消耗在Nginx自身或网络传输上,比如客户端上传大文件;两者接近且都高,则问题在后端服务本身。再用$upstream_connect_time区分是连接慢还是处理慢,问题定位方向就非常清晰了。
配置注意事项与常见坑
首先是条件日志的问题。access_log支持在指定级别关闭,比如在health check的location里写access_log off;,避免健康检查刷爆日志。但要注意log_format本身只能在http段定义,access_log则可以放在http、server、location和limit_except块中,遵循就近覆盖原则。
其次是缓冲配置。默认情况下每条日志都是同步写入磁盘的,高并发场景下会带来性能损耗。可以开启缓冲:
access_log /var/log/nginx/access.log json_log buffer=32k flush=5s;
buffer=32k表示日志先写入32KB的内存缓冲区,写满或超过flush时间再落盘。对于日志量大的站点,这个优化能明显降低磁盘IO压力。但代价是日志不再是实时的,排查正在发生的问题时要意识到可能存在几秒延迟。
最后是日志切割。Nginx重新打开日志文件依靠USR1信号,配合logrotate的标准做法是:
mv /var/log/nginx/access.log /var/log/nginx/access.log.1 nginx -s reopen # 或 kill -USR1 $(cat /run/nginx.pid)
如果只做mv不做reopen,Nginx会继续向旧文件句柄写入,新文件始终为空,这是新手最常踩的坑之一。掌握好log_format的这些细节,Nginx日志就能真正成为你观察线上服务状态的眼睛。
Nginx log_formatNginx日志配置Nginx变量修改时间:2026-09-16 07:02:36