【AWS/Lambda】例外を吐いた同期呼び出ししたLambdaのエラーメッセージをキャッチする方法
同期呼び出しでLambdaを呼び出したときに、Lambda内でエラーになろうがなるまいが200が返ってきて困ることってありますよね。
例えば呼び出し元のアプリで、Lambdaのエラー毎に動作を変えたいとか、そんなときです。
今回はLambdaで発生した例外ってどうやって呼び出し元でキャッチすればいいの?というのを調べてみました。
やったこと
- Lambdaを2つ作って、片方のLambdaから他方のLambdaをinvokeして呼び出し元Lambdaでエラーメッセージを取得します。
調査内容
まずこんな感じで、例外をキャッチするLambdaと例外をだすLambdaを作ります。
cdk的にはこんな感じです
import aws_cdk as cdk
from aws_cdk import aws_lambda
app = cdk.App()
stack = cdk.Stack(app, "test")
# 例外を出すlambda
fn1 = aws_lambda.Function(
stack,
"ExceptionFunction",
code=aws_lambda.Code.from_asset("lambdas"),
handler="fn1.handler",
runtime=aws_lambda.Runtime.PYTHON_3_9,
)
# fn1を同期呼び出しするlambda
fn2 = aws_lambda.Function(
stack,
"ExceptionHandlingFunction",
code=aws_lambda.Code.from_asset("lambdas"),
handler="fn2.handler",
runtime=aws_lambda.Runtime.PYTHON_3_9,
environment={"FUNCTION_NAME": fn1.function_name},
)
fn1.grant_invoke(fn2)
app.synth()
fn1(例外を出す)側のLambdaはこんな感じ。
class HogehogeException(Exception):
pass
def handler(event, context):
"""例外を投げるlambda"""
raise HogehogeException("HHHHHHHHOOOOOOOOOOGGGGGGGGEEEEEEEE")
オレオレ例外を作成して、その例外を使ってエラーを出してー、、、呼び出し元でキャッチしようという感じです。
そしてfn2(例外をキャッチする)側のLambdaはこんな感じです
import json
import os
from pprint import pprint
import boto3
def handler(event, context):
"""lambdaを同期呼び出しするlambda"""
# 同期呼び出し
client = boto3.client("lambda")
response = client.invoke(FunctionName=os.getenv("FUNCTION_NAME"))
# こんなレスポンスが返ってくる。Lambdaで例外が発生してもLambdaとしては正常終了してるので例外はでない。
pprint(response)
# {'ExecutedVersion': '$LATEST',
# 'FunctionError': 'Unhandled',
# 'Payload': <botocore.response.StreamingBody object at 0x7fc86c9de310>,
# 'ResponseMetadata': {'HTTPHeaders': {'connection': 'keep-alive',
# 'content-length': '263',
# 'content-type': 'application/json',
# 'date': 'XXX, 00 XXX 0000 00:00:00 XXX',
# 'x-amz-executed-version': '$LATEST',
# 'x-amz-function-error': 'Unhandled',
# 'x-amzn-remapped-content-length': '0',
# 'x-amzn-requestid': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
# 'x-amzn-trace-id': 'root=1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;sampled=0'},
# 'HTTPStatusCode': 200,
# 'RequestId': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
# 'RetryAttempts': 0},
# 'StatusCode': 200}
# エラー発生の有無は`FunctionError`の有無判断する
if response.get("FunctionError"):
print("同期呼び出ししたLambdaは例外を吐いたよ!!")
# errorの中身はここで確認できる
payload = response["Payload"].read().decode("utf8")
payload_dict = json.loads(payload)
pprint(payload_dict)
# {'errorMessage': 'HHHHHHHHOOOOOOOOOOGGGGGGGGEEEEEEEE',
# 'errorType': 'HogehogeException',
# 'requestId': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
# 'stackTrace': [' File "/var/task/fn1.py", line 3, in handler\n'
# ' raise HogehogeException("HHHHHHHHOOOOOOOOOOGGGGGGGGEEEEEEEE")\n']}
print(payload_dict.get("errorMessage"))
# HHHHHHHHOOOOOOOOOOGGGGGGGGEEEEEEEE
print(payload_dict.get("errorType"))
# HogehogeException
return
printの出力はコメントで入れた通りです。
補足説明をしていくと、まず
# エラー発生の有無は`FunctionError`の有無判断する
if response.get("FunctionError"):
...
この部分ですが、公式ドキュメントだとこの辺に書いてあります。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-exceptions.html
エラーが発生した場合は、レスポンスにはFunctionErrorフィールドが含まれます。
なのでまずレスポンスがFunctionError
keyを持っているかいなかでエラーの有無を判断します。
(ちなみに今回Unhandled
が返ってきてますが、ここを任意で変える方法をわたしは知りません。どなたかしってたら教えて下さいmm)
そして発生した例外ですが、レスポンスのPayload
keyに入ってるオブジェクトのread
関数を呼び出すと取得できます。
ただしこのままだとバイナリなので使いやすくするためにはutf8
で文字列にしてからjson形式で読み込む必要があります。
payload = response["Payload"].read().decode("utf8")
json.loads(payload)
するとその中に、errorMessage
にエラーメッセージ、errorType
に例外の型が入っている感じです!
はい。以上でした!最後まで読んでいただいてありがとうございます!
まとめ
- 同期呼び出ししたLambdaのエラーの有無はレスポンスに
FunctionError
が存在するかで判断する。 - エラー内容はレスポンスの
Payload
のread
関数から取得できる。ただしバイナリで入っているのでパースする
今回のリポジトリはこちら
https://github.com/sisi100/lambda-error-handling-raise-exception