map块通过正则匹配$upstream_response_time字符串实现耗时分级,如~^0.→fast、~^[1-2].→normal、~^[3-9].|^1[0-9].→slow,不可用数值比较;分级结果作为变量嵌入log_format,由外部采集器按字段过滤实现日志分离。

map 块怎么捕获响应耗时并分级?
$upstream_response_time 是 Nginx 中唯一能拿到后端真实处理耗时的变量(单位秒,含小数),但它是字符串类型、可能为空或为“-”(如连接上游失败),不能直接用于数值比较。必须用 map 块做字符串到分类标签的映射,而不是算术判断。
常见错误是试图在 map 里写 if ($upstream_response_time > 2) { ... } ——map 不支持条件表达式,只支持正则或精确匹配。
实操建议:
- 先用
log_format记录原始$upstream_response_time,验证其取值分布(尤其关注空值、“-”、超长小数位) - 在
http块顶层定义map,避免嵌套在server或location中(Nginx 不允许) - 用正则覆盖全部可能值:例如
~^[0-9]+\.[0-9]{3,}$匹配毫秒级精度,^-$单独映射为error,空字符串映射为unknown - 分级阈值建议用字符串前缀匹配更稳:比如
~^0\.→fast,~^[1-2]\.→normal,~^[3-9]\.|^1[0-9]\.|^2[0-9]\.|^3[0-9]\.→slow
如何让不同耗时等级写入不同日志文件?
Nginx 的 access_log 不支持运行时动态路径,但可以结合 map 输出的分类变量 + log_format 的条件字段 + 外部日志轮转工具间接实现“分级采集”。真正在 Nginx 内部做到“一个请求写多个日志文件”不可行,强行用 access_log 多次声明会触发 warning 并只生效最后一个。
正确做法是统一写入主日志,但用结构化字段标记等级,再由日志收集器(如 filebeat、fluent-bit)按 latency_level: "slow" 过滤转发。
实操建议:
- 定义
log_format main_json '{ "ts": "$time_iso8601", "uri": "$uri", "rt": "$upstream_response_time", "level": "$latency_level", ... }'; -
map输出的变量名(如$latency_level)必须与log_format中引用的名称完全一致,大小写敏感 - 不要尝试用
access_log /var/log/nginx/slow.log if=$is_slow——if参数只支持布尔变量,且$latency_level是字符串,无法直接判等 - 若坚持 Nginx 端分离文件,只能靠外部脚本定时
awk '/"level":"slow"/ {print}' /var/log/nginx/access.log > /var/log/nginx/slow_requests.log,但存在实时性差、重复写入风险
为什么 map 变量在 log_format 里显示为空?
最常见原因是 map 定义位置错误或变量依赖未就绪。$upstream_response_time 在 log_phase 阶段才可用,而 map 查找发生在变量求值时;如果 map 块中引用了尚未生成的变量(比如 $request_time 在某些场景下比 $upstream_response_time 更晚就绪),会导致映射失败返回空字符串。
实操建议:
- 确认
map块位于http块最外层,且在所有server块之前 - 用
log_format debug '$latency_level|$upstream_response_time|$request_time';对比输出,定位哪个变量为空 - 避免在
map中链式依赖:比如用$latency_level去驱动另一个map,层级越深越容易因执行时机错位失效 - 测试时用
curl -I http://localhost/xxx触发真实 upstream 请求,不要用return 200这类无 upstream 的配置,否则$upstream_response_time恒为“-”
异步日志采集链路中哪些环节会丢数据?
Nginx 的 access_log 默认是 buffered + sync-on-close,不是真正异步。所谓“异步采集”,实际指日志写入 OS buffer 后由内核异步刷盘,或交由外部 agent 轮询读取。这个链路里有三个明确的数据丢失点:
- Nginx worker crash 且 buffer 未 flush:可通过
access_log ... buffer=64k flush=5s控制强制刷盘间隔 - OS page cache 满或 OOM killer 杀掉 rsyslog/filebeat:需限制日志 agent 内存用量,并开启
force_sync(如 fluent-bit 的storage.type memory+storage.sync true) - 网络抖动导致日志 collector 与后端存储断连:必须启用本地磁盘缓存(如 filebeat 的
spool_size和disk.path),否则日志直接丢弃
分级策略本身不增加丢数据风险,但会让 slow 日志更受关注——而恰恰是 slow 请求更容易触发 timeout、连接中断,导致 $upstream_response_time 不完整,进而影响 map 分级准确性。这点常被忽略。











