こんにちは、GMOインターネット株式会社の斉藤です。今回のnginxの小ネタのお話です。
はじめに
nginxは非常に多く使われているWebサーバーの1つで、弊社のレンタルサーバやーVPSでも多く使われています。
私が関わっている案件で、「nginxのアクセスログから特定のクエリだけを除去したい」という要求がありました。クエリというのは、ここではURLに含まれるクエリ文字列のことです。
たとえば、このdeveloper.gmo.jpのサイトの右上にある検索フォームから「gmo」というワードで検索を行うと、URLは下記のようになります。
https://developers.gmo.jp/?s=gmo&action=Search
このs=golangの部分だけをアクセスログから除去したいと言う要求です。
平たく言うとユーザーが入力した検索文字列などをログに残したくないという話でした、実際には以下のような要件になります。
nginxのアクセスログのURLクエリ文字列から特定のキーを除去したいHTTPリファラのURLも同様にしたい一時的であれストレージに記録はしたくない(なので一度記録されたログファイルに置換をかけるのは避けたい)nginxだけで完結したいs=hogehogeのキーの部分は残し値の部分だけ除去したい
2番目を少し補足すると、アクセスログではHTTPリファラーにもURLが入ることもあります。典型的なのは下記のようなWebサイトのアセットへのアクセスログです。
XXX.XXX.XXX.XXX - - [04/May/2021:18:00:00 +0900] "GET /js/index.js HTTP/1.1" 200 37194 "https://developers.gmo.jp/?s=gmo&action=Search" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"
さて、通常こういった要件ではPOSTリクエストを使うのが一般的です。ただ、今回の案件は既存のシステムへの改修でしたので、リクエスト方法変更という大きな変更ができなかったのです。
前提
nginxのログ出力形式はngx_http_log_moduleのlog_formatディレクティブで設定できます。出力自体はaccess_logディレクティブで行います。
log_formatのデフォルトは以下のようになっています。
log_format compression '$remote_addr - $remote_user [$time_local] ''"$request" $status $bytes_sent ''"$http_referer" "$http_user_agent" "$gzip_ratio"';access_log /spool/logs/nginx-access.log compression buffer=32k;
変数が使えるのでこれで実現できそうです。$request と $http_referer ですね。これらの変数はnginxによってプリセットされるので、これを置換してURLクエリの該当部分を変更すれば良さそうです。そして、このlog_formatディレクティブはhttpコンテクストの中にしか書けません。access_logはhttp, server, locationの3つのコンテクストの中に書くことができます。
これを踏まえて、以下方針を決めました。
$requestのURLクエリ文字列から特定のキー除外した変数$filtered_requestを用意$http_refererののURLクエリ文字列から特定のキー除外した変数$filtered_refererを用意独自のlog_formatを定義access_logに上記log_formatを指定してログを出力する
設定方法
変数の用意
まず変数の用意です。オリジナルの変数に対して正規表現置換を行って、URLクエリ文字列から特定キーを除去します。ただ、今回は完全に空文字にするのでは無く[FILTERED]という文字列に置き換えることにしました。この方が意図的に除去されたことがわかるので良いでしょう。
これはserverコンテクストの中で設定します。以下は例です。
server { listen 443 ssl default_server;(snip...) set $filtered_request $request; if ($filtered_request ~ (.*)q=[^&]*(.*)) { set $filtered_request $1q=[FILTERED]$2; } set $filtered_referer $http_referer; if ($filtered_referer ~ (.*)q=[^&]*(.*)) { set $filtered_referer $1q=[FILTERED]$2; }
これで$filtered_requestと$filtered_refererと言う2つの変数がセットされます。
log_formatの定義
次に、httpコンテクストの中でlog_formatを定義します。
http { ## # Basic Settings ## sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream;(snip...) log_format myformat '$http_cf_connecting_ip - $remote_addr - $remote_user [$time_local] ' '"$filtered_request" $status $body_bytes_sent ' '"$filtered_referer" "$http_user_agent"';
ここで$requestを$filtered_request、$http_refererを$filtered_refererにそれぞれ変更します。log_foramtの最初の引数は、このフォーマットの名前です。
access_logディレクティブの定義
最後にserverコンテクストに戻ってaccess_logディレクティブを定義します。access_logディレクティブは第二引数にログフォーマットの名前を渡すことができるので、ここに先ほど作成したフォーマットの名前を渡します。
access_log /dev/stdout myformat;error_log /dev/stderr;
上で設定は完了です。nginxをリロードして設定を反映させましょう。すると、ログには以下のように記録されているはずです。
XXX.XXX.XXX.XXX - - [04/May/2021:18:00:00 +0900] "GET /?s=[FILTERED]&action=Search" 200 1335 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"
おわりに
今回は特定のURLパラメータをログに出力しない設定をしましたが、同様のやり方でログへの出力を柔軟に変更できるでしょう。何かのお役に立ったならうれしく思います。
それではまた次回お目にかかりましょう。