🔍

CloudWatch Logs のログを Athena で手軽に分析する

2021/09/24に公開

Athena の Federated Query を使用して, CloudWatch Logs に溜め込んだログを最小限の手間で分析する方法と,そのためのログストリーム設計のちょっとしたコツについて紹介します.

目的

CloudWatch Logs はログを溜め込むのにはたいへん手軽でよいサービスです.
たとえば私がいま携わっているプロダクトでは,操作ログやバッチの実行ログなど,件数が多くなりがちで参照頻度の低いものは CloudWatch Logs にとりあえず放り込んでいくような運用をしています.

CloudWatch Logs は構築・実装・運用にかかるコストが低く,導入もしやすい反面,単体では分析がしにくいため, Athena などを使って柔軟にクエリを書きたいところです.
しかし, Athena でクエリできるようにするためにすべてのログを継続的に S3 にエクスポートするような仕組みを作るのは,構築にかかる手間や運用にかかるコストが見合わないことがあります.

Athena の Federated Query を使用して,構築や運用に手間をかけず,かつ必要なときにはすぐに柔軟なクエリで分析できるようにする方法と, Athena での分析に適したログストリーム設計例を紹介します.

Federated Query とは?

Athena Query Engine のバージョン 2 では, Federated Query という機能を使用することができます.
これは, S3 以外の RDB, NoSQL, その他のサービスなどと Athena を接続し, S3 のデータと同じように Athena 上で分析できるようにする機能です.

利用するには Athena と接続先のサービスをつなぐためのコネクタとよばれる Lambda を用意する必要がありますが, CloudWatch などは公式のコネクタが提供されており,コンソールからわずかな操作で設定を済ませることができます.

Federated Query には

  • view が構築できない
  • write オペレーションができない

などの制約はありますが,頻度の低いログのアドホックな分析用途では十分でしょう.

Federated Query の設定

詳しい設定方法は割愛しますが, Athena コンソール上のデータソースの画面で「データソースを接続」を押して,接続先に Amazon CloudWatch Logs を選択することで設定できます.
基本的には画面にしたがっていくつか必要事項を入力するだけで設定が完了します.

CloudWatch Logs のデータソース

設定が完了すると,Athena 上で新しいデータソースが使用できるようになっているはずです.

データソースの中身には,

  • CloudWatch Logs のロググループごとに 1 つのデータベース
  • ロググループ内のログストリームごとに 1 つのテーブル

が用意されています.

all_log_streams テーブル

ログストリームごとのテーブルの他にもうひとつ,ロググループ内のすべてのログストリームに横断的にアクセスするための all_log_streams という特別なデーブルも作成されます.
私は分析したいログストリームが 1 つに特定できない場合も多いので, all_log_streams テーブルをメインで使っています.

作成されるテーブルの情報

作成されたテーブルには下記の 3 つの列が含まれています.

  • log_stream : ログストリーム名
  • time : unixtime
  • message : ログに記録されているメッセージ

time は桁数が多いためすこし扱いにくく, from_unixtime 関数にそのまま渡すと正しい日時に変換できません.

from_unixtime(time / 1000.0, 'Asia/Tokyo')

のように 1000 で割ると正しい日時が得られます.

クエリを書くときのコツ

JSON メッセージの分析

Federated Query で作成される message 列は単なる文字列として扱われています.CloudWatch Logs のメッセージに JSON を格納している場合は, json_extract 関数などの JSON 操作関数を使って詳しい分析を行えます.

たとえば,

{
  "Foo": {
    "Bar": {
      "field1": 100
    }
  }
}

のような json 文字列が message 列に格納されている場合にfield1 の値を取り出すには

json_extract(message, '$.Foo.Bar.field1')

のようにします.

Athena における JSON の扱いについて,詳しくは 公式ドキュメント に書かれています.

ログストリームの絞り込み

Federated Query で作成されるテーブルは log_stream 列でパーティション化されています.
all_log_streams テーブルで複数のログストリームを横断的に分析する場合にも,適切にログストリーム名を指定することでスキャンの量や実行時間を最低限に抑えることができます.

ULID を使用した Athena に優しいログストリーム設計

ログストリームの絞り込みをするときに,たとえば GUID のような完全にランダムなログストリーム名を使用していた場合,対象となるログストリーム名をすべて列挙する必要があります.
必要なログストリームの範囲が広い場合,このようなランダムなログストリーム名では不便なことがあります.

ログ分析の際には対象となる期間が決まっていることが多いです.こういった用途には,ログストリームの名前は一意性を担保しつつも時刻順にソート可能になっていると,ログストリームの絞り込みがしやすく大変便利です.
こういった用途では ULID が便利です.

ULID とは,「時刻順にソート可能なランダムな 128 bit の ID」です.ULID は上位 48 ビットにタイムスタンプが格納されており,残りの部分はランダムなビット列で構成されます.
このため,ULID を使えば実用的には十分な一意性を持ったソート可能な ID を簡単に作ることができます.
ULID の実装は各言語でいろいろとありますが,私は C# で CySharp による実装 を使用しています.

ログストリーム名に base64 エンコードした ULID を使用しておくと,

  • 分析したい期間の最初のログストリーム名が AXveDqLXTQBeH+xaUtnLKQ==
  • 分析したい期間の最後のログストリーム名が AXwXnfcfH+EoOyVHDjDdsA==

となっていた場合には, Athena のクエリの WHERE 句に

where log_stream >= `AXveDqLXTQBeH+xaUtnLKQ==` log_stream <= `AXwXnfcfH+EoOyVHDjDdsA==`

などと指定して,簡単にログストリームの範囲を指定できるようになります.

まとめ

CloudWatch Logs に溜め込んだログを Athena で簡単に分析する方法について紹介しました.

要点:

  • Athena の Federated Query で CloudWatch Logs のデータを直接 Athena で扱うことができる
  • JSON 文字列は json_extract などの関数で分析できる
  • ログストリーム名には ULID などのソート可能なものを使用しておくと便利

私のプロダクトではこの仕組みは最近導入したばかりなので,運用上の課題などが見つかればまた追記などしようと思います.

Discussion