AWS
CloudWatch
aws-cli
CloudWatch-Insight

AWS-CLI & CloudWatch Insightでログを集計する


概要

とある案件でCloudWatch Logsの集計をAWS-CLI & CloudWatch Insightを用いて行うことになったので、その方法を共有します。

定期的に集計をレポーティングすることを想定しているため、最終的な出力はcsvを想定しています。


CloudWatch Ingihtとは?


新しいサービス CloudWatch Logs Insights がお役に立ちます。これはクラウドスケールで動作するよう設計され、セットアップやメンテナンスが不要なフルマネージドのサービスです。これは大量のログを数秒で走査し、インタラクティブなクエリの実行と可視化を提供します。あらゆるログフォーマットや、JSON ログから自動検出したフィールドを扱えます。使ってみると分かるように、これは大変フレキシブルであり、ログを調査するためのお気に入りのツールの一つになるでしょう。

公式ドキュメントより抜粋。


ログの検索自体はすでに実装されているfilter_log_eventsで近しいことはできます。

しかしこれにはクエリ言語が設定されておらず、ちょっと工夫した検索をしたい場合はとても使いづらいものになります。

CloudWatch Insightは独自の検索クエリ言語を持っており、多様なログフォーマットの検索を柔軟に実行できます。

非常に強力なツールです。しかし、後述の問題点があります。


背景

この強力なツールは当然各種AWS-SDKに含まれていると思うでしょう?

実は本記事執筆時点でCloudWatch InsightはSDKに含まれていないのです。

だから定期的に集計バッチ処理を行おうとすると、シェルスクリプトとAWS-CLIで頑張るしかないのです。

ということで、この解説記事を書きました。


必要なパッケージ


  • AWS-CLI ver.1.16以上(このバージョンじゃないとInsightが使えない)

  • jq(レスポンスを処理するために使う)

インストール方法は割愛します。

なお、実行環境はMacかec2上とします。


ソース

実際に動作するソースを見ながら解説していきます。

サンプルプログラムでは、あるロググループに対して昨日中の集計を行うことを目的としています。


#!/bin/sh
alias date='gdate'

YESTERDAY=`date -d '1 days ago' '+%Y/%m/%d'`
START_TIME=`date -d $YESTERDAY' 00:00:00 9 hours ago' '+%s'`
END_TIME=`date -d $YESTERDAY' 23:59:59 9 hours ago' '+%s'`

#必要な設定
CLOUDWATCH_LOG_GROUP_NAME="ここにロググループ名"
REGION="ap-northeast-1"
QUERY="fields @timestamp, @message | limit 10"

echo 'Start aggregation.'

QUERYID=`
aws logs start-query \
--log-group-name $CLOUDWATCH_LOG_GROUP_NAME \
--start-time $START_TIME \
--end-time $END_TIME \
--query-string "$QUERY" \
--region $REGION \
--output text
`

#終了を待つ
printf 'Wait for complete'
while :
do
STATUS=`aws logs get-query-results --query-id $QUERYID --output text --region $REGION | sed -n '1p'`
if [ "$STATUS" = "Complete" ]; then
break
fi
printf "."
sleep 1s;
done

#終了したら結果をjsonで出力
aws logs get-query-results --query-id $QUERYID --output table --region $REGION
RESULT_JSON=`aws logs get-query-results --query-id $QUERYID --output json --region $REGION`

#マッチ件数が1件以上ならjsonをcsvに変換し、results以下に保存する
MATCHED=`echo $RESULT_JSON | jq '.statistics.recordsMatched'`
if [ $MATCHED -gt 0 ] ; then
#
#header書き込み
echo $RESULT_JSON \
| jq '.results[0]' \
| jq -r -c '([ .[].field | values ]) | @csv' \
> ./result.csv

#内容書き込み
echo $RESULT_JSON \
| jq '.results[]' \
| jq -r -c '([ .[].value | values ]) | @csv' \
>> ./result.csv
fi

echo 'Aggregation has completed.'


解説


前準備


#!/bin/sh
alias date='gdate'

YESTERDAY=`date -d '1 days ago' '+%Y/%m/%d'`
START_TIME=`date -d $YESTERDAY' 00:00:00 9 hours ago' '+%s'`
END_TIME=`date -d $YESTERDAY' 23:59:59 9 hours ago' '+%s'`

#必要な設定
CLOUDWATCH_LOG_GROUP_NAME="ここにロググループ名"
REGION="ap-northeast-1"
QUERY="fields @timestamp, @message | limit 10"

ここではクエリ実行に必要な変数を定義しています。

注意したいのは期間指定であり、ここはUTC標準時のタイムスタンプを設定する必要があります。

