Amazon CloudWatch Logs でログを読むための手法メモ

Amazon CloudWatch Logs でログを読むために学んだ手法をいくつかメモします。  
あまり高度な機能は利用せず、ECS や Lambda をほぼデフォルト設定で使って出力されたログを調査するような想定です。

# 前提となる概念について

CloudWatch でログを読む際には「ログイベント」、「ログストリーム」、「ロググループ」の概念について意味を理解している必要があります。

下記のドキュメントを引用しつつ、ごく簡単に各概念を説明します。

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CloudWatchLogsConcepts.html

## ログイベント

> ログイベントは、モニタリングされているアプリケーションまたはリソースによって記録されたアクティビティのレコードです。

![ログイベント](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273742/1b652c66-c7b5-f818-a898-f0e0273868cb.png)


AWS のコンソール画面ではこの画像のように表示されます。画像の各行がそれぞれログイベントです。

## ログストリーム

> ログストリームは、同じソースを共有する一連のログイベントです。

要するにログストリームとはログイベントが集まったもののようです。

![ログストリーム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273742/a14f9f9c-aaf8-661b-3c81-40be48ab9d94.png)

画像の各行が一つ一つのログストリームです。

## ロググループ

> ロググループは、保持、監視、アクセス制御について同じ設定を共有するログストリームのグループを定義します。

![ロググループ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273742/a066a565-b849-3a9d-c031-6ab01c04683d.png)


ロググループはログストリームの集まりです。画像の各行が一つ一つのロググループです。

まとめると 3 つの概念は`Log events` ∈ `Log streams` ∈ `Log groups`となると思います。

# CLI でログを読む

もちろんコンソール画面からでもログを読むことはできますが、CLI のほうが便利なときもあります。  
AWS CLI の CloudWatch Logs 関連コマンドをいくつか紹介します。

- 本稿はバージョン`2.3.1`で確認しています。
- AWS CLI はデフォルトでは JSON で結果を出力します。しかし、JSON で出力されると人間には読みにくいです。そのため、`--output text`を指定して多少読みやすい形式で出力させています。
  - 参考：[AWS CLI 出力フォーマットの設定](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-output-format.html)
- 必要な情報のみを取得するために`--query`を指定してフィルタリングしています。このクエリは JMESPath という構文が使われています。jq で使うルールと似ていますが、少し違うものです。
  - 参考：[AWS CLI 出力をフィルタリングする](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-filter.html)、[JMESPath Tutorial](https://jmespath.org/tutorial.html#jmespath-tutorial)
- Lambda のログストリーム名などは「$」が含まれることがあります。その場合適宜シングルクォーテーションで囲むなどしてください。（でないと変数展開しようとされます）

## ロググループを一覧にする

```bash
aws logs describe-log-groups --query "logGroups[].[logGroupName]" --output text
```

## ログストリームを一覧にする

`--log-group-name` の後ろに対象のロググループを指定します。

```bash
aws logs describe-log-streams --log-group-name /ecs/hello-spring --query "logStreams[].[logStreamName]" --output text
```

## ロググループ、ログストリームを指定して読む

`--log-group-name` と`--log-stream-name` を指定します。

```bash
aws logs get-log-events --log-group-name /ecs/hello-spring --log-stream-name ecs/minimal-spring/f4dd8cd7-0943-4a4f-b3b4-a13b7e5d9701 --query "events[].[message]" --output text
```

## tail する

`tail -f`のように追記を追いかけて CloudWatch のログを読むこともできます。

```bash
aws logs tail --since 5m --format short --follow /ecs/hello-spring
```

- `--format short`すると TimeStamp などの表示が消え、見やすくなります。
- since でその名の通りいつからのログを表示するか指定できます。デフォルトは 10 分です。

# ログをファイルでダウンロードする

実は前述の`get-log-events`コマンドではあるログストリームのログをすべて取得できるとは限りません。  
[AWS CLI のドキュメント](https://docs.aws.amazon.com/cli/latest/reference/logs/get-log-events.html)には

> By default, this operation returns as many log events as can fit in a response size of 1MB (up to 10,000 log events).

とあり、最大 1MB、もしくは 10,000 ログイベントしか取得できないようです。しかし、目 grep したい、オンプレのログファイルに対して行っているようなシェル芸を実行したい、などといったときにログ全体が必要になることもあります。  
この問題に直面する人は少なくないようで、検索するといくつかダウンロード方法を紹介するブログなどが見つかります。  
Qiita だと例えば

https://qiita.com/shinsaka/items/23a37873a23bdc2fc7fb

がログをローカルにダウンロードする Python スクリプトに関する記事になっています。  
この記事のPythonスクリプトを使うとログをダウンロードすることが可能です。

# CloudWatch Logs Insights を使ったログの検索

ログを検索したい、と思った場合、AWS コンソール画面でも各ログストリームのページに「インベントをフィルター」というフォームがあり、ログを検索できるようになっています。  
しかし、ログが多いと重すぎるのか結果がなかなか返ってこなかったり、返ってこなかったりと不便です。

このような場合に CloudWatch Logs Insights を使った検索が便利です。
ロググループに対して独自の文法によるクエリを発行できます。「インベントをフィルター」よりも動作が軽く、ログが多くてもきちんと結果が返ってきます。

![CloudWatch Logs Insights](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273742/299e976f-da90-ea0c-9433-03c23717c596.png)


特定ワードで検索するなら下記のようなクエリをクエリ入力フォームに書きます。

```sql
fields @message,@logStream
| filter @message like "HikariPool-1"
| sort @timestamp
| limit 25
```

`fields`には取得して表示するフィールドを記載します。SQL の SELECT 句に近いです。  
フィールドを複数にする場合は、

```sql
fields @message,@logStream
```

のようにカンマ区切りにします。  
`@message`はデフォルトで自動的に用意されるフィールドで「生の未解析のログイベント」を表します。（参考：[サポートされるログと検出されるフィールド](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData-discoverable-fields.html)）  
要するに該当したログが表示されます。

`filter`はその名の通りフィルタリングを行います。高度な指定が可能らしいですが、grep したいだけならば非常にシンプルな判定のみで十分です。  
想像できると思いますが、`@message like "HikariPool-1"`で部分文字列の一致のチェックを行っており、ログに`HikariPool-1`という文字列があれば対象となります。

`sort`はその名の通りソートします。SQL と同じように asc と desc を指定でき、デフォルトだと asc（昇順）になるようです。

`limit`も SQL と同じように返される結果の数を指定します。

「ロググループを選択」プルダウンでロググループを選択（複数可）し、「クエリの実行」ボタンを押下すると、例えば下記のような結果が取得できます。

![クエリの実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273742/66ecf2c4-b241-ef27-3e22-263241c57cad.png)


あるロググループ中で`HikariPool-1`という文字列を含むログイベントが検索できました。  
画像だと `fields` に`@message` だけを指定して実行していますが、`@logStream` も指定するとどのログストリームに所属するログイベントかも一目でわかる上に、クリックするとそのログストリームに遷移できるようにもなるため便利です。