title | description | services | ms.suite | author | ms.reviewer | ms.topic | ms.date |
---|---|---|---|---|---|---|---|
Exception handling & error logging scenario |
Advanced exception handling and error logging in Azure Logic Apps. |
logic-apps |
integration |
hedidin |
estfan, azla |
how-to |
07/29/2016 |
This scenario describes how you can extend a logic app to better support exception handling. We've used a real-life use case to answer the question: "Does Azure Logic Apps support exception and error handling?"
Note
The current Azure Logic Apps schema provides a standard template for action responses. This template includes both internal validation and error responses returned from an API app.
Here's the story as the use case for this scenario:
A well-known healthcare organization engaged us to develop an Azure solution that would create a patient portal by using Microsoft Dynamics CRM Online. They needed to send appointment records between the Dynamics CRM Online patient portal and Salesforce. We were asked to use the HL7 FHIR standard for all patient records.
The project had two major requirements:
- A method to log records sent from the Dynamics CRM Online portal
- A way to view any errors that occurred within the workflow
Tip
For a high-level video about this project, see Integration User Group.
We chose Azure Cosmos DB As a repository for the log and error records (Cosmos DB refers to records as documents). Because Azure Logic Apps has a standard template for all responses, we would not have to create a custom schema. We could create an API app to Insert and Query for both error and log records. We could also define a schema for each within the API app.
Another requirement was to purge records after a certain date. Cosmos DB has a property called Time to Live (TTL), which allowed us to set a Time to Live value for each record or collection. This capability eliminated the need to manually delete records in Cosmos DB.
Important
To complete this tutorial, you need to create a Cosmos DB database and two collections (Logging and Errors).
The first step is to create the logic app and open the app in Logic App Designer. In this example, we are using parent-child logic apps. Let's assume that we have already created the parent and are going to create one child logic app.
Because we are going to log the record coming out of Dynamics CRM Online, let's start at the top. We must use a Request trigger because the parent logic app triggers this child.
We are using a Request trigger as shown in the following example:
"triggers": {
"request": {
"type": "request",
"kind": "http",
"inputs": {
"schema": {
"properties": {
"CRMid": {
"type": "string"
},
"recordType": {
"type": "string"
},
"salesforceID": {
"type": "string"
},
"update": {
"type": "boolean"
}
},
"required": [
"CRMid",
"recordType",
"salesforceID",
"update"
],
"type": "object"
}
}
}
},
We must log the source (request) of the patient record from the Dynamics CRM Online portal.
-
We must get a new appointment record from Dynamics CRM Online.
The trigger coming from CRM provides us with the CRM PatentId, record type, New or Updated Record (new or update Boolean value), and SalesforceId. The SalesforceId can be null because it's only used for an update. We get the CRM record by using the CRM PatientID and the Record Type.
-
Next, we need to add our Azure Cosmos DB SQL API app InsertLogEntry operation as shown here in Logic App Designer.
Insert log entry
Insert error entry
Check for create record failure
Note
The following examples are samples only. Because this tutorial is based on an implementation now in production, the value of a Source Node might not display properties that are related to scheduling an appointment.>
The following logic app code sample shows how to handle logging.
Here is the logic app source code for inserting a log entry.
"InsertLogEntry": {
"metadata": {
"apiDefinitionUrl": "https://.../swagger/docs/v1",
"swaggerSource": "website"
},
"type": "Http",
"inputs": {
"body": {
"date": "@{outputs('Gets_NewPatientRecord')['headers']['Date']}",
"operation": "New Patient",
"patientId": "@{triggerBody()['CRMid']}",
"providerId": "@{triggerBody()['providerID']}",
"source": "@{outputs('Gets_NewPatientRecord')['headers']}"
},
"method": "post",
"uri": "https://.../api/Log"
},
"runAfter": {
"Gets_NewPatientecord": ["Succeeded"]
}
}
Here is the log request message posted to the API app.
{
"uri": "https://.../api/Log",
"method": "post",
"body": {
"date": "Fri, 10 Jun 2016 22:31:56 GMT",
"operation": "New Patient",
"patientId": "6b115f6d-a7ee-e511-80f5-3863bb2eb2d0",
"providerId": "",
"source": "{/"Pragma/":/"no-cache/",/"x-ms-request-id/":/"e750c9a9-bd48-44c4-bbba-1688b6f8a132/",/"OData-Version/":/"4.0/",/"Cache-Control/":/"no-cache/",/"Date/":/"Fri, 10 Jun 2016 22:31:56 GMT/",/"Set-Cookie/":/"ARRAffinity=785f4334b5e64d2db0b84edcc1b84f1bf37319679aefce206b51510e56fd9770;Path=/;Domain=127.0.0.1/",/"Server/":/"Microsoft-IIS/8.0,Microsoft-HTTPAPI/2.0/",/"X-AspNet-Version/":/"4.0.30319/",/"X-Powered-By/":/"ASP.NET/",/"Content-Length/":/"1935/",/"Content-Type/":/"application/json; odata.metadata=minimal; odata.streaming=true/",/"Expires/":/"-1/"}"
}
}
Here is the log response message from the API app.
{
"statusCode": 200,
"headers": {
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"Date": "Fri, 10 Jun 2016 22:32:17 GMT",
"Server": "Microsoft-IIS/8.0",
"X-AspNet-Version": "4.0.30319",
"X-Powered-By": "ASP.NET",
"Content-Length": "964",
"Content-Type": "application/json; charset=utf-8",
"Expires": "-1"
},
"body": {
"ttl": 2592000,
"id": "6b115f6d-a7ee-e511-80f5-3863bb2eb2d0_1465597937",
"_rid": "XngRAOT6IQEHAAAAAAAAAA==",
"_self": "dbs/XngRAA==/colls/XngRAOT6IQE=/docs/XngRAOT6IQEHAAAAAAAAAA==/",
"_ts": 1465597936,
"_etag": "/"0400fc2f-0000-0000-0000-575b3ff00000/"",
"patientID": "6b115f6d-a7ee-e511-80f5-3863bb2eb2d0",
"timestamp": "2016-06-10T22:31:56Z",
"source": "{/"Pragma/":/"no-cache/",/"x-ms-request-id/":/"e750c9a9-bd48-44c4-bbba-1688b6f8a132/",/"OData-Version/":/"4.0/",/"Cache-Control/":/"no-cache/",/"Date/":/"Fri, 10 Jun 2016 22:31:56 GMT/",/"Set-Cookie/":/"ARRAffinity=785f4334b5e64d2db0b84edcc1b84f1bf37319679aefce206b51510e56fd9770;Path=/;Domain=127.0.0.1/",/"Server/":/"Microsoft-IIS/8.0,Microsoft-HTTPAPI/2.0/",/"X-AspNet-Version/":/"4.0.30319/",/"X-Powered-By/":/"ASP.NET/",/"Content-Length/":/"1935/",/"Content-Type/":/"application/json; odata.metadata=minimal; odata.streaming=true/",/"Expires/":/"-1/"}",
"operation": "New Patient",
"salesforceId": "",
"expired": false
}
}
Now let's look at the error handling steps.
The following logic app code sample shows how you can implement error handling.
Here is the logic app source code for creating an error record.
"actions": {
"CreateErrorRecord": {
"metadata": {
"apiDefinitionUrl": "https://.../swagger/docs/v1",
"swaggerSource": "website"
},
"type": "Http",
"inputs": {
"body": {
"action": "New_Patient",
"isError": true,
"crmId": "@{triggerBody()['CRMid']}",
"patientID": "@{triggerBody()['CRMid']}",
"message": "@{body('Create_NewPatientRecord')['message']}",
"providerId": "@{triggerBody()['providerId']}",
"severity": 4,
"source": "@{actions('Create_NewPatientRecord')['inputs']['body']}",
"statusCode": "@{int(outputs('Create_NewPatientRecord')['statusCode'])}",
"salesforceId": "",
"update": false
},
"method": "post",
"uri": "https://.../api/CrMtoSfError"
},
"runAfter":
{
"Create_NewPatientRecord": ["Failed" ]
}
}
}
{
"uri": "https://.../api/CrMtoSfError",
"method": "post",
"body": {
"action": "New_Patient",
"isError": true,
"crmId": "6b115f6d-a7ee-e511-80f5-3863bb2eb2d0",
"patientId": "6b115f6d-a7ee-e511-80f5-3863bb2eb2d0",
"message": "Salesforce failed to complete task: Message: duplicate value found: Account_ID_MED__c duplicates value on record with id: 001U000001c83gK",
"providerId": "",
"severity": 4,
"salesforceId": "",
"update": false,
"source": "{/"Account_Class_vod__c/":/"PRAC/",/"Account_Status_MED__c/":/"I/",/"CRM_HUB_ID__c/":/"6b115f6d-a7ee-e511-80f5-3863bb2eb2d0/",/"Credentials_vod__c/",/"DTC_ID_MED__c/":/"/",/"Fax/":/"/",/"FirstName/":/"A/",/"Gender_vod__c/":/"/",/"IMS_ID__c/":/"/",/"LastName/":/"BAILEY/",/"MasterID_mp__c/":/"/",/"C_ID_MED__c/":/"851588/",/"Middle_vod__c/":/"/",/"NPI_vod__c/":/"/",/"PDRP_MED__c/":false,/"PersonDoNotCall/":false,/"PersonEmail/":/"/",/"PersonHasOptedOutOfEmail/":false,/"PersonHasOptedOutOfFax/":false,/"PersonMobilePhone/":/"/",/"Phone/":/"/",/"Practicing_Specialty__c/":/"FM - FAMILY MEDICINE/",/"Primary_City__c/":/"/",/"Primary_State__c/":/"/",/"Primary_Street_Line2__c/":/"/",/"Primary_Street__c/":/"/",/"Primary_Zip__c/":/"/",/"RecordTypeId/":/"012U0000000JaPWIA0/",/"Request_Date__c/":/"2016-06-10T22:31:55.9647467Z/",/"ONY_ID__c/":/"/",/"Specialty_1_vod__c/":/"/",/"Suffix_vod__c/":/"/",/"Website/":/"/"}",
"statusCode": "400"
}
}
{
"statusCode": 200,
"headers": {
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"Date": "Fri, 10 Jun 2016 22:31:57 GMT",
"Server": "Microsoft-IIS/8.0",
"X-AspNet-Version": "4.0.30319",
"X-Powered-By": "ASP.NET",
"Content-Length": "1561",
"Content-Type": "application/json; charset=utf-8",
"Expires": "-1"
},
"body": {
"id": "6b115f6d-a7ee-e511-80f5-3863bb2eb2d0-1465597917",
"_rid": "sQx2APhVzAA8AAAAAAAAAA==",
"_self": "dbs/sQx2AA==/colls/sQx2APhVzAA=/docs/sQx2APhVzAA8AAAAAAAAAA==/",
"_ts": 1465597912,
"_etag": "/"0c00eaac-0000-0000-0000-575b3fdc0000/"",
"prescriberId": "6b115f6d-a7ee-e511-80f5-3863bb2eb2d0",
"timestamp": "2016-06-10T22:31:57.3651027Z",
"action": "New_Patient",
"salesforceId": "",
"update": false,
"body": "CRM failed to complete task: Message: duplicate value found: CRM_HUB_ID__c duplicates value on record with id: 001U000001c83gK",
"source": "{/"Account_Class_vod__c/":/"PRAC/",/"Account_Status_MED__c/":/"I/",/"CRM_HUB_ID__c/":/"6b115f6d-a7ee-e511-80f5-3863bb2eb2d0/",/"Credentials_vod__c/":/"DO - Degree level is DO/",/"DTC_ID_MED__c/":/"/",/"Fax/":/"/",/"FirstName/":/"A/",/"Gender_vod__c/":/"/",/"IMS_ID__c/":/"/",/"LastName/":/"BAILEY/",/"MterID_mp__c/":/"/",/"Medicis_ID_MED__c/":/"851588/",/"Middle_vod__c/":/"/",/"NPI_vod__c/":/"/",/"PDRP_MED__c/":false,/"PersonDoNotCall/":false,/"PersonEmail/":/"/",/"PersonHasOptedOutOfEmail/":false,/"PersonHasOptedOutOfFax/":false,/"PersonMobilePhone/":/"/",/"Phone/":/"/",/"Practicing_Specialty__c/":/"FM - FAMILY MEDICINE/",/"Primary_City__c/":/"/",/"Primary_State__c/":/"/",/"Primary_Street_Line2__c/":/"/",/"Primary_Street__c/":/"/",/"Primary_Zip__c/":/"/",/"RecordTypeId/":/"012U0000000JaPWIA0/",/"Request_Date__c/":/"2016-06-10T22:31:55.9647467Z/",/"XXXXXXX/":/"/",/"Specialty_1_vod__c/":/"/",/"Suffix_vod__c/":/"/",/"Website/":/"/"}",
"code": 400,
"errors": null,
"isError": true,
"severity": 4,
"notes": null,
"resolved": 0
}
}
{
"statusCode": 400,
"headers": {
"Pragma": "no-cache",
"x-ms-request-id": "3e8e4884-288e-4633-972c-8271b2cc912c",
"X-Content-Type-Options": "nosniff",
"Cache-Control": "no-cache",
"Date": "Fri, 10 Jun 2016 22:31:56 GMT",
"Set-Cookie": "ARRAffinity=785f4334b5e64d2db0b84edcc1b84f1bf37319679aefce206b51510e56fd9770;Path=/;Domain=127.0.0.1",
"Server": "Microsoft-IIS/8.0,Microsoft-HTTPAPI/2.0",
"X-AspNet-Version": "4.0.30319",
"X-Powered-By": "ASP.NET",
"Content-Length": "205",
"Content-Type": "application/json; charset=utf-8",
"Expires": "-1"
},
"body": {
"status": 400,
"message": "Salesforce failed to complete task: Message: duplicate value found: Account_ID_MED__c duplicates value on record with id: 001U000001c83gK",
"source": "Salesforce.Common",
"errors": []
}
}
After you get the response, you can pass the response back to the parent logic app.
"SuccessResponse": {
"runAfter":
{
"UpdateNew_CRMPatientResponse": ["Succeeded"]
},
"inputs": {
"body": {
"status": "Success"
},
"headers": {
" Content-type": "application/json",
"x-ms-date": "@utcnow()"
},
"statusCode": 200
},
"type": "Response"
}
"ErrorResponse": {
"runAfter":
{
"Create_NewPatientRecord": ["Failed"]
},
"inputs": {
"body": {
"status": "BadRequest"
},
"headers": {
"Content-type": "application/json",
"x-ms-date": "@utcnow()"
},
"statusCode": 400
},
"type": "Response"
}
Our solution added capabilities with Azure Cosmos DB.
To view the errors, you can create an MVC web app to display the error records from Cosmos DB. The List, Details, Edit, and Delete operations are included in the current version.
Note
Edit operation: Cosmos DB replaces the entire document. The records shown in the List and Detail views are samples only. They are not actual patient appointment records.
Here are examples of our MVC app details created with the previously described approach.
To view the logs, we also created an MVC web app. Here are examples of our MVC app details created with the previously described approach.
Our open-source Azure Logic Apps exception management API app provides functionality as described here - there are two controllers:
- ErrorController inserts an error record (document) in an Azure Cosmos DB collection.
- LogController Inserts a log record (document) in an Azure Cosmos DB collection.
Tip
Both controllers use async Task<dynamic>
operations,
allowing operations to resolve at runtime,
so we can create the Azure Cosmos DB schema in the body of the operation.
Every document in Azure Cosmos DB must have a unique ID.
We are using PatientId
and adding a timestamp that is converted to a Unix timestamp value (double).
We truncate the value to remove the fractional value.
You can view the source code of our error controller API from GitHub.
We call the API from a logic app by using the following syntax:
"actions": {
"CreateErrorRecord": {
"metadata": {
"apiDefinitionUrl": "https://.../swagger/docs/v1",
"swaggerSource": "website"
},
"type": "Http",
"inputs": {
"body": {
"action": "New_Patient",
"isError": true,
"crmId": "@{triggerBody()['CRMid']}",
"prescriberId": "@{triggerBody()['CRMid']}",
"message": "@{body('Create_NewPatientRecord')['message']}",
"salesforceId": "@{triggerBody()['salesforceID']}",
"severity": 4,
"source": "@{actions('Create_NewPatientRecord')['inputs']['body']}",
"statusCode": "@{int(outputs('Create_NewPatientRecord')['statusCode'])}",
"update": false
},
"method": "post",
"uri": "https://.../api/CrMtoSfError"
},
"runAfter": {
"Create_NewPatientRecord": ["Failed"]
}
}
}
The expression in the preceding code sample checks for the Create_NewPatientRecord status of Failed.
- You can easily implement logging and error handling in a logic app.
- You can use Azure Cosmos DB as the repository for log and error records (documents).
- You can use MVC to create a portal to display log and error records.
The source code for the Logic Apps exception management API application is available in this GitHub repository.