Error Handling and Diagnostics
Logging in orchestrator functions
An orchestrator function creates checkpoints as each of its composite activity functions complete. After each call to CallActivityAsync
on the DurableOrchestrationContext
instance completes, the orchestrator function will automatically replay to rebuild their in-memory state.
To prevent logging statements from duplicating, use the IsReplaying
property on the DurableOrchestrationContext
.
[FunctionName("PlaceOrder")]
public static async Task<InvoiceData> OrderOrchestration(
[OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
{
OrderRequestData orderData = context.GetInput<OrderRequestData>();
if (!context.IsReplaying) log.LogInformation("About Checking inventory");
await context.CallActivityAsync<bool>("CheckAndReserveInventory", orderData);
if (!context.IsReplaying) log.LogInformation("Processing payment");
InvoiceData invoice = await context.CallActivityAsync<InvoiceData>("ProcessPayment", orderData);
return invoice;
}
Takeaways
GetStatusAsync
method ofDurableOrchestrationClient
can be used to get status information for a given orchestration.- Alternatively, the status endpoint returned from
CreateCheckStatusResponse
can be used. - The execution history and results can be included in the response if
showHistory
andshowHistoryOutput
are set to true. GetStatusAsync
returns an instance ofDurableOrchestrationStatus
that contains all the status information.
Read more
- HTTP APIs in Durable Functions
- Debugging and Diagnostics of Durable Functions
- Durable Functions repo and samples
Handling activity function exceptions
Unhandled exceptions thrown in activity functions will be rethrown in the calling orchestrator function as a FunctionFailedException
. The InnerException
property of the FunctionFailedException
will contain the original exception from the activity.
[FunctionName("PlaceOrder")]
public static async Task OrderOrchestration(
[OrchestrationTrigger] DurableOrchestrationContext context,
TraceWriter log)
{
try
{
await context.CallActivityAsync<bool>("CheckAndReserveInventory", null);
}
catch (FunctionFailedException ex)
{
log.Error("Inventory check failed", ex);
}
}
[FunctionName("CheckAndReserveInventory")]
public static bool CheckAndReserveInventory([ActivityTrigger] DurableActivityContext context)
{
throw new ArgumentException("Oops...");
}
Takeaways
FunctionFailedException
is thrown in the orchestrator if an activity function throws an unhandled exceptionFunctionFailedException
'sInnerException
property will contain the source exception from the activity function.
Read more
- Handling errors in Durable Functions
- Debugging and Diagnostics of Durable Functions
- Durable Functions repo and samples
Calling activity functions with retry
When call activities in your orchestration functions, you may want to apply a retry policy to handle transient errors that may occur. In such cases, the DurableOrchestrationContext
provides the CallActivityWithRetryAsync
method. The difference between CallActivityAsync
and CallActivityWithRetryAsync
is that the latter accepts a type of RetryOptions
that specifies the retry behavior.
public static async Task OrderOrchestration(
[OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
{
RetryOptions retryPolicy = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(3),
maxNumberOfAttempts: 3);
retryPolicy.Handle = (ex) =>
{
TaskFailedException failedEx = ex as TaskFailedException;
return (failedEx.Name != "CheckAndReserveInventory") ? false : true;
};
try
{
await context.CallActivityWithRetryAsync<bool>("CheckAndReserveInventory", retryPolicy, null);
}
catch (FunctionFailedException ex)
{
log.LogError("Inventory check failed", ex);
}
}
Here is an activity function that throws an exception.
[FunctionName("CheckAndReserveInventory")]
public static async Task<bool> CheckAndReserveInventory([ActivityTrigger] DurableActivityContext context)
{
throw new Exception("Ooops...");
}
Takeaways
CallActivityWithRetryAsync
allows orchestrator functions to call activities with retry behavior.- A
RetryOptions
instance is used to define retry behavior. - A
RetryOptions
instance can be reused across multiple calls toCallActivityWithRetryAsync
. - The
Handle
property onRetryOptions
lets callers define whether retries should proceed or not.