如何将Nginx访问日志输出为结构化JSON格式?

来源:Oracle教程作者:缓存小熊猫头衔:程序员
导读:本期聚焦于缓存小熊猫创作的《如何将Nginx访问日志输出为结构化JSON格式?》,敬请观看详情。Nginx默认的combined日志用空格分隔字段,一旦URL或User-Agent中包含空格,采集端就很容易把字段切错,后续还得靠脆弱的正则慢慢修复。想绕开这些麻烦,最直接的办法是让Nginx在写入日志时就输出标准JSON。Nginx从1.11.8版本开始为log_format提供了escape=json参数,会自动对变量内容做JSON转义,避免双引号、反斜杠等字符破坏结构。本文会从基础配置讲起,覆盖字段类型处理、空值兜底、条件日志、syslog输出以及Filebeat采集,并给出高流量场景下的字段裁剪与性能建议。读完可以落地一套无需正则解析的Nginx结构化日志方案,方便接入ELK、Loki或ClickHouse。

把Nginx日志改成JSON输出,并不是简单地把花括号拼进log_format,因为访问日志中的请求路径、User-Agent、Referer等变量经常包含空格、双引号或反斜杠。真正可靠的做法是启用escape=json参数,让Nginx在写盘前完成安全的JSON转义。下面从配置和采集两端展开,说明如何落地一套稳定的结构化日志方案。

如何将Nginx访问日志输出为结构化JSON格式?

一、为什么要把Nginx日志改成JSON

Nginx默认的combined日志格式已经在生产环境使用了很多年,它的字段顺序固定,看起来也很紧凑。但问题在于,这种格式本质上是一种约定俗成的文本协议,字段之间用空格分割,而不同字段内部本身也可能出现空格。例如请求行中的URL参数、User-Agent字符串、Referer地址,都可能让日志分析程序产生歧义。

当这些日志被Filebeat、Logstash或Promtail采集后,通常需要写一长串正则表达式来提取字段。一旦某个字段顺序调整、新增自定义变量,或者某个请求参数里混入了异常字符,解析就可能失败。更麻烦的是,Nginx变量中偶尔会出现双引号或反斜杠,文本日志无法表达这些字符的边界,解析器很难判断哪里是真实字段值。

JSON格式天然带有字段名和类型边界,采集端可以使用现成的JSON解析器直接还原结构,无需再维护脆弱的正则。Nginx从1.11.8版本开始支持escape=json,这让JSON日志的输出变得可行,并且可以安全处理变量值中的特殊字符。

二、基础配置:log_format与escape=json

Nginx的日志格式化由log_format指令完成。要输出JSON,需要在http块中定义一个新的格式,并在access_log中引用它。关键在于加上escape=json参数,这样Nginx会负责把变量内容中的双引号、反斜杠、换行符等转换成合法的JSON转义序列。

下面是一份常见的JSON访问日志配置,字段类型上做了区分:字符串字段用双引号包裹,状态码和请求耗时等数值字段不加引号。

http {
    log_format json_combined escape=json
        '{"timestamp":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"request_method":"$request_method",'
        '"request_uri":"$request_uri",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"request_time":$request_time,'
        '"http_referer":"$http_referer",'
        '"http_user_agent":"$http_user_agent",'
        '"http_x_forwarded_for":"$http_x_forwarded_for"}';

    access_log /var/log/nginx/access.json.log json_combined;
}

重启或重新加载Nginx后,访问站点就能看到类似下面的一行JSON日志。

{"timestamp":"2025-04-09T10:20:30+08:00","remote_addr":"203.0.113.7","request_method":"GET","request_uri":"/api/v1/health?x=1","status":200,"body_bytes_sent":42,"request_time":0.001,"http_referer":"","http_user_agent":"Mozilla/5.0","http_x_forwarded_for":"192.168.1.10"}

这里有一个细节需要注意:并不是所有变量都适合不加引号。status和body_bytes_sent在正常请求中几乎一定存在,因此可以当作数字输出。但如果某个变量可能为空,并且你又没有给它加默认值,那么不带引号就可能产生无效JSON,比如字段后半段直接缺失。这个问题会在下一节详细展开。

三、空值、数值类型与特殊字符处理

