Lambdaリアルタイムログ監視、ログレベル毎に通知先をSNSで一括管理してSlackやメールで通知したい


やりたいこと

  • CloudWatch Alarmの通知先
  • AWS Lambdaのログ監視の通知先

を通知レベルに応じてSNSでソースコード変更なしに柔軟に一括管理できるようにしたく実現方式を検討したメモ。

例えば、以下のように通知レベル毎に通知先を定義する。

通知レベル
(SNSトピック)
通知先 定義
INFO ・Slack #log_info システムログ。調査時の効率化目的で普段は見ないログ。
WARN ・Slack #log_warn 異常事態の予兆を知らせる通知。障害の未然防止に繋がるアクション検討するため。
ERROR ・Slack #log_error
・メール サービス運用者のML
異常事態が明らかな重大なエラー。

検討した結論はこんな感じ。

Lambdaの実行ログはサブスクリプションフィルタでフィルターをかけて、各SNSへ通知するLambdaを噛ませて、SNSからSlack通知はLambdaのBlueprint「cloudwatch-alarm-to-slack」をCloudWatch経由以外の通知にも対応できるよう修正して対応した。

ログ監視対象のLambda

index.js
'use strict';

exports.handler = (event, context, callback) => {

    // INFO通知したい時
    console.log('SNS-INFO Subject-INFO Message-INFO');
    // WARN通知したい時
    console.log('SNS-WARN Subject-WARN Message-WARN');
    // ERROR通知したい時
    console.log('SNS-ERROR Subject-ERROR Message-ERROR');

    callback(null);
};

以下のログパターンに合致した場合に通知できるようにした。

SNS-<LEVEL> <SUBJECT> <MESSAGE>

CloudWatchLogsのサブスクリプションフィルタでSNS通知まで

サブスクリプションフィルタの設定方法

ログ監視対象のLambdaに対してCloudWatchLogsから対象ロググループに対してLambdaストリーミングを設定する。ログの形式は「AWS Lambda」を選択してサブスクリプションフィルターとして以下を登録。

[timestamp=*Z, request_id="*-*", level="SNS-*", subject, message]

フィルタとパターンの構文を参考にSNS通知時の件名(subject)とメッセージ本文(message)をLambdaで扱える変数として自動抽出してくれる機能を使った。

SNSへ通知するLambda

index.js
'use strict';

const zlib = require('zlib');
const aws = require('aws-sdk');
const sns = new aws.SNS({apiVersion: '2010-03-31'});

function publishToSnsTopic(topicArn, subject, message) {
    sns.publish({
        TopicArn: topicArn,
        Subject: subject,
        Message: message
    }, function (err, data) {
        if (err) { console.log('[Error] Failed sns published: ' + data);}
    });
}

exports.handler = (event, context, callback) => {
    const base64Logs = new Buffer(event['awslogs']['data'], 'base64');
    zlib.gunzip(base64Logs, function(err, binary) {
        var data = JSON.parse(binary.toString('utf8'));

        if(!Array.isArray(data.logEvents)) { return; }
        data.logEvents.forEach(function(d) {
           if (d.extractedFields === undefined ) { return; }
           if (d.extractedFields.level === undefined ) { return; }

           var topicArn;
           switch (d.extractedFields.level) {
               case "SNS-INFO":
                   topicArn = process.env.SNS_INFO_TOPIC_ARN;
                   break;
               case "SNS-WARN":
                   topicArn = process.env.SNS_WARN_TOPIC_ARN;
                   break;
               case "SNS-ERROR":
                   topicArn = process.env.SNS_ERROR_TOPIC_ARN;
                   break;
               default:
                   console.error('[ERROR] Level is not matched: ' + data.message);
                   return;
           }
           publishToSnsTopic(topicArn, d.extractedFields.subject, d.extractedFields.message);
        });
    });
};

ソースコードの変更なくSNSを変更できるようLambdaの環境変数でSNSのARNを管理できるようにした。
Lambdaのロールにはsns:Publishのポリシー許可が必要。

SNSからSlack通知するLambda

Blueprintの「cloudwatch-alarm-to-slack」を以下のdiffのように修正。
CloudWatch経由じゃないとJSONパースエラーになるので、CloudWatchLogs経由も対応できるように修正した。
KMSとかの設定はこちらの記事を参考にした。

@@ -84,18 +84,23 @@ function postMessage(message, callback) {
 }

 function processEvent(event, callback) {
-    const message = JSON.parse(event.Records[0].Sns.Message);
-
-    const alarmName = message.AlarmName;
-    //var oldState = message.OldStateValue;
-    const newState = message.NewStateValue;
-    const reason = message.NewStateReason;
+    const messageStr = event.Records[0].Sns.Message;
+    const subject = event.Records[0].Sns.Subject;

     const slackMessage = {
         channel: slackChannel,
-        text: `${alarmName} state is now ${newState}: ${reason}`,
+        text: subject + "\n" + messageStr
     };

+    try {
+        const message = JSON.parse(messageStr);
+        const alarmName = message.AlarmName;
+        //var oldState = message.OldStateValue;
+        const newState = message.NewStateValue;
+        const reason = message.NewStateReason;
+        slackMessage.text = `${alarmName} state is now ${newState}: ${reason}`;
+    } catch (e) {}

これで完了。

Lambdaのログ監視のSlack通知

CloudWatch AlertのSlack通知

参考

* cloudwatchlogs -> lambda -> SNSを試してみた
* AWS Lambda で CloudWatch Logs のログ本文をSlack通知(2)