2022/12/27 一部の文章を修正・更新しています。
こんにちは! BFT名古屋支店の佐野です。
今回はAWSのCloudWatch Logsに出力されるログから、一定のワードを検出するために使用するフィルターのお話です。
はじめに:CloudWatch Logsのフィルター機能
AWSでは、仮想マシンに相当する“EC2”やコンピューティングサービスである“Lambda”、そして“API Gateway”などのログをCloudWatch Logsに出力することができます。
そして実際の運用においては、単に出力するだけでなく「●●というログが出力されたら、○○に通知を送る」といったログ監視の機能を付けることが多いです。
CloudWatch Logsでは正にその機能に適する、出力された単語を検知するための2種類のフィルターを設定できます。
メトリクスフィルター
2種類のフィルターのうち、ひとつは“メトリクスフィルター”といい、こちらは特定の文字列が含まれているログを集計するものになります。
そのためメトリクスフィルターの設定時には集計用のメトリクスを作成することになり、そしてそのメトリクスにはアラームを設定できます。
これを利用し、検出したいログが出力された時にメールなどによって通知を送ることができます。
しかしながら欠点もあります。
メトリクスフィルターはあくまで「集計」を行なうものであるため、アラームによる通知の内容にログ本文を入れることはできません。
なので具体的にどういったログが出力されたのかは、通知された情報を元にCloudWatch Logsから確認する必要があります。
フィルターの条件を細かく設定していればあまり問題にはなりませんが、例えば“Error”のようなありふれたキーワードのみをフィルタ対象としてしまうと、通知が来た時点で「どういったエラーなのか」を把握できないため、確認の手間がかかってしまいがちです。
また次に紹介するサブスクリプションフィルターにも共通する欠点ですが、フィルタには正規表現は使用できず、単純な文字列の指定しか行なうことができません。
例えばエラーを検出したいフィルタに“Error”というキーワードを設定したとしても、ログに出力される“ERROR”や“error"を検知することができないのです。
当然と言えば当然ですが、もしこの意図通りにしたい場合は、出力され得る「エラー」のパターンをすべて網羅してフィルタに設定する必要があります。
サブスクリプションフィルター
もうひとつのフィルターは“サブスクリプションフィルター”といい、フィルタで検知したログの内容をLambdaやKinesisを始めとしたサービスに送ることができるものです。
Lambdaについてはこれまでの記事で何度か扱っていますが、プログラムを組む必要こそあれど、それ故に自由に処理を行なうことができるため、CloudWatch Logsからのログを処理してメール通知を行なうようなLambdaを作れば、通知内容にサブスクリプションフィルターで検出したログの内容を含めることができるようになります。
ただしサブスクリプションフィルターには、メトリクスフィルターとは異なる欠点もあります。
メトリクスフィルターは1つのロググループに対していくつでも設定することができますが、サブスクリプションフィルターは1つのロググループに2つまでしか設定することができません。
そのため様々な条件でログを検出したい場合、サブスクリプションフィルターを増やすのではなく、フィルタの構文に工夫を加えて検出できるようにする必要があります。
フィルタの構文、フィルターパターンの書き方
CloudWatch Logsで設定できるふたつのフィルターについて解説しましたが、フィルターの設定、フィルターパターンの書き方についてはどちらも共通しています。
フィルターパターンの構文についてのドキュメントは公式のものが以下の通りにありますが、ここではそれを要約、また公式ドキュメントにはないパターンを紹介します。
まずフィルターパターンには大きく分けて“通常のフィルターパターン”と“スペース区切りフィルターパターン”があります。
通常のフィルターパターンは、例えば単一のキーワード、あるいは複数のキーワードすべてがある場合を検知するような時は、以下のように構文を記述します。
(なお、スペース区切りフィルターパターンの“msg”の部分に関しては、任意の文字列を入れてもよいです)
- 単一のキーワードがあることを検知する場合(検知対象:ERROR)
①通常のフィルターパターンの場合
ERROR
②スペース区切りフィルターパターンの場合
[(msg = "*ERROR*")]
- 複数のキーワードがすべてあることを検知する場合(検知対象:ERROR , ARGUMENTS)
①通常のフィルターパターンの場合
ERROR ARGUMENTS
②スペース区切りフィルターパターンの場合
[(msg = "*ERROR*" && msg = "*ARGUMENTS*")]
通常のフィルターパターンはキーワードを直書きするだけで記述できますが、スペース区切りフィルターパターンの場合は細かく条件を書く必要があります。
スペース区切りフィルターパターンで、複数設定した全てのキーワードが存在することを設定するには、以上の通り“&&”で設定をつなげていきます。
こういった、設定したすべてのキーワードがあった時に検知するような場合は、殆どの場合は単純化できる通常のフィルタパターンを使うことになるでしょう。
① 検知するキーワードのいずれかがある時に検知するパターン
通常のフィルターパターンでは、各キーワードの初めに“?”を付けることで、設定したキーワードのいずれかがログにあった場合に検出できるようになります。
スペース区切りフィルターパターンの場合は、キーワードを以下のように“||”でつなげることで、同様のことが可能です。
①通常のフィルターパターンの場合
?warning ?error
②スペース区切りフィルターパターンの場合
[(msg = "*warning*" || msg = "*error*")]
② 検知すべきキーワードの他に、検知から除外するキーワードがある時は検知しないパターン
検知したいキーワードがあっても、同時に含まれていたら検知せずに正常扱いとしたいキーワードを設定したい場合は、そのキーワードの初めに“-”を付けることで、そのキーワードがあるログをフィルター検知から除外することができます。
スペース区切りフィルターパターンの場合は、検知するためのキーワードとは別の括りで、条件を“!="としてキーワードを設定することで特定キーワード除外のパターンにできます。
その際、検知するキーワードと除外するキーワードの括りを“&&”でつなぐ必要があります。
①通常のフィルターパターンの場合
ERROR -ARGUMENTS
②スペース区切りフィルターパターンの場合
[(msg = "*ERROR*") && (msg != "*ARGUMENTS*")]
③ 検知するキーワードのいずれかがあり、検知から除外するキーワードがない場合に検知するパターン
簡単に言えば①と②を併用するパターンであり、そしてフィルターパターンを設定する際に一番引っかかりやすいポイントです。
これまでの流れから、通常のフィルターパターンの場合なら検知したいキーワードの初めに“?”を、除外したいキーワードの初めに“-”を付ければできるのではないかと想像するかと思いますが、残念ながらそれはできません。
例えば以下のようにフィルターパターンを設定した場合、“?warning”や“?error"が無視されてしまい、“-work_user”が含まれるログ以外が全て検知されてしまう条件になってしまいます。
?warning ?error -work_user
公式ドキュメントには記載されていませんが、“?”によるキーワード指定は、すべてのキーワードに“?”が付けられている場合に限られます。
なのでこういった「指定したいずれかのキーワードがあったら検知する」と「指定したいずれかのキーワードがあったら検知から除外する」は、通常のフィルターパターンでは設定できないのです。
ではスペース区切りフィルターパターンの場合はどうかというと、その場合は以下のようにすることで指定ができます。
[(msg = "*warning*" || msg = "*error*") && (msg != "*work_user*")]
このことから、より細かいフィルターを組む場合は、スペース区切りフィルターパターン形式で実装する必要があると言えます。 いずれのパターンでも、通常のフィルタパターンとスペース区切りフィルターパターンでは同一の動作になるため、ケースに応じてどちらのフィルターパターンを利用するかは都度検討となるでしょう。
最後に
フィルターパターンの設定は、単純に設定する分には何となく通常のフィルタパターンを使うだけで簡便に設定できる優れた機能ですが、実際の運用では検知頻度や範囲の調整で詳細に設定することとなり、最終的にスペース区切りフィルターパターンの方を使うことが多くなる印象です。
サブスクリプションフィルターは当然のこと、メトリクスフィルターの設定においても、エラーだけでなく特定の操作があった場合に検知するというようなパターンを作る場合は、自ずとスペース区切りフィルターパターンを使っていくことになるでしょう。
それでは、今回はここまで。
また次回の記事でお会いしましょう。