Nginx 用 map 指令实现轻量日志采样,基于 $msec 或 $remote_addr 构造哈希逻辑达成近似均匀采样,支持分层保底(如 5xx 全量)和多条件组合,需注意 if= 值必须为整数 0/1、log_format 定义顺序及目录权限。

直接用 map 指令做日志采样,是 Nginx 原生、轻量、无依赖的方案,特别适合 QPS 上万的场景。它不靠外部脚本或定时任务,而是在请求进入阶段就决定“这条要不要记”,从源头减少 I/O 和磁盘写入压力。
采样原理:用 map 构建概率开关
map 本身不支持随机数,但可以结合内置变量(如 $msec、$request_id 或 $remote_addr)构造可预测又足够分散的哈希逻辑,实现近似均匀采样。
-
基于毫秒级时间戳采样(简单稳定):
$msec是当前秒内毫秒数(含小数),取其整数部分对固定值取模,比如模 100 → 实现约 1% 采样 -
基于客户端 IP 哈希采样(用户维度稳定):对
$remote_addr做哈希(如 CRC32),再取模,确保同一 IP 的请求始终被采或不被采,利于问题复现 -
避免使用 $request_id 直接采样:虽然它唯一,但默认不开启且需
random模块支持;若未启用,该变量为空,会导致所有请求落入default分支,失去采样意义
配置示例:按 5% 比例记录访问日志
以下配置将只记录约 5% 的请求到独立采样日志文件,其余请求完全跳过写入:
http {
log_format sample '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'$request_time $upstream_response_time';
<pre class="brush:php;toolbar:false;">map $msec $sample_flag {
~^\d+\.(\d{3})$ $1; # 提取毫秒后三位
default "000";
}
map $sample_flag $log_sample {
~^[0-4][0-9]$ 1; # 后两位在 00–49 范围 → 约 5%
default 0;
}
server {
listen 80;
server_name example.com;
access_log /var/log/nginx/access.log main;
access_log /var/log/nginx/access-sample.log sample if=$log_sample;
}
}
说明:
• 第一个 map 提取 $msec 小数点后三位(如 172345.678 → 678)
• 第二个 map 判断这三位是否落在 000–049 区间(即前导为 0 且后两位 ≤49),命中即设为 1
• access_log ... if=$log_sample 表示仅当 $log_sample == 1 时才写入该日志行
进阶技巧:多级采样 + 错误保底
生产环境建议“分层保底”:高频正常请求降采样,关键错误请求全量记录,兼顾分析效率与故障可见性。
-
对 5xx 错误强制记录:在
map中优先匹配$status,若为 500/502/503/504,则直接设$log_sample = 1,覆盖时间采样逻辑 -
组合多个条件:可用嵌套
map或借助geo模块预定义白名单(如运维IP、测试UA),让特定流量始终被采 -
验证采样率是否准确:用
awk '{print $9}' /var/log/nginx/access-sample.log | sort | uniq -c | wc -l统计样本数,再对比总请求数(tail -n 10000 access.log | wc -l)估算实际比例
注意事项与常见坑
map 日志采样看似简洁,但几个细节不注意就会失效:
-
if= 变量必须是整数 0 或 1:Nginx 的
access_log ... if=仅识别数值型布尔值;若$log_sample是字符串"on"或空,将被视为 true,导致全量写入 - log_format 必须在 map 之前定义:否则引用时报错 “unknown log format”
-
采样日志路径目录需提前创建并授权:Nginx worker 进程用户(如
nginx)必须对/var/log/nginx/下目标目录有写权限,否则静默失败 - 不建议用 $args 或 $uri 做采样依据:它们含特殊字符或长度不定,正则易出错,且 URL 解码可能引发不一致











