NginxとLuaでリクエストHTTPヘッダを全てログに残す
NginxでリクエストHTTPヘッダを全てログに残すにはLuaスクリプトを使うといい。NginxでLuaを動かすにはNginxのモジュールが組み込まれている必要がある。
CentOS 7でyumから入れたNginxにはlua-nginx-moduleが組み込まれていない。
$ cat /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=0
$ sudo yum install nginx --enablerepo=nginx
$ nginx -V 2>&1 | grep -o ngx_lua | head -1
OpenResty
install
Luaモジュールを使うために、NginxをソースからinstallするかLua周りを整えてくれるNginxのOpenRestyを使う。
ソースからのインストールはCentOS 7にNginx + Lua のインストールを参考にしようとしたが結構大変そう。
対してOpenRestyが便利そうなのでOpenRestyを使う。
OpenRestyはnginxのほかにngx_luaをはじめとするCで書かれた各種サードパーティモジュールとngx_luaのAPIを利用したrestyモジュール、そしてLua/LuaJITで構成されています。
OpenRestyに含まれているnginx自体は本家のnginxと基本同じなので、別にOpenRestyを利用しなくても自分でngx_luaを組み込んだり、サーバ上にrestyモジュールを配布することで似たような環境を構築することは可能ですが、OpenRestyであれば主要なモジュールやライブラリが./configure、make、make installの一連の流れですべてゴソッとインストールされますし、OpenRestyのconfigureスクリプトはnginxのconfigureスクリプトを継承したものなのでnginxのconfigureオプションをほぼそのまま利用することもできます。
なので、ngx_luaを利用したいようなケースでは特に理由がなければOpenRestyを利用する方が手っ取り早くそして運用も楽になるのでオススメです。
引用: https://tech.mercari.com/entry/2016/05/25/170108
公式のインストール手順を見てinstallする。yumで簡単にinstallできる。
$ sudo yum install yum-utils
$ sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
$ sudo yum install openresty
$ openresty -V 2>&1 | grep -o ngx_lua | head -1
ngx_lua
nginx.conf
リクエストHTTPヘッダを全てログに残すためのLuaスクリプトをnginx.confに書く。
$ cd /usr/local/openresty/nginx/
$ sudo vim conf/nginx.conf
serverディレクティブやlocationディレクティブに書けるようだ。https://stackoverflow.com/questions/24380123/how-to-log-all-headers-in-nginxを参考にして書いた。
server {
listen 80;
略
header_filter_by_lua_block {
local h = ngx.req.get_headers()
local h_str = "##"
for k, v in pairs(h) do
h_str = h_str..k..":"..v.."\t"
end
h_str = h_str.."##"
ngx.log(ngx.ERR, "Got header "..h_str)
}
}
各ヘッダはタブでつなげて一つの文字列とし、後でこの部分だけを取り出しやすいように##
でくくった。
$ sudo systemctl start openresty
$ systemctl status openresty
$ curl http://localhost/
$ curl http://localhost/test
$ tail logs/error.log
2018/07/27 20:53:02 [error] 8434#8434: *30 [lua] header_filter_by_lua:8: Got header ##host:localhost accept:*/* user-agent:curl/7.29.0 ## while sending response to client, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost"
2018/07/27 20:53:05 [error] 8434#8434: *31 open() "/usr/local/openresty/nginx/html/test" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "GET /test HTTP/1.1", host: "localhost"
2018/07/27 20:53:05 [error] 8434#8434: *31 [lua] header_filter_by_lua:8: Got header ##host:localhost accept:*/* user-agent:curl/7.29.0 ##, client: 127.0.0.1, server: localhost, request: "GET /test HTTP/1.1", host: "localhost"
##host:localhost accept:*/* user-agent:curl/7.29.0 ##
というようにヘッダが取得できている。
ヘッダログの利用例
残したヘッダログを用いて、ブラウザやアプリから送られてきたHTTPリクエストをヘッダ部分までログから正確に再現する。それを使ってCDNの暖気などに使う。
ワンライナーで書くと次のようになる。
$ cat error.log | grep 'Got header' | sed 's/^.*Got header ##//' | sed 's/##.*"GET//' | sed 's#HTTP/1.1".*##' | sort -u | ruby -e 'STDIN.readlines.each{|line| puts "curl " + line.chomp.split("\t").map{|i| "-H \"#{i}\""}.join(" ").gsub(/-H \" (\/.*) \"$/, "http://localhost\\1") }' | sh
さすがに長いので改行する。
$ cat error.log |
grep 'Got header' |
sed 's/^.*Got header ##//' |
sed 's/##.*"GET//' |
sed 's#HTTP/1.1".*##' |
sort -u
ここまでで行っていることは、##ヘッダ文字列##
とGET /URL
の/URL
を取り出し一意にしている。このような出力となる。
host:localhost accept:*/* user-agent:curl/7.29.0 /
host:localhost accept:*/* user-agent:curl/7.29.0 /test
これをcurlでアクセスするように行単位で文字列整形する。
## 続き
ruby -e 'STDIN.readlines
.each{|line|
puts "curl " +
line.chomp
.split("\t")
.map{|i| "-H \"#{i}\""}
.join(" ")
.gsub(/-H \" (\/.*) \"$/, "http://localhost\\1") }
'
出力はこうなる。
curl -H "host:localhost" -H "accept:*/*" -H "user-agent:curl/7.29.0" http://localhost/
curl -H "host:localhost" -H "accept:*/*" -H "user-agent:curl/7.29.0" http://localhost/test
あとは出力をパイプでsh
につないで、実際にcurlを発行している。