Aurora MySQL の一般ログや監査ログを CloudWatch Logs 経由でファイル取得する

こんにちは。インサイトテクノロジーの松尾です！

本投稿では Amazon Aurora MySQL の一般ログや監査ログを CloudWatch Logs 経由でファイル取得する方法を紹介します！

## はじめに

Aurora MySQL の一般ログや監査ログは AWS コンソールなどからダウンロードできますが、状況により CloudWatch Logs に一度出力させ、そこからログを取得したいというケースもあると思います。本投稿では、CloudWatch Logs 経由で、通常のコンソールからと同じ書式でファイルダウンロードする方法を紹介します。

## 背景

Aurora MySQL の一般ログには、ディスク領域の 15% が使用されるとローテーション時に削除されるという仕様があり、例えば 1 時間分に相当する 1 ファイルのログ量で 15% を超えてしまうような場合には、ローテーション後にファイルがすぐに削除されてしまい、ログファイル全体をダウンロードすることもできません。また、ローテーションの頻度(時間やログファイルサイズ)を調整するようなパラメータもありません。監査ログもルールは異なりますが、ローテーションされて自動で消されていく仕様は同じです。

参考：[Aurora MySQL データベースログの概要](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/USER_LogAccess.MySQL.LogFileSize.html)

> Aurora MySQL エラーログファイルのサイズは、DB インスタンスのローカルストレージの 15 パーセント以下に制約されます。このしきい値を維持するために、ログは 1 時間ごとに自動的にローテーションされます。Aurora MySQL は 30 日後、またはディスク領域の 15 % が使用されると、ログを削除します。古いログファイルを削除した後、ログファイルの合計サイズがしきい値を超えている場合、ログファイルのサイズがしきい値以下になるまで、最も古いログファイルから順に削除されます。

そのような場合には、以下の 2 種類の手段を選択可能です。
* `TABLE` に出力する
* CloudWatch Logs に出力する

本投稿では、後者の CloudWatch Logs へ一度出力し、そこから通常のログファイルと同じフォーマットでファイルとして取得する方法を紹介します。例として一般ログで実施しますが、監査ログでも同じ手順で行うことができます。

## 必要なこと
本目的を達成するのに必要な手順は以下です。
* 一般ログを CloudWatch Logs へ出力
* CloudWatch Logs からログを取得してファイル出力

## 一般ログを CloudWatch Logs へ出力
* Aurora 作成時に CloudWatch Logs へ一般ログを出力するように指定します。これはチェックをつけるだけなので非常に簡単です。 \
![create-db.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/469d5fcf-6183-4a35-32f4-5c5cc1a2fe27.png)
* チェックをつけて Aurora クラスターを作成すると、以下のように、クラスター名のついたロググループが作成され、その中にインスタンス名のついたログストリームが作成されます。ログの中身を見ると、実際に、いつも `general.log` ファイルに出力されていたものと同様の内容が出力されています。 \
![cloudwatch-logs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480312/2e86b96c-141e-2801-7082-1945425ebf66.png)

## CloudWatch Logs からログを取得してファイル出力
さてこの内容をダウンロードしたいのですが、AWS コンソールから全体を一度にダウンロードするのは簡単ではありませんでした。そのため、ここでは、以下の投稿で紹介されている方法少しアレンジし、python を使ってダウンロードする手法を紹介します。

参考：[CloudWatch Logs がもっと簡単にDLできたらいいのに…と思った方へ（スクリプト付き）](https://qiita.com/ozzy3/items/fd79d07f42215298e38d)

### 事前準備
本投稿で紹介しているスクリプトを実行するには以下の環境が整っている必要があります。
* python3 が利用可能であること
* boto3 が利用可能であること
* 実行環境が aws から CloudWatch Logs のアクセス権限を有していること
  * EC2 の場合にはロールがアサインされている
  * AWS CLI で事前に `aws configure` コマンドで認証情報をセットする (または、`~/.aws/credentials` ファイルを手動で作成しておく)

参考：[Credentials - Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html)

### スクリプト

先述の参照記事の一部を以下のように変更します。これは CloudWatch Logs から情報を取得してファイル出力する際に、CloudWatch Logs で付与しているタイムスタンプ情報もファイル出力していたのを削除しているのと、複数のログを API で取得した際に、改行をつけて出力するように変更しているものです。


```diff_python
                # ログのタイムスタンプとメッセージを抽出
-               messages = [datetime.fromtimestamp(event.get('timestamp')/1000).isoformat()
-                           + '\t' + event.get('message') for event in events]
+               messages = [event.get('message') + '\n' for event in events]

                # ファイル出力
                f.writelines(messages)
```

### 実行

以下で実行できます。実行にはロググループ名、ログストリーム名を指定します。

```bash
python3 download_logs.py <LOG_GROUP> <LOG_STREAM>
```

実際のファイル出力は以下のような内容を含んだテキストファイルが、実行ディレクトリに出力されます。これは、ファイルとして出力される `general.log` と同じ形式です。

```
2023-07-14T01:57:58.301103Z 3013 Query	select d.deptno,d.dname,e.empno,coalesce(e.ename,'(no employee)') ename from dept d left outer join emp e on d.deptno=e.deptno order by deptno,empno
```

上記の一部を修正したコードをgithubにあげています。参考になればと思います。

https://github.com/takumats/download-cloudwatch-logs

### おまけ：結合とソートと重複排除

ここまでで実行でログストリームごとにファイル化されています。特に監査ログは複数ファイルに分散されてログストリームも作成されていますのでファイルの結合とソートを必要に応じて行いましょう。
※もし重複行があるようであれば重複排除も

```bash
cat xxxx.audit.log.* > audit_log.txt
sort audit_log.txt > audit_log_sorted.txt
uniq audit_log_sorted.txt > audit_log_sorted_uniq.txt
```

## おわりに

改めてですが、以下の投稿のおかげで、CloudWatch Logs のファイル出力を非常に簡単に行うことができました。

参考：[CloudWatch Logs がもっと簡単にDLできたらいいのに…と思った方へ（スクリプト付き）](https://qiita.com/ozzy3/items/fd79d07f42215298e38d)

また、それをベースに、ファイル出力と同じ形式で CloudWatch Logs から取得する手順を紹介しました。Aurora PostgreSQL については未確認ですが、おそらく同じ感じで行けるのではと想像しています。

また、今回の方法では指定ログストリームを全件出力しているのですが、範囲を指定したいケースもあると思います。今回使用している、`get_log_events` ではなく `filter_log_events` だと時刻の指定も可能なようなので、いずれ試してみたいと思います。

参考：[CloudWatchLogs - Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs/client/filter_log_events.html)

## 追記 (2024/03)

* もともとの投稿では一般ログに限定した書き方としていましたが、監査ログなどでも同じなのでそのように修正しました
* githubにソースコードを公開しました