方案:逻辑应用的异常处理和错误日志记录Scenario: Exception handling and error logging for logic apps

本方案介绍如何扩展逻辑应用以更好地支持异常处理。This scenario describes how you can extend a logic app to better support exception handling. 我们已经通过一个实际用例回答了“Azure 逻辑应用是否支持异常和错误处理?”的问题We've used a real-life use case to answer the question: "Does Azure Logic Apps support exception and error handling?"

备注

当前的 Azure 逻辑应用架构提供操作响应的标准模板。The current Azure Logic Apps schema provides a standard template for action responses. 此模板包括内部验证以及从 API 应用返回的错误响应。This template includes both internal validation and error responses returned from an API app.

方案和用例概述Scenario and use case overview

下面是本方案的用例:Here's the story as the use case for this scenario:

一个著名的医疗保健组织与我们合作开发一个使用 Microsoft Dynamics CRM Online 创建患者门户的 Azure 解决方案。A well-known healthcare organization engaged us to develop an Azure solution that would create a patient portal by using Microsoft Dynamics CRM Online. 他们需要在 Dynamics CRM Online 患者门户与 Salesforce 之间发送约会记录。They needed to send appointment records between the Dynamics CRM Online patient portal and Salesforce. 我们需要对所有患者记录使用 HL7 FHIR 标准。We were asked to use the HL7 FHIR standard for all patient records.

该项目具有两个主要要求:The project had two major requirements:

  • 用于对从 Dynamics CRM Online 门户发送的记录进行日志记录的一种方法A method to log records sent from the Dynamics CRM Online portal
  • 用于查看工作流中发生的任何错误的一种方式A way to view any errors that occurred within the workflow

提示

有关本项目的高级视频,请参阅集成用户组For a high-level video about this project, see Integration User Group.

我们如何解决问题How we solved the problem

我们选择 Azure Cosmos DB 作为日志和错误记录的存储库(Cosmos DB 将记录作为文档来引用)。We chose Azure Cosmos DB As a repository for the log and error records (Cosmos DB refers to records as documents). 由于 Azure 逻辑应用具有用于所有响应的标准模板,因此我们不必创建自定义架构。Because Azure Logic Apps has a standard template for all responses, we would not have to create a custom schema. 我们可以创建 API 应用以便对错误和日志记录进行插入查询We could create an API app to Insert and Query for both error and log records. 我们还可以在 API 应用中为各个操作定义架构。We could also define a schema for each within the API app.

另一个要求是清除特定日期之后的记录。Another requirement was to purge records after a certain date. Cosmos DB 具有一个名为 Time to Live (TTL) 的属性,使用该属性可以为每个记录或集合设置“生存时间” 值。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. 这样便无需在 Cosmos DB 中手动删除记录。This capability eliminated the need to manually delete records in Cosmos DB.

重要

若要完成本教程,需要创建一个 Cosmos DB 数据库和两个集合(日志记录和错误)。To complete this tutorial, you need to create a Cosmos DB database and two collections (Logging and Errors).

创建逻辑应用Create the logic app

第一步是在逻辑应用设计器中创建并打开逻辑应用。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.

因为我们要对从 Dynamics CRM Online 传出的记录进行日志记录,所以我们从顶部开始。Because we are going to log the record coming out of Dynamics CRM Online, let's start at the top. 我们必须使用“Request” 触发器,因为父逻辑应用会触发此子级。We must use a Request trigger because the parent logic app triggers this child.

逻辑应用触发器Logic app trigger

我们使用如下面示例中所示的“Request” 触发器。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"
            }
          }
        }
      },

步骤Steps

必须对来自 Dynamics CRM Online 门户的患者记录的源(请求)进行日志记录。We must log the source (request) of the patient record from the Dynamics CRM Online portal.

  1. 必须从 Dynamics CRM Online 获取新的预约记录。We must get a new appointment record from Dynamics CRM Online.

    来自 CRM 的触发器为我们提供 CRM PatentId记录类型新的或更新的记录(新的或更新的布尔值)以及 SalesforceIdThe trigger coming from CRM provides us with the CRM PatentId, record type, New or Updated Record (new or update Boolean value), and SalesforceId. SalesforceId 可以为 null,因为它只用于更新。The SalesforceId can be null because it's only used for an update. 使用 CRM 的“PatientID” 和“记录类型” 来获取 CRM 记录。We get the CRM record by using the CRM PatientID and the Record Type.

  2. 接下来,需要在逻辑应用设计器中添加 Azure Cosmos DB SQL API 应用 InsertLogEntry 操作,如下所示。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

    条件

逻辑应用源代码Logic app source code

