应优先在log_by_lua_block中聚合状态变更,用lua_shared_dict做毫秒级计数,区分$upstream_status与$status,键名带前缀并设ttl,/status/metrics接口暴露指标需鉴权与分页。

直接在运行时环境中捕获并聚合状态变更,关键在于找准钩子时机、选择轻量存储机制、明确统计维度,而不是一上来就写模块。
区分状态来源,避免统计失真
状态码本身有多个层级,不能只看 $status:
-
$status是 Nginx 返回给客户端的最终状态,可能被error_page或缓存覆盖 -
$upstream_status才反映后端真实响应,比如"502 504"表示重试过程中的失败组合 - 若后端透传业务状态(如
X-App-Status: 40101),还需读取$upstream_http_x_app_status
建议在 log_by_lua_block 中做分支判断,例如:
local dict = ngx.shared.status_metrics
if ngx.var.upstream_status and ngx.var.upstream_status ~= "" then
for code in string.gmatch(ngx.var.upstream_status, "%d+") do
dict:incr("upstream_" .. code, 1)
end
end
dict:incr("origin_" .. ngx.var.status, 1)
用共享内存做毫秒级聚合,不碰磁盘
Nginx 原生支持 lua_shared_dict,适合高频计数:
- 在
http块中声明:lua_shared_dict status_metrics 10m; - 每次请求结束时原子自增,无需加锁(
incr本身线程安全) - 键名建议带前缀区分维度,如
"200"、"upstream_502"、"by_route_api_v1" - 设置 TTL 防止内存泄漏:
dict:set("200", 0, 60)替代无过期的incr
暴露指标要可控,兼顾安全与可用
内置 /status/metrics 接口比轮询日志更实时:
- 用
content_by_lua_block遍历字典,按需格式化为 JSON 或 OpenMetrics 文本 - 加
access_by_lua_block做基础鉴权,比如只允许可信内网 IP 访问 - 支持查询参数,如
?format=json&limit=20,避免全量 dump 压垮连接
日志打标方案更简单,90% 场景够用
真不需要写模块时,优先用 map + log_format:
- 定义语义分类:
map $status $status_level { ~^[23] ok; ~^4[0-9]{2} bad_request; ~^5[0-9]{2} server_error; } - 日志中直接记录
status_level,交由 Filebeat/Loki 做后续聚合告警 - 注意别在 map 里用捕获组(如
$1),否则触发 PCRE JIT 回退,QPS 明显下降
不复杂但容易忽略:所有统计逻辑必须放在 log_by_lua_block 或 body_filter 钩子中,确保响应已生成、状态已确定;放在 access_by_lua_block 里会漏掉超时、拒绝等未进入处理流程的请求。










