サブスクリプションフィルターで、あるLambdaの出力したログを、別のLambdaに渡す

[フューチャー Advent Calendar 2021](https://qiita.com/advent-calendar/2021/future)の23日目の記事です。

昨日は、[@hayao0727](https://qiita.com/hayao0727) さんによる
[AxumでGoのチュートリアル「Writing Web Application」をやってみた](https://qiita.com/hayao0727/items/d05b4c3c0b05d219c3bc) でした。

私自身、今年のフューチャーのアドカレ枠では既に2本投稿しているのですが、ラストに向けてガンバっていきます。

投稿した2本はこちら

- [AWS KMSとOpenSSLでエンベロープ暗号化](https://qiita.com/RuyPKG/items/21d397516c2df2f0c45a)
- [【入門】QuickSight × S3 でデータ可視化](https://qiita.com/RuyPKG/items/c32282fe57c33346a534)

# はじめに
みなさんは、CloudWatch Logsの便利機能である「[サブスクリプションフィルター](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/Subscriptions.html)」を利用したことはありますか？

ざっくりと

- 1つのロググループに、最大2つまで設定可能である
- 予め指定した「フィルターパターン」に一致する文字列が、ログに出力されたら起動する
- Lambda, Kinesis, Kinesis Data Firehose に向けて、フィルターがキャッチした文字列（正確には文字列を含むログ全文）を配信する

という機能です。

「特定のログが出力されたら、追加処理を実行したい」という要件が出てきた場合（例えば、`error` の文字列がログ出力されたら、チケットを起票するなど）、コンピュートリソース自体に追加処理を加えずとも、サブスクリプションフィルターを設定するだけで、欲しいログを Lambda や Kinesis に送信できます。

今回は以下のシステム構成で

![Screen Shot 2021-12-22 at 23.40.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/28854239-44e7-3d94-d2c3-921297709a20.png)

- main-lambda の出力したログを
- サブスクリプションフィルターで拾い上げて
- sub-lambda で取得する

までの流れを追っていきます。

# 環境用意
今回の検証環境は

1. main-lambda
2. sub-lambda
3. main-lambda → sub-lambda に向けたサブスクリプションフィルターの設定

という流れで準備していきます。

## main-lambdaの作成
1つ目のLambdaを作成して、メッセージ文をログ出力する処理を追加します。

```lambda_handler.py
import json

def lambda_handler(event, context):
    message = "Hello CloudWatch Logs subscription filters."
    print(message)
    
    return {
        'statusCode': 200,
        'body': json.dumps(message)
    }
```

Lambda のデプロイ後、コンソール画面でテスト実行 or awscliコマンドで実行すると、自動で `/aws/lambda/main-lambda` のロググループが作成されます。
CloudWatch のコンソール画面からロググループを手動追加しても問題ありませんが、面倒なので Lambda を手動テスト起動して、ロググループを自動生成します。

## sub-lambdaの作成
次は、サブスクリプションフィルターの配信を受ける側の Lambda を作成していきます。

```lambda_handler.py
import json
import base64
import gzip

def lambda_handler(event, context):
    # base64 デコード
    decoded_data = base64.b64decode(event['awslogs']['data'])
    
    # gzip 解凍
    decompressed_data = gzip.decompress(decoded_data)
    json_data = json.loads(decompressed_data)
    
    message = json_data['logEvents'][0]['message']
    print(message)

    return {
        'statusCode': 200,
        'body': json.dumps(message)
    }

```

コード解説

サブスクリプションフィルターでの配信データは、`{ "awslogs": {"data": "BASE64ENCODED_GZIP_COMPRESSED_DATA"} }` という形式であり、data キーは

- gzip 形式で圧縮
- Base64 でエンコード

された状態で届きます。

そのため、生データを取得するには、受け手側で「Base64 のデコード、gzip の解凍」処理が必要です。
生データ取得後の処理内容は、サブスクリプションフィルターの設定内容が影響するため、後半のパートで説明します。

## サブスクリプションフィルターの設定
CloudWatch のコンソール画面から、`/aws/lambda/main-lambda` のログループに移動して、サブスクリプションフィルターの設定を選択します。

![Screen Shot 2021-12-23 at 0.16.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/3fefdb37-7084-10c6-91a8-8654c11f568a.png)

いくつかの項目選択画面が表示されるため、以下情報を入力します。

送信先を選択
- Lambda 関数: sub-lambda

ログ形式とフィルターを設定
- ログの形式: AWS Lambda
- サブスクリプションフィルターのパターン: Hello
- サブスクリプションフィルター名: Detect Hello

![Screen Shot 2021-12-23 at 0.19.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/5d751b89-22d9-d046-3b80-3388170185fb.png)

今回は「サブスクリプションフィルターのパターン」を **Hello** で設定したので、`/aws/lambda/main-lambda` のログに Hello の文字列が出力されたとき、サブスクリプションフィルターが起動し、sub-lambda が起動する、という起動フローになります。

項目入力後、「ストリーミングの開始」を選択すると、フィルターの設定状況が確認できます。

![Screen Shot 2021-12-23 at 0.25.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/07753abd-f9c1-54b9-e5a3-1991ebf69ce2.png)

以上により、環境準備は完了です。

# 実行
現状をおさらいすると、

- **Hello** の文字列を含むログが `/aws/lambda/main-lambda` に出力されたとき、サブスクリプションフィルターが起動して、sub-lambda が起動する

という状態です。

![Screen Shot 2021-12-22 at 23.40.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/28854239-44e7-3d94-d2c3-921297709a20.png)

つまり、main-lambda が Hello の文字列をログ出力さえすれば、後は後続処理が自動で動く状態になっています。
さっそく main-lambda をテスト起動して、ログを出力させます。

（手動起動後の出力ログ）

![Screen Shot 2021-12-23 at 0.38.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/0d3fabad-148f-e53e-54a0-822d3eaf0c70.png)

想定どおり、Hello の文字列が出力されていますね。

このとき、サブスクリプションフィルター → sub-lambda には、以下フォーマットの jsonデータ が渡されます。

```raw.json
{
    "messageType": "DATA_MESSAGE",
    "owner": "<aws account>",
    "logGroup": "/aws/lambda/main-lambda",
    "logStream": "2021/12/22/[$LATEST]8ac0982efa7e40e78d6702702b842a7e",
    "subscriptionFilters": [
        "Detect Hello"
    ],
    "logEvents": [
        {
            "id": "<log events id>",
            "timestamp": 1640181063036,
            "message": "Hello CloudWatch Logs subscription filters.\n"
        }
    ]
}
```

前半の sub-lambda のコードで、以下のような処理がありました。

```lambda_handler.py
message = json_data['logEvents'][0]['message']
print(message)
```

この参照は、サブスクリプションフィルターが渡す jsonデータ に依拠したもので、sub-lambda が受け取ったメッセージを表示するためのものです。

ログを見ると、サブスクリプションフィルターの送信データが、sub-lambda 側で受信できていることが確認できました。
Hello という特定の文字列だけを拾うのではなく、「Hello 文字列を含めた、ログイベントに出力された全文」が渡っていることも分かります。

![Screen Shot 2021-12-23 at 0.46.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/67c52ac5-dc16-5bab-187d-927f428d522f.png)

以上で、
「サブスクリプションフィルターで、あるLambdaの出力したログを、別のLambdaに渡す」
の動作検証は終了です。

# おわりに
本記事では、サブスクリプションフィルターを利用して、Lambda のログを配信し、別の Lambda に渡す処理を見ていきました。
既にこの機能を知っている人からすれば「何を今更」という感じかもしれませんが、AWS 初心者が意外と知らずに苦労していたりするケースが見られたので、ブログ化してみました。

以上、長文にお付き合いいただき、ありがとうございました。

明日は、[@kmd252525](https://qiita.com/kmd252525) さんです！