为nginx日志增加请求体及响应内容记录,一方面可以记录网站或者nginx代理的某服务详细的请求/响应内容,以便发现bug时可以回溯追查,另一方面可以配合gotty实现实时的请求数据显示,从而在开发过程中省去了打日志或者抓包的麻烦。

安装Lua-Nginx-Module

debian系很简单,直接通过包管理就可以安装:

1
sudo apt install libnginx-mod-http-lua

Redhat系就比较烦,没找到提供该模块的repo,但是通过源码编译是不可能编译的,这辈子都不可能用源码编译的←_←,参考文档 https://github.com/openresty/lua-nginx-module#installation ,可以安装 OpenResty 获得开箱即用的 Nginx 和 ngx_lua 模块,根据 安装文档 ,CentOS的安装方法:

1
2
3
4
5
6
7
8
9
# add the yum repo:
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/

# update the yum index:
sudo yum check-update

# install openresty
sudo yum install -y openresty

这样安装好以后,OpenResty 将会被安装到 /usr/local/openresty/ 这个路径下,而它提供的 Nginx 则是其中的 /usr/local/openresty/nginx/sbin/nginx, 官方推荐的使用方式是将这个路径加入环境变量 PATH 中,这显然不是个好习惯,尤其是在已经安装过 Nginx 的情况下,所以还是做个软链接吧:

1
2
3
4
5
# 如果已经安装了Nginx,先把它重命名
sudo mv /usr/sbin/nginx /usr/sbin/nginx_bak

# 创建软链接
sudo ln -s /usr/local/openresty/nginx/sbin/nginx /usr/sbin/nginx

这样就不用做任何修改,还可以复用之前包管理安装 Nginx 的服务(/usr/lib/systemd/system/nginx.service),继续使用 systemctl 来管理 Nginx 服务的运行

修改 Nginx 配置

首先定义日志格式:

1
log_format postdata escape=json '{"realip":"$remote_addr","timestamp":"$time_iso8601","request":"$request","req_body":"$request_body","status":"$status","resp_body":"$resp_body"}';

其中 postdata 为自定义日志格式的名称;escape=json 用来对日志内容进行转义,否则日志中会出现类似 \x22这样的内容影响阅读;timestamp$time_iso8601$time_local 两种格式, $time_iso8601 相对来说可读性高一些;$request_body$resp_body 即是用来输出请求和响应的内容,这两个变量都需要在下面的服务配置中做相应设置才能使用

1
2
3
server {
……
}

中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
……
# 加入这一行使 `$request_body` 变量生效
lua_need_request_body on;

# 声明 `$resp_body` 变量,并利用 lua 模块执行脚本处理响应体,并赋值给该变量
set $resp_body "";
body_filter_by_lua '
-- 根据情况修改这里截取的长度,截取过短会导致日志中数据不完整
-- 截取过长会导致日志增长过快, 对于api服务来说响应过长一般都是无意义的错误
local resp_body = string.sub(ngx.arg[1], 1, 5000)
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
if ngx.arg[2] then
ngx.var.resp_body = ngx.ctx.buffered
end
';
……

最后在需要的 location 中加入日志配置:

1
access_log  /var/log/nginx/postdata.log  postdata;

完整配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
log_format postdata escape=json '{"realip":"$remote_addr","timestamp":"$time_iso8601","request":"$request","req_body":"$request_body","status":"$status","resp_body":"$resp_body"}';

server {
listen 80;
server_name xxx.com;

lua_need_request_body on;

set $resp_body "";
body_filter_by_lua '
local resp_body = string.sub(ngx.arg[1], 1, 5000)
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
if ngx.arg[2] then
ngx.var.resp_body = ngx.ctx.buffered
end
';

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
root /var/www/public;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}

error_page 404 /404.html;

location ~ \.php$ {
# 配置在这里就只会对该 php 服务的请求生效
access_log /var/log/nginx/postdata.log postdata;
……
}
}

利用 Gotty 实现实时请求数据显示

GoTTY 是一个简单的基于 Go 语言的命令行工具,它可以将你的终端(TTY)作为 web 程序共享。它会将命令行工具转换为 web 程序。

首先利用 tail 命令,可以实时跟踪日志文件的追加内容并输入:

1
2
# -f 参数表示循环读取, -n 3 表示执行时先读取最后三行
tail -f -n 3 /var/log/nginx/postdata.log

然后将其输出给 jq 命令:

1
tail -f -n 3 /var/log/nginx/postdata.log | jq

如果不想安装 jq 也可以用 python -m json.tool 代替,但是 json.tool 在遇到不合法的 json 时会输出错误,而使用 jq 则没有这个问题

这样就可以在服务器的 Terminal 上实时查看请求和响应的数据了:

json_log

然后利用 Gotty 把它变成 web 服务:

1
/root/gotty -p 8888 -c [user]:[passwd] sh -c 'tail -f -n 3 /var/log/nginx/postdata.log | jq' >> /dev/null 2>&1 &

用 root 帐号执行以避免权限问题,使用 -c [user]:[passwd] 设置使用网页访问时需要提供的帐户名和密码以提高安全性,使用 sh -c 以开启一个 shell 来执行我们的命令

还可以利用 crontab 将其设置为开机执行:

1
@reboot /root/gotty -p 8888 -c shixin:gogo2020 sh -c 'tail -f -n 3 /var/log/nginx/postdata.log | jq' >> /dev/null 2>&1 &