🔖

[AWS]boto3のエラーハンドリング(翻訳)とベストプラクティス

9 min read

2021/08/04 時点のドキュメントになります。

業務でboto3を使う機会があり、エラーハンドリングのベストプラクティスを知りたかったので、一先ず公式ドキュメントを読んでみることにしました。
日本語訳したものを共有致します。

原文↓

https://boto3.amazonaws.com/v1/documentation/api/latest/guide/error-handling.html

ベストプラクティス

実装者としては以下内容のみ知っていれば実装は可能です。さらに詳しく知りたい場合、翻訳全文を参考にしてください。

Boto3を使用するときに発生する例外は、botocoreの例外とAWSサービスの例外のどちらかである。
botocore の例外は以下の方法でキャッチする。

import botocore
import boto3

client = boto3.client('aws_service_name')

try:
    client.some_api_call(SomeParam='some_param')

except botocore.exceptions.ClientError as error:
    # Put your error handling logic here
    raise error

except botocore.exceptions.ParamValidationError as error:
    raise ValueError('The parameters you provided are incorrect: {}'.format(error))

AWSサービスの例外は、botocoreのClientErrorをキャッチした後に、エラーレスポンスを解析して例外処理を行う。
if error.response['Error']['Code'] == 'LimitExceededException'
上記の例外の種類は利用するサービスの公式ドキュメントに記載されているので、新たなサービスを使う際にはそちらを参考にしてコーディングしていく。

import botocore
import boto3
import logging

# Set up our logger
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()

client = boto3.client('kinesis')

try:
    logger.info('Calling DescribeStream API on myDataStream')
    client.describe_stream(StreamName='myDataStream')

except botocore.exceptions.ClientError as error:
    if error.response['Error']['Code'] == 'LimitExceededException':
        logger.warn('API call limit exceeded; backing off and retrying...')
    else:
        raise error

概要(以下、公式ドキュメントの翻訳)

boto3はAWSサービス利用時に発生するエラーや例外をナビゲートする多くの機能を提供しています。
特に、このガイドは以下の情報を記載したものです。

  • どのような例外がboto3とAWS serviceから投げられるのかを調べる方法
  • boto3とAWS serviceから投げられた例外をキャッチ、ハンドリングする方法
  • AWS serviceからのエラーレスポンスを解析する方法

なぜAWSとBotoの例外をキャッチするのか

  • サービスの制限とクォータ[1] : AWSサービスへのリクエストレートが頻繁すぎたり、特定のAWSサービスのクォータに達しているかもしれません。いずれの場合も、適切なエラー処理がなければ、わからないし、処理もできないでしょう。
  • パラメータの検証とチェック : APIの要件は、特にAPIのバージョン間で変更されることがあります。これらのエラーをキャッチすることで、特定のAPIコールに提供されたパラメータに問題があるかどうかを特定することができます。
  • 適切なロギングとメッセージング : エラーや例外をキャッチすることは、それらをログに記録することを意味します。これは、AWSサービスを利用した実装のトラブルシューティングに役立ちます。

キャッチすべき例外の決定

Boto3を使用しているときに発生する例外は、botocoreまたはクライアントが使用しているAWSサービスのどちらかに起因するものです。

botocore の例外

これらの例外は、Boto3の依存関係にあるBotocoreパッケージ内で静的に定義されています。これらの例外は、クライアント側の動作、設定、または検証に関する問題に関連しています。以下のコードを使用して、静的に定義された botocore 例外のリストを生成できます。

import botocore.exceptions

for key, value in sorted(botocore.exceptions.__dict__.items()):
    if isinstance(value, type):
        print(key)

Tip: 全ての例外は公式ドキュメント該当箇所を参照

Note:
botocoreの例外の実装はbotocoreのgitリポジトリを参照

AWS service の例外

