Alexa-HostedスキルをCloudWatchで監視する (後編)

はじめに

前編では、Winstonを使って自前のCloudWatchにログを出力する方法を紹介しました。後編では、CloudWatchに出力されたログからエラーログを検出し、その結果をEメールで開発者に通知する方法を紹介します。

本記事は下記の記事を参考に書かれています。

How to get notified on specific Lambda function error patterns using CloudWatch

全体構成

構築するソリューションの全体構成を下記に示します。前編では自前のCloudWatchのMyLogGroupに書き込むとろこまでを構築しました。後編ではMyLogGroupへのエラーログの書き込みを検出し、Eメールで通知をするところまでを構築します。

処理の流れ

後編で扱う処理の流れは大まかには以下のようになります。

スキルからログストリームのMyLogGroupにエラーログが書き込まれると、それをトリガーにしてLambda関数のmyErrorNotifierを起動します。

myErrorNotifierでは、渡されたエラーログの内容をデコードして、Simple Notification Service (SNS)のMyErrorTopicに書き込みます。

MyErrorTopicには、トピックの配信先としてEメールアドレスが登録されており、そのEメールアドレスにエラーログの内容を含むEメールが送信されます。

システムの構築

構築手順

構築は前述の構成を下流から上流に向けて行います。(これは依存関係の順になります)

  • SNSトピックの作成
  • IAMロールの作成
  • Lambda関数の作成
  • トリガーの設定

SNSトピックの作成

Simple Notification Service (SNS) にトピックを作成し、トピックの出力先 (サブスクリプション) にEメールアドレスを指定します。これにより、このトピックに書き込みがあると、Eメールで通知されます。

トピックの作成

AWSコンソールからSNSサービスのコンソールを表示します。

  • 左メニューから①トピックを選択します
  • トピックの作成をクリックします
  • タイプ①スタンダードを選択します
  • 名前 (MyErrorTopic) を入力します
  • トピックの作成をクリックします

サブスクリプションの作成

トピックが作成されたら引き続きサブスクリプションを作成します。

  • ARNは後でLambda関数の定義で使用します
  • サブスクリプションの作成をクリックします
  • プロトコルに①Eメールを選択します
  • エンドポイントに通知メールを受け取る②メールアドレスを入力します
  • サブスクリプションの作成をクリックします

MyErrorTopicへのサブスクリプションが作成されます。

Eメールアドレスの確認のために、サブスクリプションの送信先に指定したメールアドレに、下記のようなメールが送られていきます。Confirm subscriptionリンクをクリックして、サブスクリプションを確認します。

IAMロールの作成

Lambda関数myErrorNotifierに付与するロールをIndentiy and Access Management (IAM) で定義します。このロールには下記の権限がポリシーとして含まれます。

  • CloudWatchへのアクセス
    myErrorNotifier自身のログを出力するためのものです。
  • SNSへのアクセス
    MyErrorTopicにメッセージを書き込むためのものです。

以下ではまず最初にポリシーを定義し、次にロールを定義してそこに先に作成したポリシーをアタッチします。

ポリシーの作成

AWSコンソールからIAMサービスのコンソールを表示します。

  • 左メニューから①ポリシーを選択します
  • ポリシーを作成をクリックします
  • JSONタブを選択します
  • ポリシーの定義を入力します (詳細は後述)
  • 次のステップ: タグをクリックします

ポリシー定義の詳細

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "arn:<partition>:sns:<region>:<AWS account number>:MyErrorTopic"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:<partition>:logs:<region>:<AWS account number>:log-group:/aws/lambda/MyErrorNotifier:*"
        }
    ]
}

sns ARNには、先程定義したSNSトピックMyErrorTopicのARNを指定します。

log ARNには、下記を指定します。

  • <partition>は”aws”
  • <region>は、この後作成するLambda関数用のCloudWatchが作成されるリージョン (Lambda関数と同じ場所)
  • <AWS account number>は、sns ARNと同じもの

タグは設定してもしなくても構いません。次のステップ: 確認をクリックします。

  • 名前に①MyErrorNotifierPolicyを入力します
  • ポリシーの作成をクリックします

MyErrorNotifierPolicyが作成されます。

ロールの作成

