log_format必须定义在http块顶层且需access_log显式引用格式名,否则无效;常见错误是放错位置或未指定格式名,导致Nginx启动失败或日志仍为默认格式。

直接说结论:log_format 必须写在 http 块里,且必须用 access_log 显式引用格式名,否则新格式不会生效——这是 90% 配置失败的根源。
log_format 写错位置会报 nginx: [emerg] "log_format" directive is not allowed here
常见错误是把 log_format 塞进 server 或 location 块里,Nginx 启动或 reload 直接失败。它只允许出现在 http 块顶层(包括 include 进来的文件中),且不能重复定义同名格式。
- 正确位置示例:
http { log_format custom '$remote_addr - [$time_local] "$request" $status $body_bytes_sent'; server { access_log /var/log/nginx/access.log custom; } } - 命名别用
main或combined,避免和内置格式冲突;建议用语义化名称如json_api、debug_full - 多个
include文件里都定义了log_format json_api?Nginx 会报duplicate log_format "json_api",删掉重复项即可
access_log 不显式写格式名,就永远用不到你的自定义格式
很多人改完 log_format json_api ...,只写 access_log /var/log/nginx/access.log;,结果日志还是 plain text——因为 Nginx 默认 fallback 到 combined,不会自动识别你刚定义的格式。
Linux 性能分析与调优专家,覆盖 CPU、内存、磁盘 I/O、网络、内核参数、编译优化、容器/K8s。适用场景:系统卡顿/高负载、内存不足/OOM/Swap 高、CPU 异常/iowait 高。
- 必须显式写出格式名:
access_log /var/log/nginx/access.log json_api;✅ - 路径必须绝对,且 Nginx 进程有写权限:
/var/log/nginx/要提前mkdir -p /var/log/nginx && chown nginx:nginx /var/log/nginx - 同一
server块可写多条access_log,比如同时输出到文件和 syslog:access_log /var/log/nginx/app.json json_api; access_log syslog:server=127.0.0.1:514 json_api;
JSON 格式要用 escape=json,但 $request 等字段会破坏结构
想让日志直接被 ELK 或 Loki 消费,JSON 化最省事,但不加防护极易出错。
- 关键参数是
escape=json:log_format json_api escape=json '{ "ip":"$remote_addr", "uri":"$uri", "status":$status, "rt":$request_time }'; -
$request含空格、引号、换行,直接塞进 JSON 字符串里会导致解析失败;改用$request_method、$request_uri、$server_protocol拆开更安全 - 未定义变量(如拼错成
$http_x_forwared_for)不会报错,但该字段值为空字符串,日志里看到的是"xff":"",容易误判 -
$http_x_forwarded_for是原始请求头,可能被伪造或含多个 IP(1.1.1.1, 2.2.2.2),真要记录可信客户端 IP,得配set_real_ip_from+real_ip_header X-Forwarded-For
变量选错或乱用,日志字段恒为空或全是短横线 -
不是所有 $xxx 变量都随时可用,依赖上下文和模块启用状态。
-
$upstream_response_time:仅在用了proxy_pass或fastcgi_pass的 location 里有效,否则输出- -
$request_time是总耗时(秒级浮点),$msec是当前时间戳(秒+毫秒),别混淆用途 -
$http_x_real_ip≠$realip_remote_addr:前者是原始请求头,后者是 realip 模块处理后的可信 IP,需先加载模块并配置set_real_ip_from -
$request_body开销极大(需开启client_body_buffer_size并读取完整 body),生产环境禁用;排障临时用可加条件if ($request_method = POST) { ... },但注意if在日志上下文中无效,得用map预计算
最常被忽略的一点:reload 配置后,**已建立的连接仍按旧格式记日志**,只有新请求才走新格式;另外 buffer 参数(如 buffer=16k flush=5s)虽提升性能,但进程崩溃时可能丢失最后一批日志。

















