Nginx中access_by_lua_file获取状态码并调用外部API的方案咨询
解决方案:获取后端响应状态码并发送至外部API
问题原因
access_by_lua_file阶段在proxy_pass之前执行,此时后端还未返回响应,所以ngx.status/ngx.var.status会返回默认值0;log_by_lua_file阶段能拿到正确状态码,但该阶段禁止同步IO操作(比如直接发起HTTP请求),以下是可行的替代方案:
方案1:利用body_filter_by_lua_file阶段同步发送
这个阶段在后端响应返回后执行,可获取正确状态码,且允许同步发起HTTP请求。注意需限制逻辑仅执行一次(因body_filter可能因分块响应被多次调用)。
修改nginx.conf
将access_by_lua_file替换为body_filter_by_lua_file:
location /hello { # 保留原有CORS、proxy配置... body_filter_by_lua_file lua/logging.lua; proxy_pass http://127.0.0.1:3131; }
更新logging.lua
-- 标记已发送日志,避免重复执行 if ngx.ctx.log_sent then return end ngx.ctx.log_sent = true local request_time = ngx.now() - ngx.req.start_time() local data = { request_method = ngx.var.request_method, user_agent = ngx.var.http_user_agent, request_time = request_time, status = ngx.var.status, -- 此处可获取正确状态码 url = "http://" .. ngx.var.host .. ngx.var.request_uri, ip = ngx.var.remote_addr, time = ngx.var.time_iso8601, path = ngx.var.request_uri } local httpc = require("resty.http").new() local output = cjson.encode(data) local res, err = httpc:request_uri("http://127.0.0.1:3232/api/create-log", { method = "POST", body = output, headers = { ["Content-Type"] = "application/json", }, }) if not res then ngx.log(ngx.ERR, "request failed: ", err) end
方案2:在log_by_lua_file中使用异步定时器发送
log_by_lua_file禁止同步IO,但允许通过ngx.timer.at创建异步定时器,在回调中执行API请求,既保留正确状态码的获取,又绕开阶段限制。
修改nginx.conf
将access_by_lua_file替换为log_by_lua_file:
location /hello { # 保留原有CORS、proxy配置... log_by_lua_file lua/logging.lua; proxy_pass http://127.0.0.1:3131; }
更新logging.lua
-- 定时器回调:发送日志到外部API local function send_log(premature, log_data) if premature then return end local httpc = require("resty.http").new() local output = cjson.encode(log_data) local res, err = httpc:request_uri("http://127.0.0.1:3232/api/create-log", { method = "POST", body = output, headers = { ["Content-Type"] = "application/json", }, }) if not res then ngx.log(ngx.ERR, "request failed: ", err) end end -- 收集日志数据(此时ngx.var.status已正确赋值) local request_time = ngx.now() - ngx.req.start_time() local log_data = { request_method = ngx.var.request_method, user_agent = ngx.var.http_user_agent, request_time = request_time, status = ngx.var.status, url = "http://" .. ngx.var.host .. ngx.var.request_uri, ip = ngx.var.remote_addr, time = ngx.var.time_iso8601, path = ngx.var.request_uri } -- 创建异步定时器(0表示立即执行) local ok, err = ngx.timer.at(0, send_log, log_data) if not ok then ngx.log(ngx.ERR, "failed to create timer: ", err) end
方案3:使用lua-resty-logger-socket批量异步发送(高并发场景推荐)
针对高日志量场景,推荐使用OpenResty官方的lua-resty-logger-socket库,支持异步批量发送,性能更优,可在log_by_lua_file阶段使用。
安装库
opm get openresty/lua-resty-logger-socket
更新logging.lua
local logger_socket = require "resty.logger.socket" -- 初始化logger(仅首次执行) if not logger_socket.initted() then local ok, err = logger_socket.init{ host = "127.0.0.1", port = 3232, path = "/api/create-log", method = "POST", headers = { ["Content-Type"] = "application/json", }, pool_size = 10, flush_limit = 1024, -- 累计1KB日志后发送 drop_limit = 1048576, -- 超过1MB时丢弃日志 } if not ok then ngx.log(ngx.ERR, "failed to initialize logger: ", err) return end end -- 收集日志数据 local request_time = ngx.now() - ngx.req.start_time() local log_data = { request_method = ngx.var.request_method, user_agent = ngx.var.http_user_agent, request_time = request_time, status = ngx.var.status, url = "http://" .. ngx.var.host .. ngx.var.request_uri, ip = ngx.var.remote_addr, time = ngx.var.time_iso8601, path = ngx.var.request_uri } -- 发送日志 local output = cjson.encode(log_data) .. "\n" local bytes, err = logger_socket.log(output) if err then ngx.log(ngx.ERR, "failed to log message: ", err) end
方案对比
- 方案1:实现简单,同步发送,但API响应慢会阻塞请求,适合低并发场景。
- 方案2:异步发送不阻塞请求,适配大多数场景,资源占用可控。
- 方案3:高性能批量发送,适合高日志量、高并发场景,官方维护稳定性强。
内容的提问来源于stack exchange,提问作者Kevin Surya
相关产品推荐
相关产品推荐

