CloudWatchのエラーログをSlackに通知する

いちいちLaravelのログを見るのがめんどくさいと思いCloudWatch Logsに転送しました。しかし、Slackに通知させた方がより便利だと思ったので今回は下記のような構成で行きます。

＊ AWS SNSが抜けているのでいつか変更します(2022年08月08日)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/166a50ad-a07b-a1aa-ec46-f4c1f7ad3174.png)


## 通知したいロググループに対してメトリクスフィルターを作成する

CloudWatchにて通知したいEC2ののロググループを選択し、「 アクション 」「 メトリクスフィルターを作成する 」をクリック

![スクリーンショット 2021-04-28 20.04.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/133dc39a-c533-b3cd-e848-a1ad216578bc.png)

フィルターパターンはERRORログを出力したいのでERRORとします。そして、メトリクスフィルターを作成しましょう。


## SNSのトピックを作成しましょう

CloudWatch LogsのログをSNSのメッセージとして発行し、Lambda(サブスクリプション)がSlackに通知するようにします。
![スクリーンショット 2021-04-28 20.14.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/0680f74d-eac5-a524-ddc7-f55075947f17.png)

任意の名前でさくっと作りましょう。


## Lambdaの処理

Lambdaの構成はこんな感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/f4890531-02a8-cb5d-cef1-7264c1f6d4d3.png)

SNSには作成したトピックを割り当てます。


```python:lambda_function.py

import json
import urllib.request
import logging
from collections import OrderedDict
import os
import datetime
import calendar
import boto3

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

#抽出するログデータの最大件数
OUTPUT_LIMIT=5
#何分前までを抽出対象期間とするか
TIME_FROM_MIN=5

def lambda_handler(event, context):
    
    logger.info("Event: " + str(event))
    message = json.loads(event['Records'][0]['Sns']['Message'])
    
    logs = boto3.client('logs')

    # MetricNameとNamespaceをキーにメトリクスフィルタの情報を取得する。
    metricfilters = logs.describe_metric_filters(
        metricName = message['Trigger']['MetricName'] ,
        metricNamespace = message['Trigger']['Namespace']
    )

    logger.info("Metricfilters: " + str(metricfilters))
    #ログストリームの抽出対象時刻をUNIXタイムに変換（取得期間は TIME_FROM_MIN 分前以降）
    #終了時刻はアラーム発生時刻の1分後
    timeto = datetime.datetime.strptime(message['StateChangeTime'][:19] ,'%Y-%m-%dT%H:%M:%S') + datetime.timedelta(minutes=1)
    u_to = calendar.timegm(timeto.utctimetuple()) * 1000
    #開始時刻は終了時刻のTIME_FROM_MIN分前
    timefrom = timeto - datetime.timedelta(minutes=TIME_FROM_MIN)
    u_from = calendar.timegm(timefrom.utctimetuple()) * 1000

    # ログストリームからログデータを取得
    response = logs.filter_log_events(
        logGroupName = metricfilters['metricFilters'][0]['logGroupName'] ,
        filterPattern = metricfilters['metricFilters'][0]['filterPattern'],
        startTime = u_from,
        endTime = u_to,
        limit = OUTPUT_LIMIT
    )

    # メッセージを整形しつつslackに通知
    for event in response['events']:
        postText = '''
        {logStreamName}
        {message}
        '''.format( logStreamName=str(event['logStreamName']),
                    message=str(event['message'])).strip()

        logger.info("Response: " + str(response))
        
        send_data = {
            "text": postText,
        }
        
        send_text = json.dumps(send_data)

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



```

## IAMロールの設定

IAMロールにて先ほど作成したLambda関数にポリシーをアタッチします。

![スクリーンショット 2021-04-28 20.32.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/cde82a03-2677-34ce-6171-0e3212b2fcbe.png)

そして、AWSLambdaBasicExecutionRoleをクリックしてにポリシーの編集を行います。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "------------------------------------------"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeMetricFilters"
            ],
            "Resource": [
                "-------------------------------------------"
            ]
        }
    ]
}
```


## 　CloudWatch Alarmを設定します

　CloudWatch Alarmにてアラームを作成します。設定は以下の通りです。しきい値が１以上を超えたらSNSトピックに送信するように設定します。

![スクリーンショット 2021-04-28 20.50.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/6511ddca-171c-b042-c5a7-a4aff8f15c41.png)

　送信するSNSトピックは先ほど作成したものを選択

![スクリーンショット 2021-04-28 20.54.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/955fe7cb-fe64-8115-2da9-c25fb88455ae.png)





## 　Slackの設定

　https://w1618578007-hxz964946.slack.com/intl/ja-jp/apps
を開き、「 マストアプリ 」をクリックし、検索窓で「 Incoming Webhook 」と検索

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/266bc2c6-da24-65f6-2226-5346078f7517.png" width="70%">

　そしてSlackに追加して通知させたいチャンネルを選択します。その後、生成された<strong>Webhook URLをコピーしましょう。</strong>のちに使います。


## 　最後に確認します

![slack.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/39172713-1fd4-7794-741b-42bd032a988e.jpeg)

意図的にログを吐くようにして、このような通知が来れば完了です。アイコンやアプリ名はカスタマイズすることも可能ですよ。

## 　参考文献

・ https://qiita.com/yahagin/items/2248287c5285cd1e9201
・ https://blog.chakimar.net/aws-chatbot-cloudwatch-logs/