不能在 log_by_lua 中直接发 http 请求,因为其同步阻塞会占用 worker 进程协程、损害并发性能;应使用 ngx.timer.at 异步触发或 lua-resty-logger-socket 等无阻塞方案实现日志上报。

在 Nginx 中,log_by_lua(或 log_by_lua_block)运行在请求生命周期的最后阶段,适合做日志收集、指标上报等收尾工作。但要注意:它默认是同步执行的,直接调用阻塞式网络请求(如 http.request)会拖慢响应、降低吞吐。要实现“异步自定义日志上报”,核心是避免阻塞主线程,常用方式是借助 lua-resty-http 的 cosocket + ngx.timer.at 或更推荐的 lua-resty-logger-socket 等无阻塞方案。
为什么不能在 log_by_lua 里直接发 HTTP 请求?
Nginx 的事件循环是单线程协作式的,log_by_lua 阶段虽已返回响应给客户端,但仍占用 worker 进程的 Lua 协程。若在此处使用同步 HTTP 客户端(如老版本 resty.http 的 request),会发起阻塞 socket 操作,导致该 worker 无法处理其他请求,严重损害并发性能。
推荐方案:用 ngx.timer.at 做轻量级异步上报
利用 Nginx 内置的定时器机制,在 log_by_lua 中触发一个“零延迟”定时任务,把日志发送逻辑移到 timer 回调中执行。该回调在独立协程中运行,不阻塞当前请求流。
- timer 回调内可安全使用 cosocket(如
lua-resty-http的非阻塞接口) - 需注意:timer 回调中无法访问
ngx.var、ngx.ctx等请求上下文变量,所有必要数据必须显式传入 - 建议将日志内容序列化为 JSON 字符串后传入 timer,避免闭包捕获大量请求对象引发内存问题
更稳健方案:用 lua-resty-logger-socket 推送到本地 UDP 日志服务
该模块专为高性能日志上报设计,内部基于非阻塞 UDP socket,支持批量、缓存、重试和限速:
- 在
http块中初始化 logger 实例(全局复用) -
log_by_lua_block中调用logger.log(),仅做内存写入+缓冲,完全不涉及 I/O - 后台由独立 timer 刷出缓冲区到远端(如 Logstash、Fluentd 或自建 UDP server)
- 即使目标服务暂时不可达,日志也不会丢失(有内存缓冲和可选的磁盘 fallback)
注意事项与避坑点
切勿在 log_by_lua 中使用 require("socket")、io.open、os.execute 等阻塞操作;避免在 timer 回调中频繁创建大 table 或字符串,防止 GC 压力;生产环境务必设置 lua_max_pending_timers 和 lua_max_running_timers 防止定时器堆积;上报失败时建议只打 error 日志,不要抛异常(否则可能触发 Nginx 500 错误)。










