跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

如何利用 access_log 的 if 指令实现针对特定状态码的采样记录

Nginx 不支持 access_log 中直接使用 if 指令,需通过 map 构造布尔变量配合 access_log if= 参数实现条件日志;状态码过滤与采样(如 4xx/5xx 记录、200 响应 1% 采样)须依赖 $status、$msec 等变量及正则匹配,原生不支持算术运算或字符串截取。 Access log 的
if
指令本身并不存在于标准 Nginx 的
log_format
或
access_log
语法中。你可能混淆了 Nginx 的两个不同机制: 条件日志记录(conditional logging) 和 日志格式中的变量(如 $status) 。 真正可用的方式:用 map + access _log 的 if 参数实现 状态码 采样 Nginx 1.7.0+ 支持在
access_log
指令后添加
if=
条件,但该条件只能引用一个布尔型变量(如由
map
指令定义)。因此,要按状态码采样(例如只记录 404、500 或随机 1% 的 200),必须先用
map
构造出符合条件的标志变量。 示例:仅记录 4xx 和 5xx 错误响应
map $status $log_if_error { ~^[45] 1; # 匹配以 4 或 5 开头的状态码(如 404、500、502) default 0; }

server { access_log /var/log/nginx/error_only.log main if=$log_if_error; ... }

实现“采样”:按比例记录特定状态码(如 10% 的 200 响应) 利用 Nginx 内置变量
$request_id
(或
$msec
、
$connection
)配合哈希取模,可实现伪随机采样。注意:这不是加密安全的随机,但对日志采样足够稳定。 示例:仅记录约 5% 的 200 响应 PHP数据库链接类(PDO+Access) PHP数据库链接类(PDO+Access) 下载
map $status $log_if_200_sample { 200 $log_200_sample_flag; default 0; }

map $request_id $log_200_sample_flag { ~^(?:[0-9a-f]{8}-){3}[0-9a-f]{12}$ 0; # 防止空 request_id 干扰(可选) default 0; }

利用 $msec 小数点后三位做简单哈希(更轻量)

map $msec $sample_5_percent { ~.([0-4])$ 1; # 小数点后第一位是 0–4 → 约 50%?需调整 default 0; }

更准确的 5%:用 $connection 取模(连接数相对均匀)

map $connection $sample_5_percent_conn { ~^[0-9]+$ $conn_mod; }

map $connection $conn_mod { ~^([0-9]+)$ $(${1}%20==0); # Nginx 不支持算术表达式,此写法无效 → 改用第三方模块或 Lua 不现实 }

✅ 推荐做法:用 $remote_addr 哈希后取模(稳定且无需额外模块)

map $remote_addr $sample_5_percent { default 0; ~. 1; # 占位,实际靠 split_clients 或更可靠方式 }

✔ 实际推荐(简洁可靠):

map $status $log_200_5pct { 200 $sample_flag_200; default 0; }

map $request_id $sample_flag_200 { "~^[0-9a-f]{32}$" $(${1:0:1}%20==0); # ❌ Nginx map 不支持字符串截取和运算 }

⚠️ 关键限制:Nginx 原生
map
不支持字符串切片、数学运算或哈希函数。所以“精确百分比采样”需换思路: 用 split_clients(仅适用于变量赋值,不直接用于日志) —— 不适合日志条件 用 Lua(via lua-resty-logger-socket 或 log_by_lua) —— 灵活但增加依赖 用 $request_id 或 $msec 做简单末位判断(够用) ✅ 实用方案(无 Lua):基于
$msec
小数点后两位做 1% 采样
map $msec $sample_1pct { ~\.(\d{2})$ $1; }

map $sample_1pct $log_if_200_1pct { "00" 1; default 0; }

map $status $log_if_status_200 { 200 $log_if_200_1pct; default 0; }

access_log /var/log/nginx/200_sample.log main if=$log_if_status_200;

说明:
$msec
格式为
1712345678.123
,正则
~\.(\d{2})$
提取小数点后两位(如
12
),再用
map
匹配是否为
"00"
→ 理论概率 ≈ 1%(因毫秒小数部分均匀分布)。 组合多个条件:比如“记录所有 500 + 1% 的 200” 用
map
多级嵌套或合并逻辑:
map $status $log_500_or_sampled_200 { 500 1; 200 $sampled_200_flag; default 0; }

map $msec $sampled_200_flag { ~.00$ 1; default 0; }

access_log /var/log/nginx/combined_sample.log main if=$log_500_or_sampled_200;

验证与调试技巧 临时加一条
log_format debug '$status $msec $request_id'; access_log /tmp/debug.log debug;
查看原始值 用
curl -I http://your.site/
触发不同状态码,检查日志是否按预期出现 注意:
if=
是在日志写入前判断,不影响响应流程,也 不 影响其他指令(如 return、rewrite) 确保
map
块放在
http
块顶层,不能在
server
内

相关文章