Python (Boto3) @ Lambda で CloudWatch Logs の特定のログストリームにログを出力する

# ■ はじめに

Python (Boto3) @ [Lambda](https://aws.amazon.com/jp/lambda/) から [CloudWatch Logs](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html) の特定のログストリームにログを出力したかったので、やってみました。

# ■ コード

コードは、以下。

```python
def put_logs(client, group_name, stream_name_prefix, message):
    try:
        exist_log_stream = True
        log_event = {
            'timestamp': int(time.time()) * 1000,
            'message': message
        }
        sequence_token = None
        for i in range(2):
            try:
                if exist_log_stream == False:
                    create_log_stream_response = client.create_log_stream(
                        logGroupName = group_name,
                        logStreamName = stream_name_prefix)
                    exist_log_stream = True
                if sequence_token is None:
                    put_log_events_response = client.put_log_events(
                        logGroupName = group_name,
                        logStreamName = stream_name_prefix,
                        logEvents = [log_event])
                else:
                    put_log_events_response = client.put_log_events(
                        logGroupName = group_name,
                        logStreamName = stream_name_prefix,
                        logEvents = [log_event],
                        sequenceToken = sequence_token)
            except client.exceptions.ResourceNotFoundException as e:
                exist_log_stream = False
            except client.exceptions.DataAlreadyAcceptedException as e:
                sequence_token = e.response.get('expectedSequenceToken')
            except client.exceptions.InvalidSequenceTokenException as e:
                sequence_token = e.response.get('expectedSequenceToken')
            except Exception as e:
                print(e)
                break
    except Exception as e:
        print(e)
```

# ■ `sequenceToken` の取得の仕方

こちらの記事 ( [Lambda \(Python\) から特定のログストリームにログを書こうとして苦戦した2つのポイント \- Qiita](https://qiita.com/kter/items/9db2e9d3544681ef2c1c) ) にもありますが、`put_log_events` でログを `PUT` する際、例外はありますが、基本的には `sequenceToken` を設定する必要があります。

- [put_log_events - CloudWatchLogs — Boto3 Docs 1\.16\.15 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs.html#CloudWatchLogs.Client.put_log_events)
    - 新しく作成されたログストリームでのアップロードには、シーケンストークンは不要。

## `describe_log_streams` による `sequenceToken` の取得

記事の中では `describe_log_streams` によって、ログストリームの存在判定と `sequenceToken` の取得を行っており、`describe_log_streams` のレスポンスから `nextToken` として取得しています。

- [describe_log_streams  - CloudWatchLogs — Boto3 Docs 1\.16\.15 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs.html#CloudWatchLogs.Client.describe_log_streams)

ですが、実は [CloudWatch Logs のクォータ \- Amazon CloudWatch Logs](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html) にも書かれていますが、`describe_log_streams` にはクォータによる制限があります。

- `DescribeLogStreams`
    - 1 リージョン、1 アカウントあたり 5 件のトランザクション/秒 (TPS)。
        - クォータの引き上げをリクエスト可。

その為、使い方にもよりますが、出力頻度が高い場合は、クォータの制限に引っ掛かり遅延が発生することがあり、実際、[Lambda](https://aws.amazon.com/jp/lambda/) から結構な頻度でログを出力したところ、`describe_log_streams` での遅延が発生してしまいました。

## `put_log_events` による `sequenceToken` の取得

そこで、[AWS サポート](https://aws.amazon.com/jp/premiumsupport/) に相談したところ、`put_log_events` による `sequenceToken` の取得を提案されました。

[put_log_events - CloudWatchLogs — Boto3 Docs 1\.16\.15 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs.html#CloudWatchLogs.Client.put_log_events) にも記載されていますが、`InvalidSequenceTokenException` から`expectedSequenceToken` として取得出来るみたいです。

もちろん `put_log_events` にもクォータの制限はありますが、`describe_log_streams` より緩めです。

- `PutLogEvents`
    - 1 秒、1 ログストリームあたり 5 リクエスト。
        - 追加のリクエストは調整され、クォータは変更不可。
    - 1 リージョン、1 アカウントあたり 800 件のトランザクション/秒 (TPS)。
        - クォータの引き上げをリクエスト可。

今回のやり方にしたところ遅延が解消されましたので、使い方にもよりますが、出力する頻度が多い場合は `put_log_events` を使ったほうが良い様に思います。

逆に、今回のやり方でも遅延が発生が解消されない場合は、別のやり方を検討する必要があるかもしれません。

# ■ 出力先のログストリームの存在確認

上述した記事 ( [Lambda \(Python\) から特定のログストリームにログを書こうとして苦戦した2つのポイント \- Qiita](https://qiita.com/kter/items/9db2e9d3544681ef2c1c) ) では、`describe_log_streams` で、`sequenceToken` の取得だけではなく、ログストリームの存在確認も行っていましたが、クォータの制限により `describe_log_streams` は使えませんので、別のやり方で確認する必要があります。

ただ、これも `put_log_events` の `Exceptions` で確認出来るみたいです。`ResourceNotFoundException` に入ってきたらログストリームが存在しないということで、`create_log_stream ` を実行します。

# ■ ハマりポイント

- 上述しましたが…、最初は問題無かったのにリクエスト頻度が増えるに連れて [Lambda](https://aws.amazon.com/jp/lambda/) の実行時間が伸びて非常に困りました。当然、コストにも跳ね返ってきますしね…。

- とりあえず、[AWS サポート](https://aws.amazon.com/jp/premiumsupport/) さんに相談して良かったです。[Boto3 documentation — Boto3 Docs 1\.16\.16 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) に記載されているものの、相談しなかったら、見つけられなかった気がします…。どうもありがとうございました。🙇‍♂️🙇‍♂️🙇‍♂️

# ■ まとめ

参考になれば♪

👋👋👋