map比if更适合User-Agent采样,因if在log_format中不可用且非location块中行为受限易出错;map专为变量映射设计,支持正则、惰性求值、无副作用,可安全用于日志条件判断。

为什么 map 比 if 更适合做 User-Agent 采样
因为 if 在 log_format 上下文不可用,且 Nginx 的 if 在非 location 块中行为受限、易出错;而 map 是专为变量映射设计的指令,支持正则、惰性求值、无副作用,且可安全用于日志条件判断。
常见错误是试图在 access_log 行里直接写逻辑表达式,比如 access_log /var/log/nginx/access.log main if=$is_sampled —— 这里的 $is_sampled 必须是已定义的变量,不能是内联判断。
-
map必须定义在http块顶层,不能嵌套在server或location中 - 匹配顺序从上到下,第一个成功匹配项生效;末尾的
default是兜底值,不可省略 - 正则需用波浪线
~*(忽略大小写),User-Agent字符串通常含空格和斜杠,正则要转义或用字符组
如何用 map 提取并分类常见爬虫和客户端
真实场景中,你不是要“记录所有 UA”,而是想:对搜索引擎爬虫全量记录,对移动端按 10% 抽样,对桌面端只记 1%,其他丢弃。这时 map 变量本质是一个采样开关(0/1)或权重标识(如 "full"/"sampled"),后续再由 log_format 或 access_log 的 if 参数消费。
map $http_user_agent $log_level {
default "drop";
~*bingbot|googlebot|yandexbot "full";
~*Android|iPhone|iPad "sampled_10";
~*Windows|Macintosh|X11 "sampled_1";
}
注意:$http_user_agent 是 Nginx 自动提供的请求头变量,首字母小写、带下划线;正则中的竖线 | 是或关系,无需额外转义;~* 已隐含忽略大小写,不用再写 i 标志。
- 不要在
map值里写复杂逻辑(如rand() ),Nginx 不支持运行时计算 - 如果需要真随机抽样,必须用
geo+map组合,或改用lua-resty-random - 测试时可用
curl -H "User-Agent: Mozilla/5.0 (iPhone..." http://localhost配合tail -f /var/log/nginx/access.log验证
怎样把 map 结果真正作用到日志输出
光有 $log_level 变量没用,得让它控制日志是否写入,或影响日志内容。最常用的是 access_log ... if= 条件写法,但注意:这个 if 只接受布尔值(0/空字符串为 false,非 0/非空为 true),所以你要把 map 输出转成 0/1。
map $log_level $should_log {
default 0;
"full" 1;
"sampled_10" 1;
"sampled_1" 1;
}
access_log /var/log/nginx/access.log main if=$should_log;
更精细的做法是让日志格式本身体现采样级别,便于下游解析:
log_format sampled '[$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$log_level"'; access_log /var/log/nginx/access.log sampled if=$should_log;
-
if=$should_log中的$should_log必须是整数或字符串,但只有值为0或空字符串时才跳过记录 - 若用字符串如
"yes"/"no",Nginx 会把它当非空字符串处理,等效于 true,导致全量记录 - 多个
access_log指令可以共存,比如同时写全量爬虫日志和抽样业务日志,互不干扰
容易被忽略的兼容性和调试陷阱
看似简单几行 map,上线后常因环境差异失效:Nginx 版本低于 1.11.0 时,map 的正则不支持命名捕获组;某些容器镜像默认关闭了 http_realip_module,导致 $http_user_agent 实际是反向代理的 UA 而非客户端真实值。
- 用
nginx -V 2>&1 | grep -o 'with-http-map-module'确认模块已编译 - 加一行
add_header X-UA-Logged "$log_level";到location,用curl -I直接看响应头,比翻日志快得多 -
User-Agent可能为空(比如某些 HTTP/2 探测请求),$http_user_agent此时是空字符串,会被map的default分支捕获,需提前考虑 - 日志采样不是替代监控的方案,它只是降低存储压力的手段;关键错误仍需通过
error_log或 tracing 全量捕获
真正难的不是写对这四五行配置,而是确认每条日志背后的 UA 是否真的被归类到了预期分支——尤其当业务接入了小程序、快应用、各种 WebView 容器时,UA 字符串越来越长、越来越不规范。