なぜならAWS-CLIにおけるCloudWatch Insightの期間指定は、タイムスタンプで指定する必要があるからです。

これは他のlogs関連クエリでも同様です。

今回の例では前日の丸一日を走査の対象としています。

Insightは走査したログのデータ量によって料金が変わってきますので、不要に長い期間を設定するのは時間と金の無駄遣いと心得ましょう。

なお、冒頭のgdateのエイリアスはMacでは必要ですが、ec2で動かすなら不要です。


クエリの実行


QUERYID=`
aws logs start-query \
--log-group-name $CLOUDWATCH_LOG_GROUP_NAME \
--start-time $START_TIME \
--end-time $END_TIME \
--query-string "$QUERY" \
--region $REGION \
--output text
`

ここからが本題です。AWS-CLIでCloudWatch Insightを実行するためのコマンドは aws logs start-queryとなっています。

詳しくは公式ドキュメントを見る必要がありますが、ここでは使用しているパラメータについて解説します。



  • log-group-nameはそのままロググループ名です。必須です。


  • start-timeは集計開始のタイムスタンプです。必須です。


  • end-timeは集計終了のタイムスタンプです。必須です。


  • query-stringはクエリ本文です。必須です。


  • regionはリージョンです。configでデフォルト値を設定している場合は不要です。


  • outputは出力形式です。このコマンドの出力結果は実行結果を得るためのクエリIDなので、単にテキストを取得できればいいので指定しています。

このパラメータで注意したいのは、期間指定のタイムスタンプはということです。

というのも他のCloudWatch logsの取得コマンドではマイクロ秒を指定する場合があるためです。

例えばfilter-log-eventsでは期間指定がマイクロ秒となっているので注意してください。ややこしいな!


クエリ実行完了の待機

while : 

do
STATUS=`aws logs get-query-results --query-id $QUERYID --output text --region $REGION | sed -n '1p'`
if [ "$STATUS" = "Complete" ]; then
break
fi
printf "."
sleep 1s;
done

#終了したら結果をjsonで出力
aws logs get-query-results --query-id $QUERYID --output table --region $REGION
RESULT_JSON=`aws logs get-query-results --query-id $QUERYID --output json --region $REGION`

start-queryを実行するとAWSのほうで集計タスクが走りますが、これはすぐに完了するものではありません。

完了するまでクエリIDを用い、レスポンスを確認する処理が必要です。

スクリプト側はget-query-resultコマンドをクエリIDを指定して実行することで、集計ステータスおよび集計結果を得ることができます。

出力形式をテキストにしている場合、レスポンスの1行目は単なるテキストRunningとなり、完了するとcompleteとなります。よってレスポンスがRunningの間はループを実行しています。

ステータスがcompleteになった後、出力形式をテーブルにした上で結果を表示しています。

最終的には出力形式をjsonにした上で変数に格納します。

集計結果がjsonで得られれば十分であればここで完了となります。

次のパートから実用的と思われるcsvに変換します。


結果の処理

#マッチ件数が1件以上ならjsonをcsvに変換し、results以下に保存する

MATCHED=`echo $RESULT_JSON | jq '.statistics.recordsMatched'`
if [ $MATCHED -gt 0 ] ; then
#
#header書き込み
echo $RESULT_JSON \
| jq '.results[0]' \
| jq -r -c '([ .[].field | values ]) | @csv' \
> ./result.csv

#内容書き込み
echo $RESULT_JSON \
| jq '.results[]' \
| jq -r -c '([ .[].value | values ]) | @csv' \
>> ./result.csv
fi

echo 'Aggregation has completed.'

ここは本題から外れるので詳しくは解説はしません。

awscliの出力はtext, json, tableと選択できます。

簡単なメッセージのみ取得したいのであればtextで十分ですし、データを視覚的に認識したい場合はtableで出力します。

今回はcsvとして最終的な結果を得るために、処理をしやすいjson形式で出力しています。

この部分はstatistics.recordsMatchedが0以上の場合、jsonからcsvに変換しています。

statistics.recordsMatchedは条件に一致した件数をさします。


最後に

そのうちSDKでもCloudWatch Insightが利用できるようになると思います。

おそらく、このような定期実行によるレポーティングをするベストプラクティスは下記のような構成でしょう。


  • 実行環境はLambda、Pythonならboto3ライブラリでクエリを実行する

  • CloudWatch EventsでLambdaを定期実行する

  • 検索クエリはs3に配置して、集計結果もs3にアップロード

私が作った集計バッチプログラムも、実行環境がec2上という以外は上にならっています。

直近で必要な方はここのソースを参考にしてみてください。