おすすめアイテムをいくつか紹介

広告ブロックがONになっていると表示されないことがあります。

【LogsInsights】parseとstatsでログ解析を捗らせよう

みなさまはLogsInsightsで
ログ解析をされますでしょうか?
そのなかで、parseコマンドを使っていますでしょうか。

AWS基盤のシステムでログ調査を行う際、
LogsInsightsを使うことが多いと思います。

ログ内容はログ設計次第で
かなり見やすさが変わるものかと思います。

すべてが見やすいログ設計であると
解析作業をする身としては嬉しいです。
が、そういうわけにもいかないです。
もちろん、システムによっては少々解析しにくいログも存在しています。

ユノミン

余談ですが、json形式で出力されているログはデータとして扱いやすいけど、シンプルな文字列のみのログは解析目線だと扱いにくいなと思います。

そこで今回は、LogsInsightsで
シンプルな文字列ログを解析するのに役立つコマンドを
サンプル付きで紹介していきます。

parseコマンドとstatsコマンドってなにもの?

クエリコマンドに関する説明は
以下の公式ドキュメントで紹介されています。

その中でparseコマンドとstatsコマンドについては、
それぞれ以下のように説明されています。

  • stats
    • stats を使用して、ログフィールド値で集約統計を計算します。
  • parse
    • parse を使用して、ログフィールドからデータを抽出し、クエリで処理できるエフェメラルフィールドを作成します。
CloudWatch Logs Insights のクエリ構文

文章を読むだけじゃイメージ沸かない、という方…。私もそうでした。

なので自分の中で噛み砕いで実践的に
理解した内容をアウトプットしたいと思います。

サンプルログを使ってparseとstatsの実例を解説します

例えば死活監視として定期的に
HTTPステータスを拾っているアプリがあるとします。
そのアプリケーションは
下記のようなログメッセージを出力しています。

このログ文字列をパターン別に解析してみようと思います。

parseコマンドで文字列を分割する

まずログ文字列を集計可能な情報単位に分割したいです。
そのために、parseコマンドを使います。

先ほどのサンプルログを
情報単位に分割するためには、
下記クエリを実行します。

クエリ①「文字列分割クエリ」
parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| display result, id, url, http_status, message
  • 1行目のparseコマンドですが、ざっくりと説明すると正規表現を使用して抜き取りたい情報を新しいフィールド名に定義し直しています。
    本クエリでは@messageフィールドに存在する文字列のうち、*(アスタリスク)に該当する文字列をas句以降に定義した名前で新しいフィールド名を作成します。
  • 続けて2行目ですが、しれっとdisplayコマンドを使っています。
    今回の使い所でいうと、1行目のparseコマンドのas句以降のフィールドを表示用に指定しているクエリになります。
クエリ①出力結果

今回しれっと使ったdisplayコマンドは
公式ドキュメントで下記の通り解説されています。
本記事でも今後度々活用します。

  • display
    • display を使用して、クエリ結果の特定のフィールドを表示します。
CloudWatch Logs Insights のクエリ構文

特定の@messageだけparseしたい場合

ログにはparseしたい文字列以外の
フォーマットも混ざっているかもしれません。
例えば下記のように、開始と終了が記録されているログがあるとします。

もしこのようなログの中で
特定のメッセージのみparseしたい場合は、
filterコマンドを付け足しましょう。
下記はfilterコマンドの条件を使用したサンプルになります。

クエリ②「filter条件を使用した文字列分割クエリ」
filter @message =~ /(\[OK\]|\[NG\])/
| parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| display result, id, url, http_status, message
  • クエリ①「文字列分割クエリ」の1行目にfilterコマンドを足しています。
    =~ /(\[OK\]|\[NG\])/は正規表現による条件指定を実施しており、ログメッセージ内に[OK]あるいは[NG]が含まれているメッセージのみを抽出するように条件指定しています。
クエリ②出力結果
ユノミン

