使用 ILogger 将遥测数据写入 Application Insights 资源

Important

若要使用此功能,必须先使用管理员帐户启用 Application Insights 集成功能。 确保启用该功能的用户具有修改 Dataverse 组织(如系统管理员角色或 Power Platform/Dynamics 365 管理员)的必要权限,并且具有对 Application Insights 资源的参与者访问权限。 如果没有必要权限的用户启用集成,则遥测数据不会写入 Application Insights。 有关详细信息,请参阅使用 Application Insights 分析模型驱动应用和Microsoft Dataverse遥测

目前,在插件注册工具或 Visual Studio 的 Power Platform Tools 扩展的插件性能分析或调试会话中,尚不支持 ILogger

为组织启用 Application Insights 后,任何使用 .NET 程序集 SDK 中提供的 ILogger 接口 编写的插件都会将遥测数据写入你的 Application Insights 资源。

Dataverse 平台捕获 Dataverse 和模型驱动应用遥测数据,并将其导出到 Application Insights 资源。 捕获时间与 Application Insights 中可供你使用的时间之间存在一些延迟。 由于Microsoft收集此遥测数据,因此无需编写任何代码来启用它。

来自使用 ILogger 接口的插件的遥测数据有两个不同之处:

  • 此遥测数据直接写入 Application Insights 资源,永远不会发送到Microsoft。
    • 查看此数据时延迟较低。
  • 必须更新插件代码才能使用 ILogger 接口。

使用 ILogger 提供真正的遥测数据,旨在与使用 ITracingService 接口编写的现有插件跟踪日志协同工作。 下表比较了这些功能:

Criteria 适用于 Application Insights 的 ILogger ITracingService 对插件跟踪日志的跟踪
预期用途 捕获随时间推移的遥测数据进行分析和调试。 开发和调试插件时
存储数据的时间 根据你的 Application Insights 数据保留期(默认为 90 天) 24 小时
可用的 仅适用于订阅 Application Insights 集成的组织。 启用插件跟踪后,适用于任何组织。
数据量 每个日志消息都可以传递字符串值。 每次插件执行最多只能写入 10 kb 文本。 文本在第一个 10 kb 之后被截断。
在运行时错误中可用 可在模型驱动应用的客户端错误信息中使用,也可作为 Web API 中的注释提供。 有关详细信息,请参阅在错误中包含更多详细信息

如果需要,应继续使用 ITracingService.Trace 将信息写入插件跟踪日志表。 并非每个组织都启用 Application Insights。 如果插件代码使用 ILogger 接口,而组织未启用 Application Insights 集成,则不会写入任何内容。 因此,请务必继续使用插件中的 ITracingService Trace 方法。插件跟踪日志仍然是在开发和调试插件时捕获数据的重要方法,但它们从未用于提供遥测数据。 有关详细信息,请参阅 插件:跟踪和日志记录

应使用 ILogger ,因为它提供有关插件内发生的情况的遥测数据。 此遥测数据已整合到通过与 Application Insights 的集成所收集的更大范围的数据中。 Application Insights 集成会告诉你插件何时执行、运行需要多长时间以及它是否发出任何外部 http 请求,但Microsoft无法在你编写的插件中添加任何遥测代码来扩展平台的行为。

如果你是其产品包含插件的 ISV,那么那些启用了 Application Insights 的客户会很看重能够查看你的插件内部的运行情况;如果出现问题,这些数据还可能帮助你为他们提供支持。 但使用 ILogger 捕获的数据仅发送到订阅客户的资源。 只有在启用了 Application Insights 后,才能看到为自己的环境捕获的数据。

使用 ILogger

ILogger 是用于捕获日志信息的常见接口。 与 SDK 一起提供的用于.NET程序集的实现提供了常见方法,以支持建立范围和不同级别的日志记录。 目前没有设置来控制写入的日志级别。 使用 Application Insights 中的级别来筛选要查看的日志。

以下示例插件演示了如何使用 ILogger 和 ITracingService.Trace。

注释

确保包含 using Microsoft.Xrm.Sdk.PluginTelemetry;。 请勿使用 using Microsoft.Extensions.Logging;,否则实例 ILogger 为 null。

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.PluginTelemetry;
using System;
using System.Net.Http;

