直接在http块中定义log_format并使用escape=json可输出合法json日志,但需显式引用格式名、避免重名、正确处理字段类型与变量,并预处理高危字段。

直接用 log_format + escape=json 就能输出合法、可解析的 JSON 日志,不需要第三方模块,但必须写在 http 块里,且 access_log 必须显式引用格式名,否则根本不会生效。
log_format 必须定义在 http 块内,且不能重名
Nginx 会拒绝在 server 或 location 块里解析 log_format,报错是 unknown directive "log_format"。更隐蔽的问题是:多个 include 文件(比如 vhosts/*.conf)里如果都定义了同名格式(如 log_format json),Nginx 启动会失败,提示 duplicate log_format "json"。
- 只在
http块顶层定义一次,命名建议带语义,比如log_format nginx_json escape=json { ... }; - 避免拼写错误,比如把
$http_x_forwarded_for写成$http_x_forwared_for—— Nginx 不报错,但该字段日志里为空字符串 - 不要在
log_format中使用未声明的变量(如$request_body需要log_format前启用proxy_set_header或client_body_buffer_size才可能有值)
escape=json 是关键,但不能替代字段类型控制
escape=json 会自动转义变量值里的双引号、反斜杠、换行符等,极大降低 JSON 破损风险。但它不改变字段类型:字符串变量仍需手动加双引号,数值变量(如 $status、$request_time)必须不加引号,否则 Elasticsearch 或 Loki 会当成字符串处理,无法做数值聚合。
- 正确写法:
"status":$status,(无引号)、"uri":"$uri",(有引号) - 错误写法:
"status":"$status",(全当字符串)、"uri":$uri(JSON 解析失败) -
$upstream_response_time可能为空或含逗号分隔多个值(如"0.002, 0.005"),建议统一用"upstream_response_time":"$upstream_response_time"保持字符串类型,后续由日志系统清洗
access_log 必须显式指定格式名,路径要绝对且可写
只写 access_log /var/log/nginx/access.json; 不会触发 JSON 格式,Nginx 默认 fallback 到内置的 combined 格式。必须明确写出格式名:access_log /var/log/nginx/access.json nginx_json;。
- 路径必须绝对,且
/var/log/nginx/目录需提前创建并赋予nginx用户写权限:mkdir -p /var/log/nginx && chown nginx:nginx /var/log/nginx - 支持多条
access_log指令,例如同时写文件和 syslog:access_log /var/log/nginx/app.json nginx_json; access_log syslog:server=127.0.0.1:514 nginx_json; - 启用缓冲可减少 I/O:
access_log /var/log/nginx/access.json nginx_json buffer=64k flush=5s;,但要注意:崩溃时可能丢失最后几秒日志
真实 IP 和高危字段需要预处理
$http_x_forwarded_for 可被伪造,$http_user_agent 可能含换行或双引号 —— 即使用了 escape=json,这些字段仍可能破坏 JSON 结构或引入不可信数据。可靠方案是组合 map 清洗 + set_real_ip_from。
- 获取真实客户端 IP:
set_real_ip_from 10.0.0.0/8;(填你实际的代理网段),再用$realip_remote_addr替代$remote_addr - 清洗
$http_user_agent:map $http_user_agent $ua_clean { default "-"; "~[[:cntrl:]"\]" "-"; },然后在log_format中用$ua_clean - 不要直接用
$request或$args,它们极易含非法字符;如需记录参数,应先提取关键字段(如$arg_id)或改用 OpenResty 注入结构化数据
最常被忽略的是:reload Nginx 后,已有长连接的请求仍按旧日志格式记录,只有新建立的连接才走新格式;验证时务必用 curl 触发新请求,再检查日志文件首行是否为合法 JSON 对象(每行一个,无换行嵌套)。











