TECH
BLOG

Serverless Framework でエラーを検知して Webhook で Slack に通知を飛ばす方法

2020
8

はじめに

AppSync の Lambda リゾルバを書く際に Serverless Framework を使用したのですが、
デプロイ後のバグ調査の際、毎回ブラウザから AWS Console を開いて該当 Lambda の CloudWatch のログを見に行くのが面倒でした。。

そのため、エラーレポートの仕組みが欲しくなり、Lambda のエラーを Slack に通知する仕組みを Serverless Framework で実装する方法について調査したので、備忘録も兼ねて記事にまとめました

追記 (2020/08/19)

動作環境

1. Slack で Webhook URL を発行する

まずは 公式サイトの手順 に従って Webhook URL を発行します

無事発行できると、
https://hooks.slack.com/services/~~~~~/~~~~~/~~~~~~~~~~~ のようなフォーマットの URL が取得出来るはずなのでメモっておきます

2. 必要な npm パッケージをインストールする

Slack の Incoming Webhook の仕組みを使用し、
チャンネルにメッセージを送信するための npm パッケージをインストールします

npm install @slack/webhook --save

3. Slack にエラーレポートを送信する Lambda 関数を作成する (TypeScript)

Serverless Framework の handler に Slack にエラーレポートを送信する関数を追加します

handlers/Reporter.ts

import { gunzip } from "zlib";
import { IncomingWebhook } from "@slack/webhook";
/**
* CloudWatch のログ情報
*/
interface CloudWatchLogContent {
 messageType: string;
 owner: string;
 logGroup: string;
 logStream: string;
 subscriptionFilters: string[];
 logEvents: {
   id: string;
   timestamp: string;
   message: string;
 }[];
}
/**
* Lambda リゾルバの型定義
*/
type LambdaResolver<TEvent = any> = (event: TEvent) => Promise<any> | any;
/**
* CloudWatch のロググループに出現するエラーを通知する
* @param event 該当するエラーログの内容
* @return {object} event オブジェクトをそのまま返却する
*/
export const NotifyError: LambdaResolver = async (event: any) => {
/**
{
 awslogs: {
   data: 'H4sIAAA...'
 }
}
CloudWatch から呼ばれた際の event には上記フォーマットでデータが入っている。
data 内には Base64 でエンコードされた gzip 形式で圧縮されたデータが入っているので、
gzip 形式のデータを解凍しつつ、Base64 デコードを行い JSON 文字列を取得するための関数
*/
 const gunzipAsync = async (base64Logs): Promise<string> => {
   return new Promise(function (resolve, reject) {
     gunzip(base64Logs, function (err, binary) {
       err ? reject(err) : resolve(binary.toString("ascii"));
     });
   });
 };
// 1. Base64 でエンコードされた gzip 形式で圧縮されたデータを Base64 でデコードし、gzip のバイナリとして取得する
// gunzipAsync 関数で gzip 解凍して ascii 文字列として取得することで CloudWatch のログ内容を JSON 文字列で取得する
 const base64Logs = Buffer.from(event["awslogs"]["data"], "base64");
 const uncompressedLogs = await gunzipAsync(base64Logs);
 console.log(uncompressedLogs);
// 2. 取得した JSON 文字列を CloudWatchLogContent に変換して取得する
 const content = <CloudWatchLogContent>JSON.parse(uncompressedLogs);
 console.log(content);
// 3. 発行した Slack の Webhook URL で IncomingWebhook クラスを生成し、
// send 関数で Slack チャンネル名 (ex. #serverless-error-report) と、
// CloudWatchLogContent の内容を元に作成したテキストを引数に指定して、
// 該当する Slack チャンネルにテキストを投稿する
 const webhook = new IncomingWebhook("<1. で発行した Slack の Incoming Webhook URL>");
 await webhook.send({
   channel: "<通知したいチャンネル名 (例: #serverless-error-report)>",
   icon_emoji: "hammer", // icon_emoji パラメタを指定すると Slack へのメッセージ通知の際のアイコンを変更することが可能
   text: `*Group*\n_${content.logGroup}_\n\n*Message*\n\`\`\`${content.logEvents[0].message}\`\`\``,
 });
 return event;
};

4. 3. の関数をデプロイする関数として追加し、各種 Lambda 関数の CloudWatch のログを監視するイベントと紐付ける

serverless.yml に 手順 3. で作成した関数 NotifyError を記載すると共に、
監視したい関数の CloudWatch ロググループを events の cloudwatchLog に定義し、filter に ERROR を指定します

これで、
該当ロググループに ERROR が含まれていた場合、
都度 Lambda 関数が実行されるようになります

serverless.yml

#...
# 3. で作成した Slack にエラーレポートを送信する関数 NotifyError を functions に追記し、
# events を用いて、他 Lambda 関数のロググループに 'ERROR' が出力されていた場合、 NotifyError 関数が実行されるようにする
functions:
#...
 NotifyError:
   handler: CloudWatch.NotifyError
   events:
     - cloudwatchLog:
         logGroup: /aws/lambda/${self:service.name}-${self:provider.stage}-TestFunction1
         filter: ERROR
     - cloudwatchLog:
         logGroup: /aws/lambda/${self:service.name}-${self:provider.stage}-TestFunction2
         filter: ERROR
     - cloudwatchLog:
         logGroup: /aws/lambda/${self:service.name}-${self:provider.stage}-TestFunction3
         filter: ERROR
#...

が完了したら sls deploy で NotifyError 関数をデプロイします

最後に AWS CLI で CloudWatch にイベントログデータを送信してみて、
本当に Slack に通知が飛んでくるか動作確認を行いましょう

注意事項 (複数の cloudwatchLog を定義した場合)

events に複数の cloudwatchLog を定義した場合、
関数のデプロイ後、AWS Console で該当する Lambda 関数を見に行くと、
正しく CloudWatch のイベントが紐付けられていないように見えます


492d4761e7fadb62612e539c17b3d05f.png

同様のケースが発生している方は他にもいらっしゃるようですが、
手順 5. で Slack への通知まで確認出来れば問題なく設定できています

5. AWS CLI を利用してCloudWatch にイベントログデータを送信して Slack に通知が飛んでくるか検証する

CloudWatch にイベントログデータを送信するコマンドです

aws logs put-log-events \
   --log-group-name '<該当するロググループ名>(例: /aws/lambda/test-dev-TestFunction1)' \
   --log-stream-name '<ログストリーム名(例: test-stream)>' --log-events \
   timestamp=(node -e 'console.log(Date.now())'),message="This is ERROR"

コマンド実行後、
AWS Console から CloudWatch の該当するロググループのログストリームを見に行くと、
This is ERROR という文字列が出力されている事が確認できるはずです

あとは Slack に通知が飛んできたことまで確認できれば動作確認完了です!

4a5d063485ed635c83e3ea1917043673.png

おわりに

Serverless Framework 内で完結する形で、
Lambda 関数のエラーを捕捉して Slack に通知を飛ばす方法についてまとめました

events には cloudwatchLog の他にも eventBridge というものも指定できます。
eventBridge を使用すると CloudWatch 以外の様々な AWS サービスのイベント駆動で Lambda 関数を実行することが可能です

events を有効活用することで効率よくイベント駆動の処理を書いていけるので、
是非とも有効活用していきましょう!

参考リンク

RELATED PROJECT

No items found.