namespace ILoggerExample
{
    public class AccountPostOperation : IPlugin
    {
        private string webAddress;
        public AccountPostOperation(string config)
        {

            if (string.IsNullOrEmpty(config))
            {
                webAddress = "https://www.bing.com";
            }
            else
            {
                webAddress = config;
            }
        }


        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService =
               (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            ILogger logger = (ILogger)serviceProvider.GetService(typeof(ILogger));

            IPluginExecutionContext context = (IPluginExecutionContext)
               serviceProvider.GetService(typeof(IPluginExecutionContext));

            try
            {
                string startExecMsg = "Start execution of AccountPostOperation";
                logger.LogInformation(startExecMsg);
                tracingService.Trace(startExecMsg);

                Entity entity = (Entity)context.InputParameters["Target"];
                if (entity.LogicalName != "account")
                {

                    string wrongEntityMsg = "Plug-in registered for wrong entity {0}";
                    logger.LogWarning(wrongEntityMsg, entity.LogicalName);
                    tracingService.Trace(wrongEntityMsg, entity.LogicalName);
                    return;
                }

                string activityMsg = "Callback";

                using (logger.BeginScope(activityMsg))
                {
                    tracingService.Trace(activityMsg);

                    string startTaskMsg = "Start Task Creation";
                    logger.LogInformation(startTaskMsg);
                    tracingService.Trace(startTaskMsg);

                    Entity followup = new Entity("task");
                    followup["subject"] = "Send e-mail to the new customer.";
                    followup["description"] =
                        "Follow up with the customer. Check if there are any new issues that need resolution.";
                    followup["scheduledstart"] = DateTime.Now.AddDays(7);
                    followup["scheduledend"] = DateTime.Now.AddDays(7);
                    followup["category"] = context.PrimaryEntityName;

                    // Refer to the account in the task activity.
                    if (context.OutputParameters.Contains("id"))
                    {
                        Guid regardingobjectid = new Guid(context.OutputParameters["id"].ToString());
                        string regardingobjectidType = "account";

                        followup["regardingobjectid"] =
                        new EntityReference(regardingobjectidType, regardingobjectid);

                    }

                    // Obtain the IOrganizationService reference.
                    IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider
                    .GetService(typeof(IOrganizationServiceFactory));

                    IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
                    //Create the task
                    service.Create(followup);

                    string endTaskMsg = "Task creation completed";
                    logger.LogInformation(endTaskMsg);
                    tracingService.Trace(endTaskMsg);
                }

                string outBoundScope = "OutboundCall";

                using (logger.BeginScope(outBoundScope))
                {

                    string outboundStartMsg = "Outbound call started";
                    logger.LogInformation(outboundStartMsg);
                    tracingService.Trace(outboundStartMsg);

                    using (HttpClient client = new HttpClient())
                    {
                        client.Timeout = TimeSpan.FromMilliseconds(15000); //15 seconds
                        client.DefaultRequestHeaders.ConnectionClose = true; //Set KeepAlive to false

                        HttpResponseMessage response = client
                            .GetAsync(webAddress)
                            .GetAwaiter()
                            .GetResult(); //Make sure it is synchronous

                        response.EnsureSuccessStatusCode();

                        string responseText = response.Content
                            .ReadAsStringAsync()
                            .GetAwaiter()
                            .GetResult(); //Make sure it is synchronous

                        string shortResponseText = responseText.Substring(0, 20);

                        logger.LogInformation(shortResponseText);
                        tracingService.Trace(shortResponseText);

                        string outboundEndMsg = "Outbound call ended successfully";

                        logger.LogInformation(outboundEndMsg);
                        tracingService.Trace(outboundEndMsg);

                    }

                }

            }
            catch (Exception e)
            {
                string errMsg = "Plugin failed";
                logger.LogError(e, errMsg);
                tracingService.Trace($"{errMsg}:{e.Message}");
                throw new InvalidPluginExecutionException(e.Message, e);
            }
        }
    }
}

当您在 account 实体的 Create 的同步 PostOperation 步骤上注册此插件时,可在几分钟内使用 Application Insights 日志查看输出内容。 使用 Kusto 查询语言 (KQL) 查询结果。

使用表示响应标头中的请求 ID 的 operation_ParentId,按单个操作筛选项目。

使用 operation_ParentId 筛选单个操作对应的项。

相应的插件跟踪日志条目如下所示:

Start execution of AccountPostOperation
Callback
Start Task Creation
Task creation completed
Outbound call started
<!doctype html><html
Outbound call ended successfully 

Application Insights 中返回的行不显示使用 BeginScope 方法设置的信息。 此数据被设置在该作用域内添加的日志的 customDimensions 中。 使用此查询可显示作用域内的日志。

此查询将结果限制为在 Callback 范围内添加的日志。

该查询将结果限制为在回调作用域内添加的日志。

此查询将结果限制为在 OutboundCall 范围内添加的日志:

查询将结果限制为在 OutboundCall 范围内添加的日志。

记录异常

在上一个插件代码示例的底部,以下代码使用 LogError 记录捕获的异常并引发 InvalidPluginExecutionException

catch (Exception e)
{
    string errMsg = "Plugin failed";
    logger.LogError(e, errMsg);
    tracingService.Trace($"{errMsg}:{e.Message}");
    throw new InvalidPluginExecutionException(e.Message, e);
}

使用前面的插件代码,可以通过将无效值传递给步骤注册配置数据来引发异常。 在此示例中,值为 NOT_A_URL

