虎の穴ラボ技術ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

Nginxのログをフィルタリングしてみる

こんにちは、虎の穴ラボの山田です。先日、開発環境の構成を変更していた際にNginxのログを見て、「一部のログをフィルタしたい」と思うことがありました。今回は、そういった時に使えるNginxの設定をご紹介します。

基本編

ログのフィルタに必要な設定は2つあります。1つはログ出力の判定、もう1つは判定結果の反映です。

ログ出力の判定

まずは「どういったログ出力を抑制するか」というログ出力の判定を設定します。
これにはNginxのmapディレクティブ、またはgeoディレクティブを利用できます。

map ディレクティブ

mapディレクティブではNginxの変数の内容を判定し、別の変数にその結果を値として代入します。
ログ出力のフィルタリングを行う際は、ログ出力を抑制する場合には 0 を設定し、そうでない場合には 1 を設定するようにします。

判定には正規表現も利用でき、例えば画像ファイルへのアクセスログを抑制したい、といった場合には以下のような設定を行います。

    # $request_filenameの内容を判定し、$loggableに値を代入します
    map $request_filename $loggable {
        ~.*\.(jpeg|jpg|gif|png|css|js|woff2|woff|ttf|svg|pdf)  0;    # 画像ファイル類へのアクセスログは出力しない
        default 1;    # それ以外はログ出力を行う
    }

先頭に ~ を付けると正規表現での判定になります。判定方法の詳細については ドキュメント を参照して下さい。

geo ディレクティブ

IPアドレスの判定にはgeoディレクティブを利用できます。 geoディレクティブでは指定された変数のIPアドレスを判定し、別の変数にその結果を値として代入します。

例えば一部のプライベートアドレスからのアクセスログを抑制したい、といった場合には以下のような設定を行います。

    # $remote_addrの内容を判定し、$loggableに値を代入します
    geo $remote_addr $loggable {
        127.0.0.1       0;    # このIPアドレスからのアクセスログは出力しない
        192.168.0.0/24  0;    # このIPアドレスからのアクセスログは出力しない
        default         1;    # それ以外はログ出力を行う
    }

geoディレクティブが参照する変数はデフォルトが $remote_addr となっているため、上記の設定は以下のようにも記述できます。

    # $remote_addrの内容を判定し、$loggableに値を代入します
    geo $loggable {
        127.0.0.1       0;    # このIPアドレスからのアクセスログは出力しない
        192.168.0.0/24  0;    # このIPアドレスからのアクセスログは出力しない
        default         1;    # それ以外はログ出力を行う
    }

mapディレクティブ及びgeoディレクティブの設定はhttpコンテキストに記述する必要があります。この点は注意しておいて下さい。

判定結果のログへの反映

先の設定によるアクセスログの出力判定を、実際のログに反映させる設定を行います。 これにはaccess_logディレクティブのifパラメータを利用します。

例えば、上記の設定を反映させるには以下のように設定します。

access_log /path/to/access.log main if=$loggable;    # ifパラメータを設定します

ifパラメータに事前に設定した変数(ここでは$loggable)を指定します。変数の値が0または空の文字列の場合は出力が抑制され、1の場合は出力が行われます。

access_logディレクティブは複数の指定が可能です。そのため既存のログファイルへの出力はそのままに、フィルタリングしたログファイルは別に作成するということもできます。

access_log /path/to/access.log main;               # 既存のログ出力はそのまま
access_log /path/to/extract.log main if=$loggable; # フィルタリングしたログは別ファイルに出力

応用編

access_logディレクティブのifパラメータでは一つの変数の判定しか行えず、ANDやORといった演算子はありません。
しかしながら、以下のような方法で複数条件の判定も可能になります。

まず、複数の判定条件をhttpコンテキストに記述します。

http {
    map $request_filename $condition1 {
        ~.*\.(jpeg|jpg|gif|png|css|js|woff2|woff|ttf|svg|pdf)  0;
        default 1;
    }

    geo $condition2 {
        127.0.0.1       0;
        192.168.0.0/24  0;
        default         1;
    }

・・・
}

次にserverコンテキストで、この判定条件の結果をsetディレクティブで結合して1つの文字列とし、それをifディレクティブで判定します。

server {
    # 判定結果の連結
    set $condition  "${condition1}${condition2}";

    # 文字列の判定
    set $loggable 0;
    if ($condition = 11) {
        set $loggable 1;
    }

・・・
}

あとは先ほどと同様にaccess_logディレクティブで出力の可否を判定します。

access_log /path/to/extract.log main if=$loggable;

ポイント

設定の際に注意してほしい点がいくつかあります。

まず、ここで使用しているディレクティブは、それぞれ記述できるコンテキストが異なるということです。
mapディレクティブ、geoディレクティブはhttpコンテキストにしか書けないのですが、setディレクティブ、ifディレクティブはhttpコンテキストには書けません。そのため記述が離れてしまいます。

http {
    mapディレクティブ、geoディレクティブはhttpコンテキストにしか書けません
・・・
    server {
        setディレクティブ、ifディレクティブはhttpコンテキストには書けず、serverコンテキスト又はlocationコンテキストに書きます
    }
・・・
}

また、ifディレクティブは 公式のwiki で説明されているように扱いが難しく、特にlocationコンテキストでの利用には注意が必要です。 ログ出力の負荷も含め、実際に利用する際には留意するようお願いします。

access_logディレクティブの ドキュメント には、ifパラメータの使い方の例として以下のような記述例が書かれているのですが、今回私の検証した環境では、この$status変数の判定結果はaccess_logディレクティブをhttpコンテキストに記述した際には正常に判定したのですが、serverコンテキストに記述した際には正常に判定できませんでした。
環境に依存する現象かもしれないのですが、こちらもどうかご留意下さい。

map $status $loggable {
    ~^[23]  0;
    default 1;
}

access_log /path/to/access.log combined if=$loggable;

終わりに

複数条件によるフィルタリングとなると少し難しい部分がでてきますが、シンプルな単一条件のフィルタリングであれば簡単に設定できます。
既存のログファイルについては(出力内容を含め)残したまま追加ができますし、なによりフィルタリングされたログファイルは見通しが良いので、開発環境などにはおすすめです。

参考にさせて頂いた記事

【nginx】特定のアクセスをログから除外する設定など – Hacker's High

Nginxで複数条件のIF文を書く方法がすごいw - Qiita

Nginx map $status gives allways default value - Stack Overflow

P.S.

虎の穴ラボでは、私たちと一緒に新しいオタク向けサービスを作る仲間を募集しています。
詳しい採用情報は以下をご覧ください。
yumenosora.co.jp