Amazon Cloudwatch のログ監視メールにログメッセージを出力する

# この記事でやったこと
CloudWatch のログ監視でメールを飛ばした際、ログメッセージを記載して飛ばす。
以下は「error」を2件検知して、メールを飛ばしたメールの本文。
```
検知したメッセージは以下

May 20 09:33:21 ip-10-0-1-121 ec2-user[1786]: 2022年 5月 20日 金曜日 09:33:21 JST error
May 20 09:33:25 ip-10-0-1-121 ec2-user[1789]: 2022年 5月 20日 金曜日 09:33:25 JST error
```

# 目次
1. [この記事でやったこと](#この記事でやったこと)
1. [実装背景](#実装背景)
1. [LambdaのPythonスクリプト](#lambdaのpythonスクリプト)
1. [Pythonスクリプトの補足1](#pythonスクリプトの補足1)
1. [Pythonスクリプトの補足2](#pythonスクリプトの補足2)
1. [Pythonスクリプトの補足3](#pythonスクリプトの補足3)
1. [Pythonスクリプトの補足4](#pythonスクリプトの補足4)
1. [EC2のコマンド参考1](#ec2のコマンド参考1)
1. [EC2のコマンド参考2](#ec2のコマンド参考2)
1. [Lambdaでもしも無限ループを起こしてしまった場合](#lambdaでもしも無限ループを起こしてしまった場合)


# 実装背景
CloudWatch の通常のログ監視を SNS を使ってメールを通知すると非常に見辛い。
以下の記事を参考にしました。
https://dev.classmethod.jp/articles/cwl-lambda-sns-publish/

実装までの手順なども上記通りに実装しました。
しかしながら、メールを飛ばした際に私の環境では以下の課題がありました。
<font color="DeepPink">・連続でログを検知しても、1行しか通知されない
・2byte文字(日本語)が文字化けされて通知されてくる</font>

# LambdaのPythonスクリプト
以下のPythonスクリプトをLambdaで動かす事で課題を解決できました。

```cwl-to-sns-publish.py
import logging
import json
import base64
import gzip
import boto3
import os

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    sns_topic_arn = os.environ['SNS_TOPIC_ARN']  # 環境変数よりSNSトピックARN取得
    sns_client = boto3.client('sns')

    # CloudWatchLogsからのデータはbase64エンコードされているのでデコード
    decoded_data = base64.b64decode(event['awslogs']['data'])
    # バイナリに圧縮されているため展開
    json_data = json.loads(gzip.decompress(decoded_data))
    logger.info("EVENT: " + json.dumps(json_data))

    # 1行目の内容は直書き
    i = 0
    message = "検知したメッセージは以下\n\n"
    
    # 検知メッセージの1つ目を取得
    message_output = message + json_data['logEvents'][i]['message'] + "\n"

    #メッセージ数をカウントしつつ処理する
    while True:
        try:
            i = i + 1
            print(json_data['logEvents'][i]['message'])
        except:
            break
        else:
            message_output = message_output + json_data['logEvents'][i]['message'] + "\n"
    
    # SNS件名設定
    log_group = json_data['logGroup']
    log_stream = json_data['logStream']

    #ロググループ名、ログストリーム名をsubjectに設定
    subject = log_group + ' ' + log_stream

    response = sns_client.publish(
        TopicArn = sns_topic_arn,
        Subject = subject,
        Message = message_output
    )
```

# Pythonスクリプトの補足1
何を出力しているか不明な場合は、好きなところに print を入れておきましょう。
Lambda のログ画面に print の出力結果が出てきます。
```python:print例
    print(json_data['logEvents'][i]['message'])
    print(i)
```

# Pythonスクリプトの補足2
json_data['logEvents'][i]['message'] は i=0 だとリストの1行目を読み込みます。
リストの1行目は、検知メッセージの1つ目を取得する事になります。
i=1の場合は、リストの2行目、検知メッセージの2つ目を取得する事になります。
```python
    message_output = message + json_data['logEvents'][i]['message'] + "\n"
```

# Pythonスクリプトの補足3
検知メッセージが1つしかない状況で、以下のように検知メッセージの2つ目を
取得しにいこうとすると Python がエラーとなり、Lambda は強制終了してしまうようです。
```python
    message_output = message + json_data['logEvents'][1]['message'] + "\n"
```

# Pythonスクリプトの補足4
存在しないリストにアクセスすると強制終了となるので、例外処理を入れています。
あえて print(json_data['logEvents'][i]['message']) を実施してエラーになった場合は break で終了させます。
エラーにならなかった場合は、message_output に追記させています。
それを while True でループさせています。
```python
    while True:
        try:
            i = i + 1
            print(json_data['logEvents'][i]['message'])
        except:
            break
        else:
            message_output = message_output + json_data['logEvents'][i]['message'] + "\n"
```


# EC2のコマンド参考1
EC2上でログを出すコマンド例をいくつか

```sh:EC2 の /var/log/messages に error を出す
$ sudo logger "error"
```

```sh:EC2 の /var/log/messages に 1分置きに error を出す
$ sudo logger "error" ; sleep 60
```

```sh:EC2 の /var/log/messages に 1分置きに時刻表示含めた error を出す
$ sudo echo `date` "error" | logger ;sleep 60
```

# EC2のコマンド参考2
EC2上でログを垂れ流しで見れる例
「EC2のコマンド参考1」とは別のターミナルを立ち上げておきます

```sh:EC2 の /var/log/messages の表示をリアルタイムで出しておく
$ sudo tail -f /var/log/messages
```

# Lambdaでもしも無限ループを起こしてしまった場合
https://open-groove.net/lambda/throttling-lambda-function/
こちらの記事を参考にし、同時実行数を0にして無限ループを止めました。
無限ループ止まったかな？というのを確認したあとにデフォルトの1000に元戻しして動かしました。