Close

Exception Handling in Logic Apps

You can implement exception handling in Logic Apps via the RunAfter setting. When an action fails (RunAfter=Failed), you can specify a specific action to be executed.
See: docs.microsoft.com

You don’t want to specify exception handling on each and every failed actions however. That’s why you often see that actions are grouped into a scope with exception handling at the scope level. When you specify exception handling at the scope level, exception handling is enabled on all contained actions and and an array of failed action responses is returned. That sounds good, but there are a few snags to take into account.

Example of a failed action:

{
   "name": "Example_Action_That_Failed",
   "inputs": {
      "uri": "https://myfailedaction.azurewebsites.net",
      "method": "POST"
   },
   "outputs": {
      "statusCode": 404,
      "headers": {
         "Date": "Thu, 11 Aug 2016 03:18:18 GMT",
         "Server": "Microsoft-IIS/8.0",
         "X-Powered-By": "ASP.NET",
         "Content-Length": "68",
         "Content-Type": "application/json"
      },
      "body": {
         "code": "ResourceNotFound",
         "message": "/docs/folder-name/resource-name does not exist"
      }
   },
   "startTime": "2016-08-11T03:18:19.7755341Z",
   "endTime": "2016-08-11T03:18:20.2598835Z",
   "trackingId": "bdd82e28-ba2c-4160-a700-e3a8f1a38e22",
   "clientTrackingId": "08587307213861835591296330354",
   "code": "NotFound",
   "status": "Failed"
}
  • I thought the responses of all actions had the same format, but that’s not the case. The exception message can be located in different fields of the response message. For that reason we built an Azure function named ErrorMessageParser. This function examines the response message to find the specific exception message. See below for details on the ErrorMessageParser.
  • Another important point to remember is related to the use of the nested actions within a scope. Let’s say you have a condition with a failed action in one of the branches. In this case the logic app will actually return a failure, but the exception message will state “An action failed. No dependent actions succeeded” and not the exception message of the actually failed action. In other words, the scope will not return the exception details of nested actions.
  • A final point to remember is related to the use of foreach/until loops. If you loop through 100 items and 5 of those items fail, the logic app will return a failure, but you will have no idea which items actually failed. You could assemble the scope actions into a child scope and do the exception handling per item. Or you could add all exception messages to a variable and then return that variable at the end of the loop with the variable content as the exception message. Point is, you will have to find some way around it.
  • Consider the use of tracked properties to see the value of specific fields, for instance customer number of product number. Tracked properties will be shown in the Logic Apps Management Solution under Log Analytics and can help you solving the production issue.

Exception Handling (Run After Failed Scope):

The Filter Array action will return an array with the failed messages.

“from”: “@result(‘Scope_Process_Message’)”,
“where”: “@equals(item()[‘status’], ‘Failed’)”

The ErrorMessageParser takes the first action that failed and parses the response message:

@body(‘Filter_array’)[0]

Azure Function ErrorMessageParser looks as follows:

[FunctionName("ErrorMessageParser")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            JObject errorMessage = JObject.Parse(requestBody);
            string message = "Unknown error. Adjust the ErrorParser for a correct error description.";

            try
            {
                message = $"{errorMessage["name"]}: {errorMessage["error"]["message"]}";
            }
            catch
            {
                try
                {
                    message = $"{errorMessage["name"]}: {errorMessage["outputs"]["errors"][0]["message"]}";
                }
                catch
                {
                    try
                    {
                        message = $"{errorMessage["name"]}: {errorMessage["outputs"]["body"]["error"]["message"]}";
                    }
                    catch
                    {
                        try
                        {
                            message = $"{errorMessage["name"]}: {errorMessage["outputs"][0]["outputs"]["body"]["message"]}";
                        }
                        catch
                        {
                            try
                            {
                                byte[] data = Convert.FromBase64String((string)errorMessage["outputs"]["body"]["$content"]);
                                message = $"{errorMessage["name"]}: {Encoding.UTF8.GetString(data)}";
                            }
                            catch
                            {
                                try
                                {
                                    message = $"{errorMessage["name"]}: {errorMessage["outputs"]["body"]["message"]}";
                                }
                                catch
                                {
                                    try
                                    {
                                        if (errorMessage["outputs"]["body"].ToString() != "")
                                        {
                                            message = $"{errorMessage["name"]}: {errorMessage["outputs"]["body"]}";
                                        }
                                        else
                                        {
                                            throw new System.ArgumentException("errorMessage not defined");
                                        }
                                    }
                                    catch
                                    {
                                        try
                                        {
                                            if (errorMessage?["outputs"]["statusCode"].ToString() != "" && errorMessage["code"].ToString() != "")
                                            {
                                                message = $"{errorMessage["name"]}: {errorMessage?["outputs"]["statusCode"]} - {errorMessage["code"]}";
                                            }
                                            else
                                            {
                                                throw new System.ArgumentException("errorMessage not defined");
                                            }
                                        }
                                        catch
                                        {
                                            try
                                            {
                                                if (errorMessage["code"].ToString() != "")
                                                {
                                                    message = $"{errorMessage["name"]}: {errorMessage["code"]}";
                                                }
                                                else
                                                {
                                                    throw new System.ArgumentException("errorMessage not defined");
                                                }
                                            }
                                            catch
                                            {
                                                try
                                                {
                                                    message = $"{errorMessage["name"]}: {message}";
                                                }
                                                catch (Exception ex)
                                                {
                                                    return new OkObjectResult(message);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }        

            return new OkObjectResult(message);

    }