Nginx变量为空时,默认输出空字符串。如果变量处在JSON字符串字段中,输出结果就是空字符串,这是合法JSON。但如果变量被当作数值字段输出,例如写成"upstream_response_time":$upstream_response_time,那么当上游没有响应时间时,日志会变成"upstream_response_time":,后面的值直接消失,整行JSON立即失效。

解决空值问题有两种方式。第一种是对可能为空的数值字段统一加引号,让它们成为字符串,再由下游日志平台做类型转换。第二种是使用map指令给变量提供默认值。比如上游响应时间变量为空时,让它输出0,再继续以数字形式写入JSON。

map $upstream_response_time $upstream_rt_json {
    default $upstream_response_time;
    "" 0;
}

log_format json_upstream escape=json
    '{"timestamp":"$time_iso8601",'
    '"upstream_addr":"$upstream_addr",'
    '"upstream_status":"$upstream_status",'
    '"upstream_response_time":$upstream_rt_json}';

除了空值,特殊字符也必须重视。User-Agent中可能出现双引号,URL参数中可能出现反斜杠,Referer中可能出现空格。如果直接拼进JSON而不做转义,这些字符会提前闭合字符串或引入非法转义。启用escape=json后,Nginx会按照JSON标准处理这些内容,生成的日志可以安全交给JSON解析器。

另外,如果使用了map自定义变量,并且这些变量来自于用户请求内容,也应当继续依赖escape=json处理。map本身不会做JSON转义,真正发挥作用的是log_format中的escape参数。

四、条件日志、syslog与Filebeat采集实践

全量JSON日志虽然结构清晰,但高流量站点每天可能产生几十GB数据。通过条件日志可以有选择地记录,例如只记录非2xx和3xx状态码的请求,减少无关数据量。Nginx的access_log指令支持if参数,配合map可以灵活控制。

map $status $loggable {
    ~^[23] 0;
    default 1;
}

access_log /var/log/nginx/access.json.log json_combined if=$loggable;

如果希望把JSON日志直接发送到syslog,由集中式日志系统接收,可以使用syslog输出。这样本机不落盘,适合容器化环境或统一日志管道。

access_log syslog:server=127.0.0.1:514,facility=local7,tag=nginx,severity=info json_combined;

在Filebeat侧,JSON日志可以省去大量解析配置。使用filestream输入并指定ndjson解析器,每一行JSON会被自动展开成字段。示例配置如下。

filebeat.inputs:
  - type: filestream
    id: nginx-json-access
    paths:
      - /var/log/nginx/access.json.log
    parsers:
      - ndjson:
          target: ""
          overwrite_keys: true
output.elasticsearch:
  hosts: ["127.0.0.1:9200"]

这种组合方式省去了Filebeat中复杂的正则处理器,也能避免字段解析顺序变化带来的维护成本。对于已经使用JSON日志的团队,接入Loki、ClickHouse或ELK的难度都会明显降低。

五、性能开销与字段裁剪建议

JSON日志的主要开销来自三方面:escape=json的转义扫描、字符数量增加带来的磁盘写入、以及采集端的解析成本。在高QPS场景下,每个请求多执行一次JSON转义,CPU消耗会略有上升。如果日志量特别大,应该只保留真正有用的字段。

建议根据业务需要裁剪掉高基数且低价值的字段,例如完整的User-Agent和Referer通常只用于营销或安全分析,如果不使用,就不要写入。可以通过定义不同的log_format来区分核心日志和扩展日志,核心日志只记录请求状态、耗时、上游地址等关键字段,扩展日志按需开启。

还可以为access_log配置缓冲写入,减少磁盘IO频率。比如使用buffer=64k和flush=5s,在高并发时能够合并大量小写入。日志轮转仍然建议保留,避免单个JSON文件无限增长。

access_log /var/log/nginx/access.json.log json_combined buffer=64k flush=5s;

总体来看,Nginx输出JSON日志的额外成本可控,换来的是日志解析稳定性和下游系统维护成本的大幅下降。对于正在整合日志管道的团队,直接让Nginx输出结构化JSON,通常比后期在采集端做文本解析更划算。

Nginx日志JSON格式结构化日志修改时间:2026-08-24 15:16:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。