#はじめに
AppSync の Lambda リゾルバを書く際に Serverless Framework を使用したのですが、
デプロイ後のバグ調査の際、毎回ブラウザから AWS Console を開いて該当 Lambda の CloudWatch のログを見に行くのが面倒でした。。
そのため、エラーレポートの仕組みが欲しくなり、Lambda のエラーを Slack に通知する仕組みを Serverless Framework で実装する方法について調査したので、備忘録も兼ねて記事にまとめました
追記 (2020/08/19)
#動作環境
- Serverless Framework 1.78.1
- Node.js 12.18.1
- Slack Incoming Webhooks 5.0.3
- AWS SDK for JavaScript 2.518.0
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 にエラーレポートを送信する関数を追加します
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 関数が実行されるようになります
#...
# 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 のイベントが紐付けられていないように見えます
同様のケースが発生している方は他にもいらっしゃるようですが、
手順 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 に通知が飛んできたことまで確認できれば動作確認完了です!
#おわりに
Serverless Framework 内で完結する形で、
Lambda 関数のエラーを捕捉して Slack に通知を飛ばす方法についてまとめました
events
には cloudwatchLog
の他にも eventBridge
というものも指定できます。
eventBridge
を使用すると CloudWatch 以外の様々な AWS サービスのイベント駆動で Lambda 関数を実行することが可能です
events
を有効活用することで効率よくイベント駆動の処理を書いていけるので、
是非とも有効活用していきましょう!
#参考リンク