/ #Lambda 

【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フィールドが含まれます。

なのでまずレスポンスがFunctionErrorkeyを持っているかいなかでエラーの有無を判断します。

(ちなみに今回Unhandledが返ってきてますが、ここを任意で変える方法をわたしは知りません。どなたかしってたら教えて下さいmm)

そして発生した例外ですが、レスポンスのPayloadkeyに入ってるオブジェクトのread関数を呼び出すと取得できます。 ただしこのままだとバイナリなので使いやすくするためにはutf8で文字列にしてからjson形式で読み込む必要があります。

payload = response["Payload"].read().decode("utf8")
json.loads(payload)

するとその中に、errorMessageにエラーメッセージ、errorTypeに例外の型が入っている感じです!

はい。以上でした!最後まで読んでいただいてありがとうございます!

まとめ

  • 同期呼び出ししたLambdaのエラーの有無はレスポンスにFunctionErrorが存在するかで判断する。
  • エラー内容はレスポンスのPayloadread関数から取得できる。ただしバイナリで入っているのでパースする

今回のリポジトリはこちら

https://github.com/sisi100/lambda-error-handling-raise-exception

Author

Sisii

インフラが好きなエンジニアぶってるなにか