AWS サービスの例外は、基本的なbotocoreの例外である ClientError でキャッチされます。この例外をキャッチした後、レスポンスを解析して、サービス固有の例外を含む、そのエラーに関する詳細を確認することができます(後述サンプルコード参照)。AWSサービスの例外やエラーは様々です。Boto3を使えば、AWSサービスの例外のリストをすぐに得ることができます。

使用しているサービスからのエラーレスポンスの完全なリストについては、個々のサービスのAWSドキュメント、特にAWSサービスのAPIリファレンスのエラーレスポンスセクションを参照してください。これらのリファレンスには、例外やエラーに関するコンテキストも記載されています。

低レベルクライアント使用時の例外処理

botocore例外の処理

Botocoreの例外は、botocoreパッケージで静的に定義されています。お客様が作成するBoto3クライアントは、これらの静的に定義された例外クラスを使用します。最も一般的なBotocore例外はClientErrorです。これは、Boto3クライアントのリクエストに対してAWSサービスからエラーレスポンスが提供された場合の一般的な例外です。
また、SSLネゴシエーション、クライアントの設定ミス、AWSサービスの検証エラーなど、クライアント側の問題でもbotocore例外が発生します。以下は、botocore例外をキャッチするための一般的な例です。

import botocore
import boto3

client = boto3.client('aws_service_name')

try:
    client.some_api_call(SomeParam='some_param')

except botocore.exceptions.ClientError as error:
    # Put your error handling logic here
    raise error

except botocore.exceptions.ParamValidationError as error:
    raise ValueError('The parameters you provided are incorrect: {}'.format(error))

AWSサービスからのエラーレスポンスの解析と例外処理

AWS service の例外はbotocoreの例外と異なり、Boto3上で静的に定義をされていません。なぜなら、AWS service からのエラーや例外は大きく変更される可能性があるためです。例外を正しく処理するために、AWS service からのレスポンスを解析する必要があります。AWSサービスからお客様のクライアントに提供されるエラーレスポンスは、共通の構造に従っており、Boto3では最小限の処理が行われるのみで、難読化はされていません。
Boto3を使用すると、AWSサービスからのエラーレスポンスは、ResponseMetadataの階層にErrorをキーとした辞書型データがあることを除いて、成功レスポンスと同様の構造を採ります。以下に、エラーレスポンスの例を示します。

{
    'Error': {
        'Code': 'SomeServiceException',
        'Message': 'Details/context around the exception or error'
    },
    'ResponseMetadata': {
        'RequestId': '1234567890ABCDEF',
        'HostId': 'host ID data will appear here as a hash',
        'HTTPStatusCode': 400,
        'HTTPHeaders': {'header metadata key/values will appear here'},
        'RetryAttempts': 0
    }
}

Boto3はAWS serviceからのエラーと例外を ClientError Exceptionにクラス化します。
AWSサービスの例外をキャッチしようとする場合、ClientErrorをキャッチしてから、AWSサービス固有の例外のエラーレスポンスを解析する方法があります。
Amazon Kinesisを例にとると、Boto3を使ってLimitExceededExceptionという例外をキャッチし、AWSサービスの リクエストスロットリング[2]を捕捉し、独自のロギングメッセージを挿入することができます。以下にコード例。

import botocore
import boto3
import logging

# Set up our logger
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()

client = boto3.client('kinesis')

try:
    logger.info('Calling DescribeStream API on myDataStream')
    client.describe_stream(StreamName='myDataStream')

except botocore.exceptions.ClientError as error:
    if error.response['Error']['Code'] == 'LimitExceededException':
        logger.warn('API call limit exceeded; backing off and retrying...')
    else:
        raise error

Boto3の標準リトライモード[3] は、スロットリングエラーや例外をキャッチし、バックオフしてリトライしてくれます。

さらに、動的なサービス側の例外の一部に、クライアントの exception プロパティからアクセスすることもできます。前述の例では、except 節のみを変更する必要があります。

except client.exceptions.LimitExceedException as error:
    logger.warn('API call limit exceeded; backing off and retrying...')

