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件のコメントがあります。