AppSync の Lambda リゾルバを書く際に Serverless Framework を使用したのですが、
デプロイ後のバグ調査の際、毎回ブラウザから AWS Console を開いて該当 Lambda の CloudWatch のログを見に行くのが面倒でした。。
そのため、エラーレポートの仕組みが欲しくなり、Lambda のエラーを Slack に通知する仕組みを Serverless Framework で実装する方法について調査したので、備忘録も兼ねて記事にまとめました
追記 (2020/08/19)
まずは 公式サイトの手順 に従って Webhook URL を発行します
無事発行できると、
https://hooks.slack.com/services/~~~~~/~~~~~/~~~~~~~~~~~ のようなフォーマットの URL が取得出来るはずなのでメモっておきます
Slack の Incoming Webhook の仕組みを使用し、
チャンネルにメッセージを送信するための npm パッケージをインストールします
npm install @slack/webhook --save
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;
};
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 に通知が飛んでくるか動作確認を行いましょう
events に複数の cloudwatchLog を定義した場合、
関数のデプロイ後、AWS Console で該当する Lambda 関数を見に行くと、
正しく CloudWatch のイベントが紐付けられていないように見えます
同様のケースが発生している方は他にもいらっしゃるようですが、
手順 5. で 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 に通知が飛んできたことまで確認できれば動作確認完了です!
Serverless Framework 内で完結する形で、
Lambda 関数のエラーを捕捉して Slack に通知を飛ばす方法についてまとめました
events には cloudwatchLog の他にも eventBridge というものも指定できます。
eventBridge を使用すると CloudWatch 以外の様々な AWS サービスのイベント駆動で Lambda 関数を実行することが可能です
events を有効活用することで効率よくイベント駆動の処理を書いていけるので、
是非とも有効活用していきましょう!