Lambdaでも、例外エラー(想定外のエラー)が発生した場合に、通知(できればスタックトレースも)を受け取って、初動を早くしたいですね。
どんな方法で実現するのが良いか検討してみました。
先に結論を記載しちゃいます。
非同期(S3トリガーやEventBridgeなど)の場合は、Lambda関数の送信先(失敗時)でSNSに送信して、メールやSlackに送る。
同期(APIなど)の場合は、CloudWatch Logsのサブスクリプションフィルターで通知用のLambdaに送信して、そこからSNSに送信して、メールやSlackに送る。
また、通知用のLambdaも非同期なので、非同期のパターンと同様にして、この関数自体の例外エラーも通知するのがベストです。
- 構成検討
- 今まで作成したものを分類
- パターン1. 直接Publish
- パターン2. DLQ(デットレーターキュー)
- パターン3. 送信先(失敗時) ←非同期(S3トリガーやEventBridgeなど)に良い
- パターン4. メトリクスフィルター
- 追加. サブスクリプションフィルター+送信先(正常)
- パターン5. サブスクリプションフィルター ←同期(APIなど)に良い
- まとめ
構成検討
パターンはこちらを参考に、非同期以外にも同期もあるので、ケース毎に検討してみました。
Lambda でバッチ処理を構築する際のエラー通知パターン 5選 – サーバーワークスエンジニアブログ
今まで作成したものを分類
非同期(S3トリガーやEventBridgeなど)
・createMediaConvertJob ← S3
・receiveMediaConvertJobState ← EventBridge (CloudWatch Events)
同期(APIなど)
・mediaConvertInputFileToJobIdApi ← API GatewayまたはApplication Load Balancer
・mediaConvertJobIdToStatusApi ← API GatewayまたはApplication Load Balancer
・mediaConvertInputFileToJobIdEdgeApi ← CloudFront(Lambda@Edge)
・mediaConvertJobIdToStatusEdgeApi ← CloudFront(Lambda@Edge)
パターン1. 直接Publish
Lambda関数で、例外をキャッチして、コードでSNSに送る方法。
エラー通知の為のSNSに繋がらなかったら通知できないので却下。
パターン2. DLQ(デットレーターキュー)
Lambda関数
デットレーターキューが動くのは非同期(S3トリガーやEventBridgeなど)のみなので、
同期(APIなど)では設定しても送信されない。テストも同期。
Lambda@Edgeでは設定されていると、関数の関連付けでエラーになります。
> The function cannot have a Dead Letter Queue configuration. Function: arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:mediaConvertJobIdToStatusEdgeApi:3
また、SNSからメッセージ整形でLambda使うと、このLambdaのエラー通知には使えない。
(ループになる)
メッセージ整形せずに直接SNSに送る事もできますが、Lambda関数名が表示されず、
スタックトレースが表示される訳でもなく、呼び出し元の情報なので、
何が、どこでエラーになったかの切り分けが難しい。
パターン3. 送信先(失敗時)
Lambda関数
こちらも非同期(S3トリガーやEventBridgeなど)のみですが、
Lambda関数名(リージョンも)やスタックトレースも(呼び出し元の情報も)表示されるのでOK!
パターン2のDLQもですが、再施行(リトライ)回数を超えた後に通知されます。
JSONなので見難いですが、わざわざLambdaで整形するのも障害ポイント増えるだけなので、
やらない方が良さそう。来たら対応して来ないようにするのが目的なので。
パターン4. メトリクスフィルター
Lambdaのログに特定の文字が含まれる数をCloudWatch Alarmで監視してSNSに通知する方法。
期間(デフォルト5分)のエラー数を通知するので、
即時性もないし、スタックトレースが表示される訳でもない。
CloudWatch ロググループ
また、アラームは期間内にエラーなしの場合「データ不足」、エラーありの場合「アラーム状態」となるだけなので、アラームが無くなっても復旧した訳ではない。
(設定の「欠落データの処理」を変更した場合は「OK」にしたり「アラーム状態」を維持できる)
通知だけの為に、アラームにコストかけるのはもったいないと思う。1個のアラーム辺り$0.10/月
料金 – Amazon CloudWatch | AWS
追加. サブスクリプションフィルター+送信先(正常)
サブスクリプションフィルターもメトリクスフィルターと同様のフィルタリングができる。
違いは送信先で、LambdaやKinesisなどに送れる。(メトリクスフィルターはAlarmのみ)
CloudWatch ロググループ
通知用のLambda関数(notifyErrorLog)
import base64 import gzip import json def lambda_handler(event, context): print(event) messages = [] data = json.loads(gzip.decompress(base64.b64decode(event['awslogs']['data']))) for log_event in data['logEvents']: messages.append(log_event['message'].replace('\u00a0', ' ').split('\n')) body = { 'owner': data['owner'], 'logGroup': data['logGroup'], 'logStream': data['logStream'], 'message': messages } print(body) return body
パターン3の送信先(失敗時)のメール内容とある程度揃える為、加工しています。
returnが送信先(正常)に送られます。
失敗時も設定。パターン3の送信先(失敗時)と同じ動きになります。
必要な情報もあり、一見良さそうなのですが、フィルター(“[ERROR] “)が正規表現に対応していない為、通知したくないものも通知されてしまいます。
例えば、パラメータにフィルターと同じ値が設定され、ログに出力している場合とか。
> {‘input_file’: ‘Big Buck Bunny.mp4’}
> {‘input_file’: ‘[ERROR] xxx’}
> [ERROR] NameError: name ‘xxx’ is not defined”, “Traceback (most recent call last):
パターン5. サブスクリプションフィルター
追加のサブスクリプションフィルター+送信先(正常)で記載したものと似た構成で、違いは送信先(正常)を設定していないのと、Lambda関数のコードの違い。
あと、環境変数で作成したSNSトピックのARNを設定しています。
SNS_TOPIC_ARN: arn:aws:sns:ap-northeast-1:【アカウントID】:LambdaError
import boto3 import os import base64 import gzip import json sns = boto3.client('sns') SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN'] SNS_SUBJECT_PREFIX = '[WARNING]notifyErrorLog report for ' PREFIX_FILTER = '[ERROR] ' AWS_REGION = os.getenv("AWS_REGION") def lambda_handler(event, context): print(event) messages = [] data = json.loads(gzip.decompress(base64.b64decode(event['awslogs']['data']))) for log_event in data['logEvents']: if log_event['message'].startswith(PREFIX_FILTER): messages.append(log_event['message'].replace('\u00a0', ' ').split('\n')) if messages == []: return log_group = data['logGroup'] body = { 'owner': data['owner'], 'region': AWS_REGION, 'logGroup': log_group, 'logStream': data['logStream'], 'message': messages } print(body) print(sns.publish( TopicArn = SNS_TOPIC_ARN, Subject = SNS_SUBJECT_PREFIX + log_group, Message = json.dumps(body) ))
リージョンも追加しました。
スタックトレースやログの場所も表示できるのでOK!同期(APIなど)に適している。
非同期(S3トリガーやEventBridgeなど)だと再施行(リトライ)毎に通知されてしまう。
まとめ
非同期(S3トリガーやEventBridgeなど): パターン3. 送信先(失敗時)
同期(APIなど): パターン5. サブスクリプションフィルター
→ Lambdaの例外エラーをメールとSlackに通知する
備えあれば憂いなし。ちゃんと設定されてて通知が来ないのと、そもそも設定がなくて通知が来ないのでは安心感が違いますね。
導入時にはちゃんとテストするものの、改修を重ねて行くと動かなくなる事も良くあるので、定期的にテストしたり、AWS Configとかで変更を監視したりもしたいですね。
“Lambdaの例外エラーの通知方法を考える” に対して1件のコメントがあります。