备注

以下内容只是示例。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.>

日志记录Logging

以下逻辑应用代码示例演示如何处理日志记录。The following logic app code sample shows how to handle logging.

日志项Log entry

下面是用于插入日志条目的逻辑应用源代码。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"]
        }
}

日志请求Log request

下面是发布到 API 应用的日志请求消息。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/"}"
        }
    }

日志响应Log response

下面是来自 API 应用的日志响应消息。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.

错误处理。Error handling

以下逻辑应用代码示例演示如何实现错误处理。The following logic app code sample shows how you can implement error handling.

创建错误记录Create error record

下面是用于创建错误记录的逻辑应用源代码。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" ]
        }
    }
}             

将错误插入 Cosmos DB 中 - 请求Insert error into Cosmos DB--request


{
    "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"
    }
}

将错误插入 Cosmos DB 中 - 响应Insert error into Cosmos DB--response

{
    "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
        }
}

Salesforce 错误响应Salesforce error response

{
    "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": []
    }
}

将响应返回给父逻辑应用Return the response back to parent logic app

获取响应之后,可以将它传递回父逻辑应用。After you get the response, you can pass the response back to the parent logic app.

将成功响应返回给父逻辑应用Return success response to 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"
}

将错误响应返回给父逻辑应用Return error response to parent logic app

"ErrorResponse": {
    "runAfter":
        {
            "Create_NewPatientRecord": ["Failed"]
        },
    "inputs": {
        "body": {
            "status": "BadRequest"
        },
        "headers": {
            "Content-type": "application/json",
            "x-ms-date": "@utcnow()"
        },
        "statusCode": 400
    },
    "type": "Response"
}

Cosmos DB 存储库和门户Cosmos DB repository and portal

我们的解决方案通过引入 Azure Cosmos DB 新增了多项功能。Our solution added capabilities with Azure Cosmos DB.

错误管理门户Error management portal

若要查看这些错误,可以创建 MVC Web 应用,显示来自 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.

备注

“编辑”操作:Cosmos DB 对整个文档进行替换。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.

下面是使用前面所述的方法创建的 MVC 应用详细信息的示例。Here are examples of our MVC app details created with the previously described approach.

错误管理列表Error management list

错误列表

错误管理详细信息视图Error management detail view

错误详细信息

日志管理门户Log management portal

为了查看日志,我们也创建了一个 MVC web 应用。To view the logs, we also created an MVC web app. 下面是使用前面所述的方法创建的 MVC 应用详细信息的示例。Here are examples of our MVC app details created with the previously described approach.

示例日志详细信息视图Sample log detail view

日志详细信息视图

API 应用详细信息API app details

逻辑应用异常管理 APILogic Apps exception management API

我们的开源 Azure 逻辑应用异常管理 API 应用提供如下所述的功能 - 有两个控制器:Our open-source Azure Logic Apps exception management API app provides functionality as described here - there are two controllers:

  • ErrorController 将错误记录(文档)插入 Azure Cosmos DB 集合中。ErrorController inserts an error record (document) in an Azure Cosmos DB collection.
  • LogController 将错误记录(文档)插入 Azure Cosmos DB 集合中。LogController Inserts a log record (document) in an Azure Cosmos DB collection.

提示

这两个控制器都使用 async Task<dynamic> 操作,这样可以在运行时解析操作,因此我们可以在操作的正文中创建 Azure Cosmos DB 架构。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.

Azure Cosmos DB 中的每个文档都必须具有唯一 ID。Every document in Azure Cosmos DB must have a unique ID. 我们使用 PatientId 并添加戳转换为 Unix 时间戳值(双精度型)的时间戳。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.

可以从 GitHub 查看我们的错误控制器 API 的源代码。You can view the source code of our error controller API from GitHub.

使用以下语法从逻辑应用调用该 API: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"]
            }
        }
 }

前面代码示例中的表达式检查“Create_NewPatientRecord” 的状态是否为“Failed” 。The expression in the preceding code sample checks for the Create_NewPatientRecord status of Failed.

总结Summary

  • 可以在逻辑应用中轻松实现日志记录和错误处理。You can easily implement logging and error handling in a logic app.
  • 可以使用 Azure Cosmos DB 作为日志和错误记录的存储库(文档)。You can use Azure Cosmos DB as the repository for log and error records (documents).
  • 可以使用 MVC 创建门户来显示日志和错误记录。You can use MVC to create a portal to display log and error records.

源代码Source code

逻辑应用异常管理 API 应用程序的源代码可在此 GitHub 存储库中找到。The source code for the Logic Apps exception management API application is available in this GitHub repository.

后续步骤Next steps