map 指令是 Nginx 的变量映射模块,根据输入值动态生成新变量,用于日志采样等场景;日志采样本质是通过 map 构建布尔变量控制 access_log 是否记录,支持按路径、客户端特征分层采样及动态热更新。

什么是 map 指令与日志采样的核心逻辑
map 是 Nginx 的变量映射模块指令,它不执行动作,只根据输入值(如请求头、URI、IP 等)动态生成一个新变量。这个变量可在 log_format、access_log、if 判断等上下文中使用。日志采样不是“随机丢弃日志”,而是**有策略地控制某条请求是否写入 access log**——本质是让 access_log 指令的条件参数依赖一个由 map 计算出的布尔型变量。
按请求路径做分层采样:高频接口全量,低频接口 1%
假设 API 接口分两类:/api/v1/user(高频,需全量记录)和 /api/v1/report(低频但响应慢,只需抽样)。我们用 map 构建一个名为 $sample_flag 的变量:
在 http 块中定义:
map $request_uri $sample_flag {
~^/api/v1/user 1;
~^/api/v1/report $sample_rate_report;
default 0;
}再定义采样率变量(支持运行时重载):
map "" $sample_rate_report {
default 0.01; # 默认 1%
}然后在 server 或 location 中启用条件日志:
- 用
access_log ... if=$sample_flag;—— 注意:Nginx 1.7.0+ 支持该语法 - 确保
$sample_flag非 0(数值)或非空(字符串)才记录 - 若需更精细控制(如仅对耗时 >2s 的 report 请求采样),可结合
$upstream_response_time再嵌套一层 map
基于客户端特征做定向采样:只记录特定地区或设备的日志
例如:只对来自北京(通过 IP 归属库识别)且使用 iOS 设备的请求开启详细日志。先通过 geoip2 或第三方模块设置 $geo_city 和 $http_user_agent,再用 map 组合判断:
map "$geo_city:$http_user_agent" $ios_beijing_flag {
~*^(Beijing|北京市):.*Mobile.*OS.* like Mac OS X 1;
default 0;
}关键点:
- 用冒号拼接多个变量,避免歧义;正则中加
~*表示忽略大小写 - 若
$geo_city为空(未命中库),拼接后为":...",不会匹配成功,自然落入 default - 该变量可同时用于日志采样、灰度路由、Header 注入等多种场景
动态开关与热更新:无需 reload 即可调整采样率
硬编码采样率(如 0.01)不利于线上调试。更优做法是把采样率从配置文件中解耦,改由共享内存或外部变量驱动:
- 用
map引用一个可被 set_by_lua 或 stub_status 影响的变量(如$sampling_ratio) - 配合 OpenResty 的
shared_dict+ Lua 脚本,实现 HTTP 接口动态修改采样率(如POST /admin/log/sampling?ratio=0.05) - 或简单些:用 Nginx 变量绑定系统环境变量(
env SAMPLE_RATE;),启动前 export 控制 - 注意:map 的计算发生在请求处理早期,所有依赖变量必须已解析完成(如 $arg_xxx、$cookie_xxx 安全;但 $upstream_* 类需在 proxy_pass 后才有值,不能直接用于 map)
验证与避坑提醒
上线前务必验证 map 变量是否生效:
- 临时添加
add_header X-Sample-Flag $sample_flag;到响应头,curl 查看 - 用
log_format debug '$remote_addr - $sample_flag $request_uri';+ 全量日志快速定位匹配逻辑 - 常见错误:map 块放在 server 块内(必须在 http 块);正则未转义特殊字符(如
.写成\.);default 分支缺失导致变量未定义而报错 - 性能影响极小:map 查表是 O(1) 散列查找,实测万级规则下单请求开销


















