📒

CloudWatch Logs Insights でログをグループ化してデータ抽出する

2022/06/21に公開

環境

Amazon CloudWatch Logs

やりたいこと

一連の処理の中で出力されたログを結合して(グループ化して、と言った方が正確かもしれない)、キーワードなどで絞り込んで、その中のデータを抽出したい。

例えば、特定のエラーが起きたリクエストのパラメタをログから拾いたいが、エラーメッセージと拾いたいデータが別々のログで出力されている、という状況を考える。トレース用の ID によって、ログの関連性は分かるものとする。

// パラメータのログ
{
  "aws.request_id":"59d96452-1dd4-4ffe-946f-b10601a35b1b",
  "dd.trace_id":"1295437772323842924",
  "dd.span_id":"1295437772323842924",
  "msg":"handler started",
  "request":{
    "method":"POST",
    "param":{
      "name":"hoge",
      "age":-1
    },
    "path":"/api/user"
  },
  ...
}
// エラーメッセージのログ
{
  "dd.trace_id":"1295437772323842924",
  "dd.span_id":"1295437772323842924",
  "error":"invalid argument: age must be greater than or equal to 0",
  "msg":"bad request",
  ...
}

単純なログ検索で解決しようとすると、
エラーメッセージでログを検索し、そのログに含まれるトレース用の ID を拾い、そのトレース ID でもう一度ログを検索し、検索結果の中から欲しいデータを探す、
という手順を、そのエラーが起きた分だけ繰り返さなくてはならず、大変面倒である。

CloudWatch Logs Insights でデータ抽出

CloudWatch Logs Insights を使うと、ログのグループ化とデータ抽出を一気にできる。

上記のログに対するクエリの例を以下に示す。

例1

「invalid argument」というエラーの出たリクエストの ID を抽出する。

クエリ:

stats sortsFirst(aws.request_id) as RequestID, sortsFirst(error) as Error by dd.trace_id
| filter Error like /invalid argument/

検索結果:

|---------------------|--------------------------------------|-------------------------------------------------------------|
|     dd.trace_id     |              RequestID               |                            Error                            |
|---------------------|--------------------------------------|-------------------------------------------------------------|
| 1295437772323842924 | 59d96452-1dd4-4ffe-946f-b10601a35b1b | invalid argument: age must be greater than or equal to zero |
|---------------------|--------------------------------------|-------------------------------------------------------------|

ポイント:

  • stats ... by トレースID で、ログをトレース ID ごとにグループ化する
  • sortsFirst(フィールド名) でフィールドの値を取り出す
  • filter フィールド名 like /正規表現/ で正規表現にマッチするものだけに絞り込む

例2

エラーの起きたリクエストのパスとパラメタを抽出する。

クエリ:

parse @message /"param":(?<paramStr>\{[^}]*\})/
| stats sortsFirst(request.path) as Path, sortsFirst(paramStr) as Param, sortsFirst(error) as Error by dd.trace_id
| filter ispresent(Error)

検索結果:

---------------------------------------------------------------------------------------------------------------------------
|     dd.trace_id     |   Path    |          Param          |                            Error                            |
|---------------------|-----------|-------------------------|-------------------------------------------------------------|
| 1295437772323842924 | /api/user | {"age":-1,"name":"foo"} | invalid argument: age must be greater than or equal to zero |
---------------------------------------------------------------------------------------------------------------------------

ポイント:

  • parse @message /正規表現の名前付きキャプチャ/ でログメッセージからデータを抽出する
    • 名前付きキャプチャの構文は (?<名前>パターン)
  • stats ... by トレースID で、ログをトレース ID ごとにグループ化する
  • sortsFirst(フィールド名) でフィールドの値を取り出す
  • filter ispresent(フィールド名) で指定したフィールドが存在するものだけに絞り込む

注意点

CloudWatch Logs Insights は検索時に走査するログの量に応じてお金も時間もかかる。
なので、クエリをあれこれ試している間は、検索したいログが含まれているごく限られた時刻範囲を指定しておくと安心である。
長期間の大量のログを検索対象にしたい場合も、まずは短い時刻範囲で試してクエリの有効性を確認するとよい。

Discussion