Note:
今のところ、ClientErrorを介して例外をキャッチし、エラーコードを解析することは、すべてのサービス側の例外やエラーをキャッチするための最良の方法です。

リソースクライアント使用時の例外処理

リソースクラスを使用して特定のAWSサービスを利用する場合、例外やエラーをキャッチすることは、低レベルのクライアントを使用するのと同様の方法をとります。
エラーレスポンスの解析は、低レベルクライアントのセクションで説明したのと同じ方法を使用します。(ClientErrorをキャッチしてレスポンスを解析する方式で可能ということ)[4]

例外をキャッチするには、クライアントのmetaプロパティにアクセスする必要があるため、クライアントのexceptionsプロパティを介した例外のキャッチとは若干異なります。

client.meta.client.exceptions.SomeServiceException

Amazon S3 をリソースサービスの例とすると、クライアントの exception プロパティを使用して、BucketAlreadyExists 例外をキャッチすることができます。また、エラーレスポンスを解析して、バケット名を取得することもできます。

import botocore
import boto3

client = boto3.resource('s3')

try:
    client.create_bucket(BucketName='myTestBucket')

except client.meta.client.exceptions.BucketAlreadyExists as err:
    print("Bucket {} already exists!".format(err.response['Error']['BucketName']))
    raise err

エラーレスポンス内の有用な情報を見極める

このガイドで前述したように、特定のAWSサービスの例外に関する詳細とコンテキストについては、個々のサービスのAWSドキュメント、特にAWSサービスのAPIリファレンスのエラーレスポンスセクションを参照してください。
Botocoreの例外は、それらの例外がスローされたときに詳細なエラーメッセージを保持しています。これらのエラーメッセージは、スローされた例外の詳細と例外の原因を示します。これらの例外の説明はここで見ることができます。
特定のエラーや例外の詳細とメッセージング以外にも、エラーレスポンスから以下のような追加のメタデータを抽出することができます。

  • 例外クラスとエラーメッセージ: このデータを使用して、これらのエラーや例外に対応するロジックを構築することができます。
  • リクエストIDとHTTPステータスコード: AWSサービスの例外は、まだ曖昧であったり、詳細が不足している場合があります。このような場合、カスタマーサポートに連絡し、AWSサービス名、エラー、エラーメッセージ、リクエストIDを提供することで、サポートエンジニアが問題をさらに詳しく調べることができます。

低レベルのAmazon SQSクライアントを使用して、AWSサービスからの一般的なまたは曖昧な例外をキャッチし、エラーレスポンスから有用なメタデータを解析する例を以下に示します。

import botocore
import boto3

client = boto3.client('sqs')
queue_url = 'SQS_QUEUE_URL'

try:
    client.send_message(QueueUrl=queue_url, MessageBody=('some_message'))

except botocore.exceptions.ClientError as err:
    if err.response['Error']['Code'] == 'InternalError': # Generic error
        # We grab the message, request ID, and HTTP code to give to customer support
        print('Error Message: {}'.format(err.response['Error']['Message']))
        print('Request ID: {}'.format(err.response['ResponseMetadata']['RequestId']))
        print('Http code: {}'.format(err.response['ResponseMetadata']['HTTPStatusCode']))
    else:
        raise err

注釈

脚注
  1. 用語説明(クォータ) サービスごとに設定された制限のこと。例えば「秒間30リクエストまで」など。https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws_service_limits.html ↩︎

  2. 用語(リクエストスロットリング) 一定期間に受信可能なリクエスト数 ↩︎

  3. 用語(リトライモード) boto3使用時にbotocoreのconfigオブジェクトやconfigファイルを用いて、legacy, standard, adaptive のリトライモードいずれかを選択することが可能。互換性の観点から、デフォルトはlegacyモードである。
    詳しくは以下を参照。
    https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html ↩︎

  4. 訳注 リソースクライアント使用時でも、botocore.exceptions.ClientError をキャッチしてからエラーレスポンスを解析する手法がベストプラクティスということ。 ↩︎

Discussion

ログインするとコメントできます