filterで絞り込んだので、クエリ①と同じ結果が出力されるようになりました。

statsコマンドで分割した情報を集計する

無事、parseコマンドで文字列から
情報を分割し抽出できるようになりました。
この抽出した情報をstatsコマンドを使ってパターン別に集計してみようと思います。

HTTPステータス別の監視結果数を集計したい

まずはシンプルにHTTPステータス別の集計を実施したいと思います。

クエリ③「シンプルな集計クエリ」
parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| stats count(*) by http_status
  • 1行目のparseコマンドは先ほど使用したものを同じクエリを流用します。
    今後の集計クエリにおいても、このparseコマンドは同じものを使用していきます。
  • 2行目のstatsコマンドで、HTTPステータス別のcount数を集計しています。
    by句に指定しているhttp_statusは、1行目のparseコマンドで抜き取ったHTTPステータスそのものが格納されています。
クエリ③出力結果

一時間ごとの監視結果を単一のHTTPステータス別に集計したい

先ほどまでのクエリ①〜③では
クエリ上での条件で時間指定を行なっていないため、
LogsInsights側の指定時間範囲内で集計が行われていました。

これを、傾向や推移を確かめるために一時間単位の集計クエリへ変更したいと思います。

まずは特定の単一HTTPステータスで一時間単位の集計クエリを書いてみます。

クエリ④「単一HTTPステータスの場合の集計クエリ」
parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| filter http_status = 200
| stats count(*) by bin(1h)
  • クエリ③「シンプルな集計クエリ」との差分は下記2点です。
    • filterコマンドの追加
    • statsコマンドのby句をbin(1h)に変更
  • 言い換えると、先ほどのstats count(*) by http_statusでは「HTTPステータス別に集計してね」でしたが、今回のstats count(*) by bin(1h)「一時間毎に集計してね」という指示に変わったというわけです。
  • ただし、無作為に「一時間毎に集計してね」としても「なにを??」となるのが明白です。なので、statsコマンドの前にfilterコマンドで集計したいmessageを絞り込んでいます。

なお、filterコマンドを使わずに下記のようなクエリでも同様の結果が得られます。
各々の好みですが、こちらの方がスッキリして見やすいかもですね。

parse @message '[*] id:*, url:*, http:*, error_no:*, msg:*' as result, id, url, http_status, message
| stats sum(http_status = 200) by bin(1h)
クエリ④出力結果

bin(1h)が一時間単位を示しています。一時間単位以外にも一日単位や15分単位といった刻み方で集計が可能です。

一時間ごとの監視結果を複数のHTTPステータス別に集計したい

次に、単一HTTPステータスの場合の集計クエリを応用して、
複数のHTTPステータスをまとめて集計したい場合のクエリを書いてみます。

こちらはクエリの書き方によって集計条件を縦軸にまとめる方法と、
集計条件を縦軸と横軸にわけてまとめる方法がありますので、
それぞれ記載していきます。

集計条件を全て縦軸に収める場合

まずは、集計条件を全て縦軸に収めても構わないクエリです。

クエリ⑤「複数条件を縦軸にまとめた場合の集計クエリ」
parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| stats count(*) as count_num by http_status, bin(1h) as bin_1h
| sort bin_1h, http_status
  • クエリ④「単一HTTPステータスの場合の集計クエリ」との差分は下記です。
    • statsコマンドのby句でbin(1h)に加えてhttp_statusを追加
  • statsコマンドのby句は複数フィールドが指定できます。
    これを使用し、「HTTPステータスを一時間単位に集計してね」という複数指示が可能になります。
  • なお、stats count(*) as count_numでas句を指定していますが、必須ではありません。
    as句で別名定義しておくことでsortコマンドやdisplayコマンドで扱えるようになりますので、つけておいて損はないです。
クエリ⑤出力結果