ロールを作成し、先程作成したポリシーをアタッチします。

  • 左メニューから①ロールを選択します
  • ロールを作成をクリックします
  • AWSサービスを選択します
  • 一般的なユースケースから②Lambdaを選択します
  • 次のステップ: アクセス権限をクリックします
  • ポリシーのフィルタに①'My'と入力します
  • 先程作成した②MyErrorNotifierPolicyを選択します
  • 次のステップ: タグをクリックします

タグは設定してもしなくても構いません。次のステップ: 確認をクリックします。

  • ロール名に①MyErrorNotifierRoleを入力します
  • ロールの作成をクリックします

MyErrorNotifierRoleが作成されます。

Lambda関数の作成

エラーログを受け取り、SNSに書き込むLambda関数myErrorNotifierを作成します。

AWSコンソールからLambdaサービスのコンソールを表示します。

  • 左メニューから①関数を選択します
  • 関数の作成をクリックします
  • 一から作成を選択します
  • 関数名②myErrorNotifierを入力します
  • 実行ロールに③既存のロールを使用するを選択します
  • MyErrorNotifierRoleを選択します
  • 関数の作成をクリックします
  • コードタブを選択します
  • ソースコードを入力します (詳細は後述)
  • Deployをクリックします

ソースコード

下記のコードを入力してください。

const zlib = require('zlib');
const AWS = require('aws-sdk');
AWS.config.update({region: '...'});
const TopicArn = '...';

exports.handler = (event, context) => {
    const base64Log = Buffer.from(event.awslogs.data, 'base64');
    zlib.gunzip(base64Log, (err, unzipedLog) => {
        if (err) {
            console.error('Error occurred:', err);
            throw err;
        }
        const stringifiedLog = unzipedLog.toString('utf8');
        const log = JSON.parse(stringifiedLog);
        var params = {
            Message: JSON.stringify(log, null, 2),
            TopicArn: TopicArn
        };

        let publishPromise = new AWS.SNS({apiVersion: '2010-03-31'}).publish(params).promise();

        publishPromise.then(function(data) {
            console.log(`Message ${params.Message} sent to the topic ${params.TopicArn}`);
        }).catch(function(err) {
            console.error(err, err.stack);
        });
    });
};

region: このLambda関数を作成しているリージョンを指定します。
TopicArn: 先に作成したトピックMyErrorTopicのARNを指定します。

トリガーからはエラーログがevent.awslogs.dataに入れられて送られてきます。このdataはzipされ、更にbase64でエンコードされています。これをデコード、unzipしてエラーログの内容を復号しています。

復号されたエラーログの内容は、SNSトピックMyErrorTopicにパブリッシュされます。MyErrorTopicのサブスクリプションの定義によりEメールとして発信されます。

トリガーの設定

Lambda関数myErrorNotifierを起動するトリガーとして、MyLogGroupへのエラーログ書き込みを指定します。

  • 設定タブを選択します
  • 左メニューから②トリガーを選択します
  • トリガーを追加をクリックします

トリガーのソースとして、CloudWatch Logsを選択します。

  • ロググループに①MyLogGroupを選択します
  • フィルターの名前に②myErrorTriggerを入力します
  • フィルターパターンに③errorを入力します
  • 追加をクリックします

Lambda関数myErrorNotifierに、トリガーが設定されて定義が完了します。

スキルの実行

前編と同じく、下記の会話を実行します。

U: Alexa, open hello world 
A: Welcome, you can say Hello or Help. Which would you like to try? 
U: Hello
A: Hello World!

U: ユーザー、A: アレクサ

メールボックスに下記の内容のメールが届けば動作確認完了です。

記載されているlogGroup, logStreamから、該当するエラーログを含むログストリームを探すことができます。

おわりに

前編と合わせて、Alexa-Hostedスキルを監視し、エラーの発生をEメールで受け取れるシステムを構築しました。

これでログを確認する手間が省け、またエラーを見落としたり、対応が遅れたりする心配も無くなります。もっとも、エラーの通知なんて来ないにこしたことはありません。筆者もメールの来ないことを祈りつつ使っています。

変更履歴

日付内容
2021/08/15初版リリース
Alexa-Skill
スポンサーリンク