nginx で nginx.conf に access_log と log_format を設定すると,ログフォーマットをカスタマイズできる.log_format のデフォルト設定は combined だけど,ログ集計などを考慮して,JSON フォーマットに変更する場面も多いと思う.例えば,以下のように log_format を設定すると,JSON フォーマットでアクセスログを出力できる.
log_format json '{' '"time": "$time_local",' '"remote_addr": "$remote_addr",' '"host": "$host",' '"remote_user": "$remote_user",' '"status": "$status",' '"server_protocol": "$server_protocol",' '"request_method": "$request_method",' '"request_uri": "$request_uri",' '"request": "$request",' '"body_bytes_sent": "$body_bytes_sent",' '"request_time": "$request_time",' '"upstream_response_time": "$upstream_response_time",' '"http_referer": "$http_referer", ' '"http_user_agent": "$http_user_agent",' '"http_x_forwarded_for": "$http_x_forwarded_for",' '"http_x_forwarded_proto": "$http_x_forwarded_proto"' '}'; access_log /var/log/nginx/access.log json;
アクセスログに " が含まれる場合
例えば,アクセスログに " が含まれる場合,自動的に \x22 に変換されてしまう.検証として,以下のように User Agent に kakaka"kakku と設定し,nginx にリクエストを送る.
$ curl -H 'User-Agent: kakaka"kakku' http://www.example.com/
すると,以下のように出力される(アクセスログを整形している).User Agent を http_user_agent の値で確認すると,確かに kakaka\x22kakku になっている.なお,今回は例として " で検証をしているけど,正確に言うと " 以外にも「RFC 4627」で定義された文字は変換されてしまう.
{ "time": "22/Nov/2019:00:00:00 +0900", "remote_addr": "x.x.x.x", "host": "www.example.com", "remote_user": "-", "status": "200", "server_protocol": "HTTP/1.1", "request_method": "GET", "request_uri": "/", "request": "GET / HTTP/1.1", "body_bytes_sent": "0", "request_time": "0.000", "upstream_response_time": "-", "http_referer": "-", "http_user_agent": "kakaka\x22kakku", "http_x_forwarded_for": "x.x.x.x", "http_x_forwarded_proto": "http" }
解決策 : escape=json を設定する
ドキュメントを読むと,「nginx 1.11.8(2016年12月リリース)」から使える escape パラメータを使って,escape=json を設定すると,今回の件は解決する.
escape=json を設定し,最終的な nginx.conf は以下のようになる.
log_format json escape=json '{' '"time": "$time_local",' '"remote_addr": "$remote_addr",' '"host": "$host",' '"remote_user": "$remote_user",' '"status": "$status",' '"server_protocol": "$server_protocol",' '"request_method": "$request_method",' '"request_uri": "$request_uri",' '"request": "$request",' '"body_bytes_sent": "$body_bytes_sent",' '"request_time": "$request_time",' '"upstream_response_time": "$upstream_response_time",' '"http_referer": "$http_referer", ' '"http_user_agent": "$http_user_agent",' '"http_x_forwarded_for": "$http_x_forwarded_for",' '"http_x_forwarded_proto": "$http_x_forwarded_proto"' '}'; access_log /var/log/nginx/access.log json;
もう1度,User Agent に kakaka"kakku と設定し,nginx にリクエストを送ると,http_user_agent の値が kakaka\"kakku となり,うまくエスケープされて出力できるようになった.なお,remote_user など,もともと値が - だった部分もブランクになっていて,ここはドキュメントには仕様が書かれてなく,理由までは追えなかった.
{ "time": "22/Nov/2019:00:00:00 +0900", "remote_addr": "x.x.x.x", "host": "www.example.com", "remote_user": "", "status": "200", "server_protocol": "HTTP/1.1", "request_method": "GET", "request_uri": "/", "request": "GET / HTTP/1.1", "body_bytes_sent": "0", "request_time": "0.000", "upstream_response_time": "", "http_referer": "", "http_user_agent": "kakaka\"kakku", "http_x_forwarded_for": "x.x.x.x", "http_x_forwarded_proto": "http" }
まとめ
nginx でアクセスログを JSON フォーマットにする場合,「nginx 1.11.8(2016年12月リリース)」から使える escape パラメータを使って,escape=json を設定する.数年前から使えるパラメータだとしても,無限に知らないことはあるし,引き続き学んだことをブログにアウトプットしていくぞ!