You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 02:45:22