比AccessLog更全面的原生Nginx 日志记录

一个原生 C 模块,让 Nginx 拥有完整 HTTP 审计能力------无需 Lua、无需 Sidecar、零运行时依赖。


网关层的 HTTP 审计,核心诉求很简单:记录完整的请求和响应对,包括 headers 和 body。

但原生 Nginx 的 access_log 天然做不到这一点------它只能记录 URI、状态码、响应时间等元数据,请求体和响应体不在其视野范围内。

OpenResty/Lua 方案可以补齐这个能力,但在金融、运营商、政企等对合规和稳定性要求极高的环境中,引入 LuaJIT 运行时本身就是一道门槛。


原生 Nginx 的审计盲区

先说说原生 Nginx 的 access_log 到底能记什么:

nginx 复制代码
log_format main '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent"';

能看到:谁、什么时候、访问了什么 URL、返回了什么状态码、花了多少字节。

看不到的:请求体里提交了哪些参数、后端返回了什么业务数据、内容在压缩前长什么样。

对于等保审计来说,这相当于只记了"某人进了门",但没记"他带走了什么文件"。

更麻烦的是,即使你自己写 C 模块,Nginx 的 body_filter 链是在 gzip 压缩之后执行的。这意味着你拿到的是压缩后的乱码,不是原始的业务数据。


OpenResty 不是银弹

有人可能会说:OpenResty + Lua 就能读 body 啊。

技术上确实可以:

lua 复制代码
-- access_by_lua 读请求体
ngx.req.read_body()
local req_body = ngx.req.get_body_data()

-- body_filter_by_lua 拼响应体(每个 chunk 都是新 Lua string)
resp_body = resp_body .. ngx.arg[1]

-- log_by_lua 输出 JSON 审计日志

但这个方案有几道隐形门槛:

1. 运行时依赖 = 合规风险

金融、运营商、政企的外网网关,安全基线通常写得明明白白:禁止在网关上引入动态语言运行时

LuaJIT 是动态执行的,代码可以热更新,这意味着审计逻辑本身可能被篡改。等保测评要的是"审计系统自身可被审计",Lua 层过不了这一关。

2. 拿不到原始 body

body_filter_by_lua 运行在 gzip 压缩之后 。想要原始 body,必须开 gunzip on,让 Nginx 先解压、Lua 审计、再压缩发出去。CPU 双倍开销不说,filter 顺序还可能冲突。

3. 性能是硬伤

我们在同硬件上做过对比(openEuler 22.03, i5-10400):

方案 RPS 相对原生
原生 Nginx 68,392 100%
OpenResty + Lua 审计 19,387 28%
nginx-flowlens (TLV) 32,422 47%

Lua 的字符串不可变性意味着每次 body_filter 拼接都要分配新内存。1MB 的响应体,几十个 chunk 下来,GC 压力直接爆炸。我们实测过,大文件场景下 Lua 方案的延迟能到秒级。

4. 维护成本

C 模块 crash 是段错误,coredump 一看就知道问题在哪。Lua 异常可能被 pcall 默默吞掉,线上出了问题你都不知道审计日志漏了没。


我们做了什么

nginx-flowlens 是一个 Nginx 原生 C 模块,只做一件事:在网关层完整捕获 HTTP 请求/响应对,输出结构化审计日志。

编译进 Nginx 二进制即可,两行配置启用:

nginx 复制代码
inspect on;
inspect_log /var/log/nginx/inspect.log;

核心设计:在正确的位置插桩

复制代码
NGX_HTTP_ACCESS_PHASE   → 捕获请求头 + 读请求体
top_header_filter       → 捕获响应头(gzip 之前)
top_body_filter         → 累积原始响应体(gzip 之前)
log handler             → 序列化为 TLV/JSON,写入日志

关键点在于 top_body_filter:我们在 gzip/brotli 压缩之前注册了 filter,所以拿到的是原始 body,不管客户端收到的是不是压缩后的。

为什么用 TLV 作为默认格式

审计日志是写密集场景。JSON 虽然人可读,但序列化开销太大:

格式 小请求 RPS 开销
baseline 32,441 ---
TLV 29,389 ~9%
JSON 2,811 ~91%

TLV 是紧凑二进制格式,序列化速度约为 JSON 的 10 倍。日常调试可以用 JSON,生产环境默认 TLV,需要分析时用工具转换:

bash 复制代码
python3 tools/tlv2json.py -i inspect.log -o audit.jsonl

子请求过滤

Nginx 的 auth_requestSSI 等内部机制会产生子请求。flowlens 通过 r != r->main 天然过滤,只审计用户原始请求,不会把内部鉴权请求混进审计日志。


适用场景

场景 为什么需要
等保/PCI-DSS 合规 三级等保明确要求"应能够记录应用系统的运行状态和用户行为,包括用户 ID、时间、事件类型、操作结果等"。没有 body 的日志等于半成品。
API 全链路追踪 当线上出现"某个请求返回了错误但抓包没抓到"时,审计日志里有完整的请求/响应对,可以直接 replay。
安全取证 疑似攻击请求进来时,安全团队需要看到完整的请求内容,包括 POST body 里的 payload。
数据变更审计 金融场景的转账、支付接口,需要留存完整的请求证据。

不是"比 OpenResty 快",而是"没有 OpenResty 也能做"

很多人听到"Nginx C 模块"的第一反应是:「为什么要自己写 C,OpenResty 不香吗?」

香不香取决于约束条件。如果你的环境允许装 LuaJIT、允许动态执行、对性能不敏感,OpenResty 确实够用。

但如果你在以下约束下工作:

  • 安全基线禁止网关引入 Lua VM

  • 等保要求审计系统自身可静态源码审查

  • 网关层性能预算有限(不能容忍 -70% RPS)

  • 需要捕获 gzip 前的原始 body

    那 OpenResty 本来就不是可选项。flowlens 的价值是从 0 到 1------让原生 Nginx 拥有原本只有外挂方案才能做到的完整审计能力。


快速体验

bash 复制代码
# 克隆项目
git clone https://github.com/kumustone/nginx-flowlens.git
cd nginx-flowlens

# 一键启动开发环境(无需 root)
./run-dev.sh install

# 发一条测试请求
curl -s -X POST http://localhost:19099/ -d '{"test":true}'

# 查看审计日志(TLV → JSON)
python3 tools/tlv2json.py -i .nginx-dev/logs/inspect.log

输出示例:

json 复制代码
{
  "timestamp": "2026-04-21T09:40:59.628Z",
  "client_ip": "127.0.0.1",
  "request": {
    "method": "POST", "uri": "/",
    "headers": {"Content-Type": "application/json"},
    "body": "eyJ0ZXN0Ijp0cnVlfQ=="
  },
  "response": {
    "status": 200,
    "headers": {"Content-Type": "text/html"},
    "body": "..."
  }
}

项目地址https://github.com/kumustone/nginx-flowlens

License:Apache 2.0

相关推荐
乘云数字DATABUFF2 天前
5分钟部署开源APM Databuff:OpenTelemetry全链路追踪入门实战
运维·后端
荣--4 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森4 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜5 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB6 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode7 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220708 天前
如何搭建本地yum源(上)
运维
ping某9 天前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
大树8811 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠11 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql