kakakakakku blog

Weekly Tech Blog: Keep on Learning!

nginx でアクセスログを JSON フォーマットにする場合は「escape=json」を設定する

nginx で nginx.confaccess_loglog_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 を設定すると,今回の件は解決する.

nginx.org

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 を設定する.数年前から使えるパラメータだとしても,無限に知らないことはあるし,引き続き学んだことをブログにアウトプットしていくぞ!