出力結果の列の並び順を調整したい場合はdisplayコマンドを使いましょう。
例えば、上記のクエリにdisplayコマンドを指定して列を入れ替えるだけでも見やすさが変わってくると思います。

parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| stats count(*) as count_num by http_status, bin(1h) as bin_1h
| sort bin_1h, http_status
| display bin_1h, http_status, count_num

このクエリの場合の出力結果は下記に変わります。

集計条件を縦軸と横軸にわける場合

では、クエリを少し書き換えて
マトリクス表のような出力結果を
アウトプットしようと思います。

クエリ⑥「複数条件を縦軸と横軸にわける場合の集計クエリ」
parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| stats
  sum(http_status = 200) as count_200,
  sum(http_status = 404) as count_404,
  sum(http_status = 500) as count_500 by http_status, bin(1h) as bin_1h
| sort bin_1h, http_status
| display bin_1h, count_200, count_404, count_500
  • クエリ⑤「複数条件を縦軸にまとめた場合の集計クエリ」との主な差分は下記です。
    • statsコマンドでHTTPステータス別にsum関数での集計を追加
  • statsコマンドで条件別の集計を実施することで、その集計結果がそれぞれ列ごとに表示されるようになります。
  • なお上記クエリ内でもそれぞれas句で別名定義していますが、こちらもas句は必須ではありません。
クエリ⑥出力結果

URL別にHTTPステータスを集計したい

先ほどまでのクエリでは、
HTTPステータスのみを集計していました。
これを、さらにURL別に集計したいと
なった場合のパターンを見ていきたいと思います。

クエリ⑦「URL別にHTTPステータスを集計したクエリ」
parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| stats
  sum(http_status = 200) as count_200,
  sum(http_status = 404) as count_404,
  sum(http_status = 500) as count_500 by url
  • これまでのHTTPステータスのみを集計していたクエリとの差分は下記です。
    • statsコマンドでhttp_statusをsum関数で集計を実施
    • statsコマンドのby句をurlに変更
  • これまではHTTPステータス別の集計だったのでby句にhttp_statusを指定し「HTTPステータス別に集計してね」という指示をだしていました。
    ここに「URL別にも集計したいなあ」となった場合は、statsコマンド内のsum関数の集計とby句の指定が肝になってきます。
  • 言い換えると、本クエリでは「HTTPステータス別の集計結果をURL単位で見せてね」ということを指示しています。
クエリ⑦出力結果

一時間ごとに特定URLのHTTPステータスを集計したい

では、URL別の集計クエリも傾向や推移を確かめるために
一時間単位の集計クエリに変更したいと思います。

クエリ⑧「一時間ごとにURL毎のHTTPステータスを集計したクエリ」
parse @message '[*] id:*, url:*, http:*, msg:*' as result, id, url, http_status, message
| stats
  sum(http_status = 200) as count_200,
  sum(http_status = 404) as count_404,
  sum(http_status = 500) as count_500 by url, bin(1h) as bin_1h
| sort bin_1h, http_status
| display bin_1h, url, count_200, count_404, count_500
  • クエリ⑦「URL別にHTTPステータスを集計したクエリ」との差分は下記です。
    • statsコマンドのby句でurlに加えてbin(1h)を追加
    • displayコマンドでbin(1h)urlstatsコマンドの集計情報を必ず記載
  • statsコマンドにてsum関数による集計及びby句での複数フィールド指定を活用した、これまで紹介してきたクエリの集大成です。
  • こちらも言い換えると、「HTTPステータス別の集計結果を一時間毎にURL単位で見せてね」ということを指示しています。
クエリ⑧出力結果

最後に

いかがでしたでしょうか?

LogsInsightsのクエリは
公式ドキュメントを読んでいても
どことなく応用的に使えなかったため、
ちゃんと時間をかけて踏み込もうとしないと
なかなか理解力が深まらないなと感じました。

本記事がLogsInsightを使ったログ調査を
もっと捗らせたい方々の参考になりましたら幸いです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA