cloudwatchlogs -> lambda -> SNSを試してみた

# はじめに

EC2でWebサイトのエラーログを受け取る時に、CloudWatch -> SNSだと、英語がよくわからなくて萎えるので、Lambdaで整形してからSNSでメール送信したい。
`Cloudwatchlogs -> lambda -> SNS`という構成でやる。

他にも、`Cloudwatchlogs -> kinesis -> lambda -> SNS`という方法があるが、kinesisを挟んでもあんまり旨みがないし（もちろんメリットが0ではない）、`kinesis -> lambda`はポーリングなので、イベントドリブンにできない欠点がある。


参考
[【新機能】Amazon CloudWatch LogsのログデータをAWS Lambdaでリアルタイムに処理する](http://dev.classmethod.jp/cloud/aws/real-time-processing-log-data-with-lambda/)
[CloudWatch Logs Subscriptionsを利用したZabbixへのログ転送](http://dev.classmethod.jp/cloud/aws/cloudwatch-logs-to-zabbix/)


# 構成

![logfilter1.png](https://qiita-image-store.s3.amazonaws.com/0/55672/eaf785c4-98f9-7cee-6353-6d30fea2ec5a.png)

* `EC2 -> CloudwatchLogs` は、エージェントを経由して行う
* データはloggroupでグループ化して管理する。（通知の際の切り分けがしやすいように）
* `CloudwatchLogs -> lambda` はsubscriptionを使って、ERROR,FATALだけに絞る（INFO,WARNを送るといっぱいリクエスト増えてお金かかるので）
* lambdaはpythonで書く（簡単なので）
* `CloudwachLogs -> SNS`はメトリックスフィルターを使う
* lambdaのエラー処理も行う（心配なので）
* 今回SNSの整形は頑張らない（必要な情報がカスタマイズできる感じがわかればいい）

# 料金

## cloudwatchlogs

|項目|単位|料金（Month）|
|:--|:--|:--|
|カスタムメトリックス|個|$0.5|
|アラーム|個|$0.10|
|取り込み|GB|$0.76|
|アーカイブ|GB|$0.033|
|EC2からの転送|GB|$0.140|

 EC2からの転送は最初の1GB無料、次の10Tまでがこの値段

エラーの頻度にもよるが、そんなにお金はかからない印象。
1時間に1回くらいの頻度でエラーが吐かれるくらいなら、2ドル/月もかからないと思う

## lambda

リクエストのうち毎月最初の 1,000,000 件は無料
今回の構成では無料でいけると思う。

## SNS

Emailは1000件無料。
その後は100,000 件あたり 2 ドル

今回は、エラーがいっぱい出たとしても2ドルで済むと思う。

## 合計

2ドル（cloudwatchlogs) + 無料（lambda） + 2ドル(SNS) = 4ドル

もちろん、cloudwatchlogsのデータ保持容量、エラーログ頻度によっていろいろ変わります。

# コード

* lambda

```py:lambda
from __future__ import print_function

import base64
import json
import zlib
import datetime
import boto3

sns = boto3.client('sns')

print('Loading function')

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)
    log_json = json.loads(json.dumps(data_json["logEvents"][0], ensure_ascii=False))
    if data_json["logGroup"]:
        date = datetime.datetime.fromtimestamp(int(str(log_json["timestamp"])[:10])) + datetime.timedelta(hours=9)
        sns_body = {}
        sns_body["default"] = "" 
        sns_body["default"] += "Owner : " +  data_json["owner"] + "\n" 
        sns_body["default"] += "LogGroup : " + data_json["logGroup"] + "\n" 
        sns_body["default"] += "LogStream : " + data_json["logStream"] + "\n" 
        sns_body["default"] += "SubscriptionFilters : " + ''.join(data_json["subscriptionFilters"]) + "\n" 
        sns_body["default"] += "Time : " +  date.strftime('%Y-%m-%d %H:%M:%S') + "\n" 
        sns_body["default"] += "Message : " + log_json['message'] + "\n" 

        topic = 'arn:aws:sns:ap-northeast-1:111111111111:***********'
        subject = 'Your Email Subject!!'
        region = 'ap-northeast-1'
        response = sns.publish(
            TopicArn=topic,
            Message=json.dumps(sns_body, ensure_ascii=False),
            Subject=subject,
            MessageStructure='json'
        )
    return 'Successfully processed {} records.'.format(len(event['awslogs']))
```

注意点: [複数ログがきても対応できるように書かないといけない模様](https://qiita.com/sadayuki-matsuno/items/83b70ee97755e534764f#comment-63156edaeeede0ed3fc5)
@ryonext ご指摘ありがとうございます。



これをlambdaにポチッと貼り付ければ大丈夫です。
SNSのTOPICだけは各自変更してください。

# 最後に

何か間違いあればご指摘ください。