CloudWatch Logsでフィルターを使ってみる

記事タイトルとURLをコピーする

こんにちは、SWX3人目の熊谷(悠)です。
下図のような流れでアプリケーションログをSlackへ通知する仕組みが既にあるとします。
app_err_log-slack_cloudwatch.jpg

この仕組み上で通知するログの種類をフィルタリングする場合はLambdaトリガー設定時のフィルタかLambda関数内になるかと思います。
しかし、不必要な通知をLambda関数内でフィルタリング処理を行う場合、Lambda関数自体を動作させる必要がある為、料金が多く発生してしまいます。
これを避ける為、Lambdaトリガー(やCloudWatch Logs メトリクスフィルター)のフィルターパターンを駆使してフィルタリングを行う方法を記します。

フィルターとパターンの構文について

フィルターは以下3種類に分類できます。※公式名称ではありません。
本稿では「テキストベースフィルター」及び「スペース区切りフィルター」について取り扱います。

  • テキストベースフィルター  ※全文一致
  • スペース区切りフィルター  ※OR パターンマッチング
  • JSONフィルター

出来ない事

公式ドキュメントに記載が無かったりして困った点は下記の通りです。

  • フィルターパターンに正規表現が扱えない。

Q: 正規表現をログデータに使用できますか?
Amazon CloudWatch のメトリクスフィルタでは、正規表現はサポートされていません。
よくある質問 - Amazon CloudWatch | AWSより

  • テキストベースフィルターにて、ORパターンマッチングと語句の除外を同構文中で表現したくてもORパターンマッチングが無視される。(もしくは構文エラーとなる)

※スペース区切りフィルターではOR演算子「||」や除外(否定)演算子「!=」等が同構文中で併用できます。

CloudWatch Logs におけるフィルタパターンで OR パターンマッチングをご使用頂く場合、「?」を付与した語句と「-」を付与した語句とをフィルタパターン内で併用致しますと、現状では「?」が付与された語句が無視されるという動作となる
AWSサポート回答より※2019/07/26時点

  • スペース区切りフィルターにて括弧に対する除外が出来ない。(構文エラーとなる)

エラー例:[A && !(B || C)]
※ただし、以下を用いて対応可能です。
改善例:[A && (!B && !C)]

ORで複数除外条件を記載したい場合
!(P || Q) = !P && !Q
ANDで複数除外条件を記載したい場合
!(P && Q) = !P || !Q
ド・モルガンの法則より

出来る事

公式ドキュメントに記載の通りスペース区切りログイベントにてカンマで区切った任意の文字列を全て角括弧[]で囲むと一致する位置(順番)のフィールドのフィルターパターンとなります。
逆に、角括弧に囲まれたカンマで区切られていない任意の文字列はフィールド全体に対するフィルターパターンとする事ができます。

CloudWatch Logs のフィルターにつきましては、JSON ログイベントもしくはスペース区切りログイベントから値を取得することができます。
今回のお客様のフィルター構文の場合、スペース区切りログフィルターとなりますが、構文の中に , (カンマ) を入れず、区切らないことによりログイベントの全体をひとつのフィールドとして認識させているものとお考えいただければと存じます。
AWSサポート回答より※2019/08/07時点

例えば以下のような3件のログイベントが登録されているとします。

127.0.0.1 - frank [10/Oct/2000:13:25:15 -0700] "GET /apache_pb.gif HTTP/1.0" 200 1534
127.0.0.1 - frank [10/Oct/2000:13:35:22 -0700] "GET /apache_pb.gif HTTP/1.0" 500 5324
127.0.0.1 - frank [10/Oct/2000:13:50:35 -0700] "GET /apache_pb.gif HTTP/1.0" 200 4355

ログ例に対して以下のフィルターを行うと1行目と3行目が一致します。

[ip, user, username, timestamp, request, status_code = 200, bytes]

次に、以下のフィルターを行った場合、2行目(status_codeの50)と3行目(timestamp列の50)が一致します。

[hoge = *50*]

フィルターパターン作成

上項ログ例は綺麗に列が揃っているスペース区切りログイベントでしたのでスペース区切りフィルターが綺麗に適用できますが、今回は都合上、以下のようなアプリケーションエラーのスタックトレースが1つのイベントとして登録されてしまいました。
このような場合は単純なスペース区切りフィルターの作成が困難なのでフィールド全体に対するスペース区切りフィルターを使用します。

org.apache.catalina.connector.ClientAbortException: java.io.IOException: Connection reset by peer
    ~(中略)~
    at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:214)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:39)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:412)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1388)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:426)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:479)

ここで、上項の出来る事と出来ない事を掛け合わせる事で、AまたはBの中から、CまたはDまたはEを除外しつつ、FかつGである場合も除外するといったフィルタリングが可能になります。
[(A || B) && (!C && !D && !E) && (!F || !G)]

例えば、以下のようなフィルターパターンでは、

  1. Exceptionもしくはexceptionがメッセージに含まれる全てのイベントの中から、
  2. hogehugaController.javayokuErrorDeruClass.javaテストというメッセージが含まれるイベントを除外し、
  3. ClientAbortExceptionかつConnection reset by peerが含まれるイベントも除外した後に残ったイベントだけがマッチするようになります。
[(str = "*Exception*" || str = "*exception*")
 && (str != "*hogehugaService.java*" && str != "*yokuErrorDeruClass.java*" && str != "*テスト*")
 && (str != "*ClientAbortException*" || str != "*Connection reset by peer*")]

確認

AWS CloudWatchコンソール上から ロググループ(ログ) > [フィルター対象のロググループ] > [フィルター対象のログストリーム] を選択します。
作成した構文を下図赤枠の「イベントをフィルター」項目に入力し、項目右側の検索する日付範囲を指定するとLambdaトリガーのフィルタ設定時と同様の結果が得られます。 2020-08-27_17h33_23.png

以下旧インターフェイスの場合 CloudWatch Logsフィルター.JPG

まとめ

フィルターパターンを駆使して不必要なログを除外する事で料金や通知の量を減らして運用コストを下げましょう!

参考

フィルターとパターンの構文 - Amazon CloudWatch Logs

熊谷 悠司 (記事一覧)