関数でのエラーの処理
このページでは、Atlas Function でエラーを処理する方法について説明します。
注意
AWS Eventbridgeを使用したデータベーストリガーのカスタムエラー処理
AWS Eventbridgeを使用して、データベーストリガー専用のカスタムエラーハンドラーを作成できます。 詳細については、「カスタムエラー処理 」を参照してください。
基本的なエラー処理
try...catch ステートメントなどの標準の JavaScript エラー処理手法を使用して、関数エラーを処理できます。
function willThrowAndHandleError() { try { throw new Error("This will always happen"); } catch (err) { console.error("An error occurred. Error message:" + err.message); } } exports = willThrowAndHandleError;
ログの表示
App Service ログでは、エラーにより実行が成功し なかった 関数の実行を含むすべての関数実行のレコードを表示できます。
関数が呼び出される方法によっては、ログに表示される方法が異なります。 たとえば、 Atlas Triggersによって呼び出される関数のログは「trigger」としてログに表示され、 Realmクライアント SDK から呼び出される関数のログは「Functions」としてログに表示されます。 詳細については、 「 ログ エントリ タイプのドキュメント 」を参照してください。
関数を再試行する
Atlas Function には再試行動作が組み込まれておらず、 カスタム再試行動作を追加できます。 たとえば、関数を呼び出すサードパーティ サービスに断続的な接続があり、サードパーティ サービスが一時的にダウンしても関数を再実行する場合は、再試行動作を追加する必要があります。
このセクションでは、関数に再試行動作を追加するための次の方法について説明します。
エラー処理ブロックでの関数の再帰呼び出し
関数 を再帰的に呼び出すことで、失敗する可能性のある操作を処理できます。
高レベルでは、このプロセスには次のコンポーネントが含まれます。
try
ステートメントで再試行する操作を実行し、catch
ステートメントで関数自体を呼び出すようにします。無限の実行を防ぐには、最大再試行回数を設定します。 関数が失敗して
catch
ステートメントになるたびに、現在の再試行回数のカウントが増加します。 関数の現在の再試行回数が最大再試行回数に達したときに、再帰実行を停止します。また、再試行を制限して、時間枠での実行の合計回数を減らすこともできます。
次の表では、再帰呼び出し戦略で関数の再試行を処理する際のいくつかの利点と不の問題を説明します。
利点 | ディスカッション |
---|---|
|
|
次のコード例は、エラー処理ブロックで再帰を使用して関数を再試行する実装を示しています。
// Utility function to suspend execution of current process async function sleep(milliseconds) { await new Promise((resolve) => setTimeout(resolve, milliseconds)); } // Set variables to be used by all calls to `mightFail` // Tip: You could also store `MAX_RETRIES` and `THROTTLE_TIME_MS` // in App Services Values const MAX_RETRIES = 5; const THROTTLE_TIME_MS = 5000; let currentRetries = 0; let errorMessage = ""; async function mightFail(...inputVars) { if (currentRetries === MAX_RETRIES) { console.error( `Reached maximum number of retries (${MAX_RETRIES}) without successful execution.` ); console.error("Error Message:", errorMessage); return; } let res; try { // operation that might fail res = await callFlakyExternalService(...inputVars); } catch (err) { errorMessage = err.message; // throttle retries await sleep(THROTTLE_TIME_MS); currentRetries++; res = await mightFail(...inputVars); } return res; } exports = mightFail;
データベーストリガーを使用した再試行
また、データベースtriggerを使用して再試行を実行し、 MongoDBコレクションを使用して以前に失敗した実行を追跡することで、関数を再試行することもできます。
高レベルでは、このプロセスには次のコンポーネントが含まれます。
再試行したいロジックを実行するメイン関数は、 ハンドラー関数(以下のドットを参照)にラップされています。
メインの 関数 の失敗した実行を追跡する失敗した実行トグルの MongoDB コレクション。
メイン関数を呼び出し、関数が失敗したときに失敗した実行追跡コレクションにログを記録するハンドラー関数。
ハンドラー関数が失敗した実行追跡コレクションにエラーを追加するたびにハンドラー関数を再実行するデータベースtrigger関数。
ハンドラー関数、実行追跡コレクション、データベースtrigger関数の 1 セットで複数のメイン関数をサポートできます。
利点 | ディスカッション |
---|---|
|
|
実行の再試行を処理する関数を作成する
まず、メインの関数を呼び出すハンドラー関数handleRetry
を作成します。
handleRetry
は、次のパラメーターを受け入れます。
Parameter | タイプ | 説明 |
---|---|---|
functionToRetry | JavaScript 関数 | 再試行する関数。 |
functionName | 文字列 | 再試行する関数の名前。 |
operationId | ObjectId | 再試行を含むメイン関数の実行のための一意の識別子。 |
previousRetries | 番号 | メイン関数が以前に試行された回数。 |
...args | 残りのパラメータ | メイン関数に渡される引数の数は無制限。 |
handleRetry
次の操作を実行します。
try
ステートメントでfunctionToRetry
の実行を試みます。 実行が成功した場合、handleRetry
はfunctionToRetry
によって返された値を返します。前のステップで
functionToRetry
の実行でエラーがスローされた場合、catch
ステートメントは次のようにエラーを処理します。以前の再試行回数が最大再試行回数と等しいかどうかを確認します。 2 つの数値が同じ場合、最大再試行に達しているため、関数はエラーをスローします。 関数は再試行を行わなくなりました。
データベースに挿入する関数の実行ログ エントリ オブジェクトを構築します。
失敗した実行追跡コレクションへの参照を取得します。
関数の実行ログ エントリを失敗した実行トレース機能のコレクションに挿入します。 この挿入操作により、次の手順で作成するデータベースtrigger関数が起動します。
メイン関数は引数functionToRetry
として渡されます。 handleRetry
はメインの関数の実行を試みます。 実行が失敗した場合、この関数はメイン関数を再試行します。
Functionsに移動します。 Create New Function ] ボタンをクリックします。
フィールドNameにhandleRetry
を追加します。
Function Editorに次のコードを追加し、関数を保存します。
// Tip: You could also put this in an App Services Value const MAX_FUNC_RETRIES = 5; async function handleRetry( functionToRetry, functionName, operationId, previousRetries, ...args ) { try { // Try to execute the main function const response = await functionToRetry(...args); return response; } catch (err) { // Evaluates if should retry function again. // If no retry, throws error and stops retrying. if (previousRetries === MAX_FUNC_RETRIES) { throw new Error( `Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}` ); } // Build function execution log entry for insertion into database. const logEntry = { operationId, errorMessage: err.message, timestamp: new Date(), retries: previousRetries + 1, args, functionName, }; // Get reference to database collection const executionLog = context.services .get("mongodb-atlas") .db("logs") .collection("failed_execution_logs"); // Add execution log entry to database await executionLog.insertOne(logEntry); return; } } exports = handleRetry;
Tip
App Services CLI のインストールと設定
CLI を使用して App Services App を更新する場合は、まずApp Services CLI をインストールして設定する必要があります。
以下をfunctions/config.json
に追加します。
[ { "name": "handleRetry", "private": true, "run_as_system": true } // ...other configuration ]
関数functions/handleRetry.js
の ファイルを作成します。
// Tip: You could also put this in an App Services Value const MAX_FUNC_RETRIES = 5; async function handleRetry( functionToRetry, functionName, operationId, previousRetries, ...args ) { try { // Try to execute the main function const response = await functionToRetry(...args); return response; } catch (err) { // Evaluates if should retry function again. // If no retry, throws error and stops retrying. if (previousRetries === MAX_FUNC_RETRIES) { throw new Error( `Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}` ); } // Build function execution log entry for insertion into database. const logEntry = { operationId, errorMessage: err.message, timestamp: new Date(), retries: previousRetries + 1, args, functionName, }; // Get reference to database collection const executionLog = context.services .get("mongodb-atlas") .db("logs") .collection("failed_execution_logs"); // Add execution log entry to database await executionLog.insertOne(logEntry); return; } } exports = handleRetry;
変更を App Services にプッシュします。
appservices push
再試行データベースtrigger作成する
アプリの UI でTriggersに移動します。
Add a Triggerボタンをクリックします。
次の構成で trigger を作成します。
フィールド値名前選択した名前(例:retryOperation
)enabledはい再有効化時にイベントをスキップはいイベントの順序付けはいクラスター名選択した名前(例:mongodb-atlas
)databaseName選択した名前(例:logs
)コレクション名選択した名前(例:failed_execution_logs
)操作タイプInsert完全な文書はいドキュメントのプレイメージNoイベントタイプの選択関数関数[ + New Functionをクリックします。 関数の内容については、次の情報を参照してください。高度な構成詳細な構成は必要ありません。
データベースtriggerの構成を追加します。 詳細については、「 trigger 構成リファレンス 」を参照してください。
{ "name": "retry", "type": "DATABASE", "config": { "operation_types": ["INSERT"], "database": "logs", "collection": "failed_execution_logs", "service_name": "mongodb-atlas", "project": {}, "full_document": true, "full_document_before_change": false, "unordered": false, "skip_catchup_events": false }, "disabled": false, "event_processors": { "FUNCTION": { "config": { "function_name": "retryOperationDbTrigger" } } } }
ここで、trigger が呼び出す関数のコードを追加します。
関数retryOperation
は、再試行ハンドラーが失敗した実行トグル コレクションに投稿されたドキュメントであるlogEntry
をパラメーターとして受け取ります。 次に、 retryOperation
はcontext.functions.execute()を使用して、 logEntry
からの情報でメイン関数を呼び出します。
フィールドFunction NameにretryOperationDbTrigger
を追加します。
フィールドFunctionに次のコードを追加し、trigger を保存します。
async function retryOperation({ fullDocument: logEntry }) { // parse values from log entry posted to database const { args, retries, functionName, operationId } = logEntry; // Re-execute the main function await context.functions.execute(functionName, ...args, operationId, retries); } exports = retryOperation;
関数のメタデータをfunctions/config.json
に追加します。
[ // ...other configuration { "name": "retryOperationDbTrigger", "private": true } ]
ファイルfunctions/retryOperationDbTrigger.js
に次のコードを追加します。
async function retryOperation({ fullDocument: logEntry }) { // parse values from log entry posted to database const { args, retries, functionName, operationId } = logEntry; // Re-execute the main function await context.functions.execute(functionName, ...args, operationId, retries); } exports = retryOperation;
変更を App Services にプッシュします。
appservices push
再試行する関数の書き込み
関数ハンドラーと再試行データベースtrigger関数が用意できたら、メインの関数を記述できます。
次の例では、関数は追加を実行するときにランダムにエラーをスローします。 このロジックを実行する JavaScript 関数は次のとおりです。
getRandomOneTwoThree()
: 例のエラーを生成するためのヘルパー関数。additionOrFailure()
: メイン ロジックを持つ関数。
再試行ハンドラーによってラップされたadditionOrFailure()
の呼び出しは、エクスポートされた関数additionWithRetryHandler()
で発生します。 再試行ハンドラー関数を使用するすべての関数は、この関数のようになります。
この関数が再試行ロジックの残りの部分で動作するようにするには、正しいパラメーターを含める必要があります。 これらのパラメーターは次のとおりです。
Parameter | タイプ | 説明 |
---|---|---|
...args | 残りのパラメータ | メイン ロジックを持つ関数に渡す 0 個以上のパラメーター。 この例の場合、 additionOrFailure() 、 num1 、 num2 に追加された 2 つの数値。 |
operationId | 関数の呼び出しと再試行のための一意の識別子。 デフォルト値を new BSON.ObjectId() に設定します。 | |
retries | 番号 | デフォルト値を 0 に設定します。 |
additionWithRetryHandler
の本体は、 context.functions.execute()
によって呼び出される再試行ハンドラーhandleRetry
であり、これによりadditionOrFailure
が呼び出されます。 context.functions.execute() に渡す引数は次のとおりです。
Argument | タイプ | 説明 |
---|---|---|
"handleRetry" | 文字列 | メイン関数が正しく実行されない場合にメイン関数を呼び出し、再試行ログに投稿するために定義した関数の名前。 |
additionOrFailure | JavaScript 関数 | handleRetry() が呼び出すメイン関数。 |
operationId | BSON.ObjectId | additionWithRetryHandler() のパラメータoperationId からの引数として渡されます。 |
retries | 番号 | additionWithRetryHandler() のパラメータretries からの引数として渡されます。 |
...args | 引数を分散する | メイン ロジックを持つ関数に渡す 0 個以上の引数。 additionWithRetryHandler() のパラメータ...args からの引数として渡されます |
フィールドFunction NameにadditionWithRetryHandler
を追加します。
フィールドFunctionに次のコードを追加し、関数を保存します。
// randomly generates 1, 2, or 3 function getRandomOneTwoThree() { return Math.floor(Math.random() * 3) + 1; } function additionOrFailure(num1, num2) { // Throw error if getRandomOneTwoThree returns 1 const rand = getRandomOneTwoThree(); if (rand === 1) throw new Error("Uh oh!!"); const sum = num1 + num2; console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`); // Otherwise return the sum return sum; } async function additionWithRetryHandler( inputVar1, inputVar2, // create a new `operation_id` if one not provided operationId = new BSON.ObjectId(), // count number of attempts retries = 0 ) { const res = await context.functions.execute( "handleRetry", additionOrFailure, "additionWithRetryHandler", // MUST BE NAME OF FUNCTION operationId, retries, inputVar1, inputVar2 ); return res; } exports = additionWithRetryHandler;
関数のメタデータをfunctions/config.json
に追加します。
[ // ...other configuration { "name": "additionWithRetryHandler", "private": false } ]
ファイルfunctions/additionWithRetryHandler.js
に次のコードを追加します。
// randomly generates 1, 2, or 3 function getRandomOneTwoThree() { return Math.floor(Math.random() * 3) + 1; } function additionOrFailure(num1, num2) { // Throw error if getRandomOneTwoThree returns 1 const rand = getRandomOneTwoThree(); if (rand === 1) throw new Error("Uh oh!!"); const sum = num1 + num2; console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`); // Otherwise return the sum return sum; } async function additionWithRetryHandler( inputVar1, inputVar2, // create a new `operation_id` if one not provided operationId = new BSON.ObjectId(), // count number of attempts retries = 0 ) { const res = await context.functions.execute( "handleRetry", additionOrFailure, "additionWithRetryHandler", // MUST BE NAME OF FUNCTION operationId, retries, inputVar1, inputVar2 ); return res; } exports = additionWithRetryHandler;
変更を App Services にプッシュします。
appservices push
これでadditionWithRetryHandler
を呼び出すと、失敗すると関数は再試行されます。