在插件步骤注册中输入无效的配置值,导致错误。

此值将替代默认值 (https://www.bing.com),并导致出站调用失败。

客户可能发送的请求本身并没有任何问题:

POST [Organization URI]/api/data/v9.1/accounts HTTP/1.1
Prefer: odata.include-annotations="*"
Authorization: Bearer [REDACTED]
Content-Type: application/json

{
  "name":"Test account"
}

但由于插件步骤注册不正确,使用 Prefer: odata.include-annotations="*" 标头时,响应会返回以下包含所有详细信息的错误:

HTTP/1.1 400 Bad Request
Content-Type: application/json; odata.metadata=minimal
x-ms-service-request-id: 8fd35fd6-5329-4bd5-a1b7-757f91822322
REQ_ID: 8fd35fd6-5329-4bd5-a1b7-757f91822322
OData-Version: 4.0
Date: Sat, 24 Apr 2021 18:24:46 GMT

{
    "error": {
        "code": "0x80040265",
        "message": "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.",
        "@Microsoft.PowerApps.CDS.ErrorDetails.OperationStatus": "0",
        "@Microsoft.PowerApps.CDS.ErrorDetails.SubErrorCode": "-2146233088",
        "@Microsoft.PowerApps.CDS.HelpLink": "http://go.microsoft.com/fwlink/?LinkID=398563&error=Microsoft.Crm.CrmException%3a80040265&client=platform",
        "@Microsoft.PowerApps.CDS.TraceText": "\r\n[ILoggerExample: ILoggerExample.AccountPostOperation]\r\n[2ee952aa-90a4-eb11-b1ac-000d3a8f6891: ILoggerExample.AccountPostOperation: Create of account]\r\n\r\n\t\r\n\tStart execution of AccountPostOperation\r\n\tCallback\r\n\tStart Task Creation\r\n\tTask creation completed\r\n\tOutbound call started\r\n\tPlugin failed:An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.\r\n\t\r\n",
        "@Microsoft.PowerApps.CDS.InnerError.Message": "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set."
    }
}

插件跟踪日志包含此异常数据,其中包括 ExceptionDetails 数据。

Exception type: System.ServiceModel.FaultException`1[Microsoft.Xrm.Sdk.OrganizationServiceFault]
Message: An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.Detail: 
<OrganizationServiceFault xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/xrm/2011/Contracts">
  <ActivityId>09bf305c-8272-4fc4-801b-479280cb3069</ActivityId>
  <ErrorCode>-2147220891</ErrorCode>
  <ErrorDetails xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
    <KeyValuePairOfstringanyType>
      <d2p1:key>OperationStatus</d2p1:key>
      <d2p1:value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:int">0</d2p1:value>
    </KeyValuePairOfstringanyType>
    <KeyValuePairOfstringanyType>
      <d2p1:key>SubErrorCode</d2p1:key>
      <d2p1:value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:int">-2146233088</d2p1:value>
    </KeyValuePairOfstringanyType>
  </ErrorDetails>
  <HelpLink i:nil="true" />
  <Message>An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.</Message>
  <Timestamp>2021-04-24T18:24:46.4900727Z</Timestamp>
  <ExceptionRetriable>false</ExceptionRetriable>
  <ExceptionSource>PluginExecution</ExceptionSource>
  <InnerFault i:nil="true" />
  <OriginalException>PluginExecution</OriginalException>
  <TraceText>
Start execution of AccountPostOperation
Callback
Start Task Creation
Task creation completed
Outbound call started
Plugin failed:An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.
</TraceText>
</OrganizationServiceFault>

在 Application Insights 中,如果您查看作用域限定为此请求且作用域设置为 OutboundCall(如前所述)的跟踪,会发现唯一的条目是出站调用已启动。

查看作用域限定为此请求且作用域设置为 OutboundCall 的跟踪。

在 Application Insights 中,当你将查询切换为使用 exceptions 而不是 traces 时,会看到已记录了三个异常:

切换查询以使用异常而不是跟踪。

其中 cloud_RoleInstance 等于 SandboxRoleInstance 的那个,是因为 ILogger LogError method 才编写的。 另外两个表示在服务器上记录错误的不同位置。

注释

SandboxRoleInstance client_TypePC. 之所以如此,是因为该插件作为客户端在隔离沙盒中运行,而不是在服务器端运行。

你可以通过按 cloud_RoleInstance 进行筛选,重点查看由你的代码写入的错误日志:

通过按 cloud_RoleInstance 进行筛选,关注由您的代码写入的错误日志。

格式化后的消息文本会作为 customDimensions 的一部分被捕获。

另见

使用 Application Insights 分析模型驱动应用和 Microsoft Dataverse 遥测数据
插件
调试插件
查看跟踪日志
跟踪服务
PluginTraceLog 表