ILogger を使用してテレメトリを 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 で利用可能になるまでにある程度の遅延がいくつかあります。 このテレメトリはマイクロソフトによって収集されるため、有効にするためにコードを記述する必要はありません。

ILogger インターフェイスを使用してプラグインから取得されるテレメトリ データは、次の 2 つの点で異なります。

  • このテレメトリは Application Insights リソースに直接書き込まれ、Microsoft に送信されることはありません。
    • このデータを表示する際の待ち時間が少なくなります。
  • ILogger インターフェイスを使用するには、プラグイン コードを更新する必要があります。

ILogger を使用すると、真のテレメトリ データが提供され、ITracingService インターフェイス を使って書き込まれる既存のプラグイン トレース ログと協働するためのものです。 次のテーブルでは機能を比較しています。

基準 Application Insights の ILogger プラグイン トレース ログの ITracingService トレース
使用目的 分析とデバッグのために、時間経過に沿ってテレメトリをキャプチャします。 プラグインの開発およびデバッグ中
データが保存される期間 既定は 90 日である Application Insights データ保有期間による 24 時間
対応可能 Application Insights 統合に登録した組織のみ。 プラグイン トレースが有効になっている場合の組織に対して利用できます。
データ量 各ログ メッセージは文字列値を渡すことができます。 プラグインの実行ごとに書き込むことができるテキストは 10kb のみです。 これ以上は切り捨てられます。
ランタイム エラーで利用可能 いいえ モデル駆動型アプリ クライアント エラーで、および 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 は、ログ情報を取得するための一般的なインターフェイスです。 .NET 用 SDK アセンブリで提供される実装は、スコープの確立とさまざまなレベルのロギングをサポートするための一般的なメソッドを提供します。 現在、書き込まれるログのレベルを制御する設定はありません。 レベルは 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エンティティの CreatePostOperation ステップに登録されている場合、Application Insights ログを使って数分以内に出力を表示できます。 Kusto クエリ言語 (KQL) を使って結果をクエリできます。

応答ヘッダーの要求 ID を表す operation_ParentId を使って、1 回の操作でアイテムをフィルタリングできます。

operation_ParentId を使用して、単一操作のアイテムをフィルタリングします。

対応するプラグイン トレース ログ エントリは次のようになります。

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

BeginScope メソッド で設定された情報は、Application Insights で返される行には表示されません。 このデータは、そのスコープ内に追加されたログの customDimensions 内に設定されます。 このクエリを使用して、スコープ内のログを表示できます。

このクエリでは、結果が Callback 範囲中に追加されたログに制限されます

クエリでは、結果が 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 内で、クエリで traces ではなく exceptions を使用するように切り替えると、3 つの例外がログに記録されます。

トレースではなく例外を使用するようにクエリを切り替えます。

cloud_RoleInstanceSandboxRoleInstance が等しい場合、ILoggerLogError メソッド が原因で書かれたものです。 他の 2 つは、エラーがサーバーに記録された異なる場所を表しています。

注意

SandboxRoleInstance client_TypePC です。 これは、プラグインがサーバーではなくクライアントとして分離されたサンドボックスで実行されるためです。

cloud_RoleInstance でフィルタリングすることにより、コードによって書き込まれたエラー ログに焦点を当てることができます。

cloud_RoleInstance でフィルタリングすることにより、コードによって書き込まれたエラー ログに焦点を当てることができます。

フォーマットされたメッセージ テキストは、customDimensions の一部としてキャプチャされます。

関連項目

Application Insights でモデル駆動型アプリと Microsoft Dataverse テレメトリを分析する
プラグイン
プラグインのデバッグ
トレース ログの表示
サービスのトレース
PluginTraceLog テーブル

注意

ドキュメントの言語設定についてお聞かせください。 簡単な調査を行います。 (この調査は英語です)

この調査には約 7 分かかります。 個人データは収集されません (プライバシー ステートメント)。