log_by_lua是OpenResty请求生命周期最后阶段,适合无阻塞打点;支持读变量、shared dict计数、本地日志、UDP发指标;需配合lua_shared_dict声明字典,按路径/状态码/耗时分桶统计,并可结合query/header/cookie提取业务维度,最终通过/metrics导出至Prometheus。

在 OpenResty 中,log_by_lua 是请求生命周期的最后阶段(响应已发出但连接尚未关闭),非常适合做轻量级、无阻塞的业务指标打点。配合 location 块使用,可以精准按路由维度采集指标,比如接口成功率、耗时、业务状态码等。
明确 log_by_lua 的执行时机和限制
log_by_lua 在 Nginx worker 进程中同步执行,不支持 I/O 操作(如 ngx.sleep、ngx.socket.tcp),也不能修改响应头或 body。它适合做:
- 读取已生成的变量(如
ngx.var.status、ngx.var.upstream_status、自定义$arg_*或$cookie_*) - 调用
ngx.shared.DICT:incr()累加共享内存计数器 - 写入本地日志文件(通过
ngx.log,注意级别和频率) - 向 UDP 端口发指标(需提前配置
lua_socket_udp_connect_timeout等,且必须是非阻塞发送)
用 shared dict 实现低开销实时计数
这是最常用、最高效的方式。先在 http 块声明共享字典:
lua_shared_dict biz_metrics 10m;
再在目标 location 中使用:
location /api/order/pay {
# ... upstream / rewrite 等逻辑
log_by_lua_block {
local dict = ngx.shared.biz_metrics
local status = tonumber(ngx.var.status) or 0
local path = ngx.var.uri
local cost = tonumber(ngx.var.request_time) * 1000 -- ms
<pre class="brush:php;toolbar:false;"> -- 按状态码分类计数
dict:incr("status_" .. status, 1)
-- 按路径 + 状态码二维统计
dict:incr("path_status:" .. path .. ":" .. status, 1)
-- 耗时分桶(如 0-100ms、100-500ms)
if cost <p>}</p>结合业务参数提取关键维度
很多指标需要依赖请求中的业务字段,例如订单 ID、用户等级、渠道来源。只要这些字段在 log_by_lua 阶段仍可访问,就能打点:
-
Query 参数:用
ngx.var.arg_order_id、ngx.var.arg_channel -
Header 字段:用
ngx.req.get_headers()["x-user-level"](注意大小写归一化) -
Cookie:用
ngx.var.cookie_uid或解析ngx.var.http_cookie -
Upstream 返回头:如果 upstream 设置了
add_header X-Biz-Code 2001,可用ngx.var.upstream_http_x_biz_code
示例:统计不同渠道的支付失败率
log_by_lua_block {
local channel = ngx.var.arg_channel or "unknown"
local status = tonumber(ngx.var.status)
local dict = ngx.shared.biz_metrics
<pre class="brush:php;toolbar:false;">dict:incr("channel_req:" .. channel, 1)
if status ~= 200 then
dict:incr("channel_fail:" .. channel, 1)
end
}
导出指标到外部系统(如 Prometheus)
shared dict 本身不暴露 HTTP 接口,需配合 prometheus.lua(OpenResty 官方库)或自定义 /metrics location 拉取:
location /metrics {
content_by_lua_block {
local prometheus = require "prometheus"
local metric = prometheus:new("biz_metrics")
local dict = ngx.shared.biz_metrics
<pre class="brush:php;toolbar:false;"> -- 把 shared dict 中的 key 映射为 Prometheus 指标
for key, val in dict:get_keys(0) do
if type(val) == "number" and key:match("^status_") then
metric:counter(key, "Business status count"):set(val)
end
end
metric:collect()
}
}
然后用 Prometheus 的 scrape_configs 定期拉取该 endpoint 即可。










