[AWS] CloudWatch Logsを監視し、Slackに通知するLambda関数の例

# 概要
Lambdaを使用してCloudWatch Logsを監視し、Slack通知を作ったときのLambda関数のソースの共有です。

以下の解説は省略します。

- CloudWatch Logsへログの出力
- Lambda関数の登録方法
- Slack Incoming Webhookの設定


# Lambda関数(Python3)
LaravelのログをJSON形式にしたものを処理しています。
ご自身の環境に合わせて修正してください。

```python
# -*- coding: utf-8 -*-
import base64
import json
import urllib.request
import zlib

def lambda_handler(event, context):
    # print("Received event: " + json.dumps(event, indent=2))
    data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
    data_json = json.loads(data)

    try:
        for item in data_json['logEvents']:
            # ↓↓↓ JSONの処理。適宜修正してください。 ↓↓↓
            # JSONフォーマット以外は処理しない
            try:
                log_json = json.loads(item['message'])
                # print(log_json)
            except json.JSONDecodeError as e:
                continue

            # JSONフォーマットが合わないものは無視
            if not 'level' in log_json.keys():
                continue
            # ログレベルが300(WARNING)以下はSlack通知しない
            if log_json['level'] <= 300:
                continue

            # Slack通知内容の整形
            log_text = '[ロググループ] ' + data_json['logGroup'] + '\n'
            log_text += '[ログストリーム] ' + data_json['logStream'] + '\n'
            log_text += '[ログレベル] ' + log_json['level_name'] + '\n'
            log_text += '[発生日時] ' + log_json['datetime'] + '\n'
            log_text += '[エラー内容]\n' + json.dumps(log_json['context'], ensure_ascii=False, indent=4, sort_keys=False, separators=(',', ': ')) + '\n'
            # print(log_text)
            # ↑↑↑ JSONの処理。適宜修正してください。 ↑↑↑

            send_data = {
                "username": "Lambda bot",
                "text": log_text,
            }
            send_text = "payload=" + json.dumps(send_data)

            request = urllib.request.Request(
                'https://hooks.slack.com/services/xxx',
                data=send_text.encode("utf-8"),
                method="POST"
            )
            with urllib.request.urlopen(request) as response:
                response_body = response.read().decode("utf-8")

    except Exception as e:
        print(e)
```

### JSONログの例
以下はCloudWatch Logsで収集しているログのサンプルです。

```JSON
{
    "message": "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'xxx' in 'where clause' (SQL: xxx)",
    "context": {
        "exception": {
            "class": "Illuminate\\Database\\QueryException",
            "message": "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'xxx' in 'where clause' (SQL: xxx)",
            "code": 42,
            "file": "/var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:671",
            "trace": [
                "/var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:631",
                〜略〜
            ],
            "previous": {
                "class": "Doctrine\\DBAL\\Driver\\PDO\\Exception",
                "message": "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'xxx' in 'where clause'",
                "code": 42,
                "file": "/var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDO/Exception.php:18",
                "trace": [
                    "/var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:83",
                    〜略〜
                ],
                "previous": {
                    "class": "PDOException",
                    "message": "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'xxx' in 'where clause'",
                    "code": 42,
                    "file": "/var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:78",
                    "trace": [
                        "/var/www/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:78",
                        〜略〜
                    ]
                }
            }
        }
    },
    "level": 400,
    "level_name": "ERROR",
    "channel": "local",
    "datetime": "2021-02-15T10:38:44.466256+09:00",
    "extra": {}
}
```