Application Insights .NET SDK でカスタム操作を追跡する

Application Insights SDK では、受信 HTTP 要求と、依存関係にあるサービスへの呼び出し (HTTP 要求や SQL クエリなど) を自動的に追跡します。 要求と依存関係の追跡と関連付けによってアプリケーションを構成しているすべてのマイクロサービスの応答性と信頼性を確認することで、アプリケーション全体の状態を把握することができます。

ただし、一律に対応することのできない類のアプリケーション パターンも存在します。 そうしたパターンを適切に監視するためには、手動によるコードのインストルメンテーションが必要となります。 この記事では、手動のインストルメンテーションを必要とする可能性があるいくつかのパターン (カスタムのキュー処理や実行時間の長いバックグラウンド タスクの実行など) を取り上げます。

この記事では、Application Insights SDK を使用してカスタム操作を追跡する方法についてのガイドラインを示します。 このドキュメントの対象を以下に示します。

  • Application Insights for .NET (別名 Base SDK) バージョン 2.4 以降。
  • Application Insights for Web Applications (ASP.NET 実行) バージョン 2.4 以降。
  • Application Insights for ASP.NET Core バージョン 2.1 以降。

Note

以下のドキュメントは、Application Insights クラシック API に関するものです。 Application Insights の長期的な計画は、OpenTelemetry を使用してデータを収集することです。 詳細については、「.NET、Node.js、Python、Java アプリケーション用の Azure Monitor OpenTelemetry を有効にする」を参照してください。

概要

操作は、アプリケーションによって実行される処理の 1 つの論理部分です。 操作には、名前、開始時刻、継続時間、および実行コンテキストがあります (ユーザー名、プロパティ、結果など)。 操作 A が 操作 B によって開始された場合は、操作 B が A の親として設定されます。1 つの操作は、親を 1 つだけ持つことができますが、複数の子操作を持つことができます。 操作とテレメトリの関連付けの詳細については、「Application Insights におけるテレメトリの相関付け」を参照してください。

Application Insights .NET SDK では、操作は、抽象クラス OperationTelemetry とその子孫である RequestTelemetryDependencyTelemetry によって表されます。

受信操作の追跡

Application Insights Web SDK は、IIS パイプラインで実行される ASP.NET アプリケーションとすべての ASP.NET Core アプリケーションを対象に、HTTP 要求を自動的に収集します。 それ以外のプラットフォームとフレームワークについては、コミュニティによってサポートされるソリューションが存在します。 標準のソリューションやコミュニティでサポートされているソリューションでサポートされていないアプリケーションについては、手動でインストルメント化できます。

キューからアイテムを受け取る worker も、独自の追跡が必要なケースです。 キューによっては、そのキューにメッセージを追加する呼び出しが、依存関係として追跡されます。 メッセージの処理を表す上位の操作については、自動的には収集されません。

そのような操作がどのように追跡されるかを見てみましょう。

簡潔に言うと、このタスクとは、RequestTelemetry を作成して既知のプロパティを設定することです。 操作が完了したら、このテレメトリを追跡します。 次の例は、このタスクを示しています。

Owin 自己ホスト型アプリにおける HTTP 要求

この例では、トレース コンテキストは、関連付け用の HTTP プロトコルに従って反映されます。 ここに記載されているヘッダーが受信されることを予期してください。

public class ApplicationInsightsMiddleware : OwinMiddleware
{
    // You may create a new TelemetryConfiguration instance, reuse one you already have,
    // or fetch the instance created by Application Insights SDK.
    private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
    private readonly TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
    
    public ApplicationInsightsMiddleware(OwinMiddleware next) : base(next) {}

    public override async Task Invoke(IOwinContext context)
    {
        // Let's create and start RequestTelemetry.
        var requestTelemetry = new RequestTelemetry
        {
            Name = $"{context.Request.Method} {context.Request.Uri.GetLeftPart(UriPartial.Path)}"
        };

        // If there is a Request-Id received from the upstream service, set the telemetry context accordingly.
        if (context.Request.Headers.ContainsKey("Request-Id"))
        {
            var requestId = context.Request.Headers.Get("Request-Id");
            // Get the operation ID from the Request-Id (if you follow the HTTP Protocol for Correlation).
            requestTelemetry.Context.Operation.Id = GetOperationId(requestId);
            requestTelemetry.Context.Operation.ParentId = requestId;
        }

        // StartOperation is a helper method that allows correlation of 
        // current operations with nested operations/telemetry
        // and initializes start time and duration on telemetry items.
        var operation = telemetryClient.StartOperation(requestTelemetry);

        // Process the request.
        try
        {
            await Next.Invoke(context);
        }
        catch (Exception e)
        {
            requestTelemetry.Success = false;
            requestTelemetry.ResponseCode;
            telemetryClient.TrackException(e);
            throw;
        }
        finally
        {
            // Update status code and success as appropriate.
            if (context.Response != null)
            {
                requestTelemetry.ResponseCode = context.Response.StatusCode.ToString();
                requestTelemetry.Success = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299;
            }
            else
            {
                requestTelemetry.Success = false;
            }

            // Now it's time to stop the operation (and track telemetry).
            telemetryClient.StopOperation(operation);
        }
    }
    
    public static string GetOperationId(string id)
    {
        // Returns the root ID from the '|' to the first '.' if any.
        int rootEnd = id.IndexOf('.');
        if (rootEnd < 0)
            rootEnd = id.Length;

        int rootStart = id[0] == '|' ? 1 : 0;
        return id.Substring(rootStart, rootEnd - rootStart);
    }
}

この関連付け用の HTTP プロトコルも、Correlation-Context ヘッダーを宣言しますが、 ここでは、簡潔にするために省略されています。

キューのインストルメンテーション

W3C トレース コンテキスト関連付け用の HTTP プロトコルは、関連付けの詳細を HTTP 要求で渡しますが、すべてのキュー プロトコルでは、同じ詳細がキュー メッセージでどのように渡されるかを定義する必要があります。 AMQP などの一部のキュー プロトコルでは、より多くのメタデータを渡すことができます。 Azure Storage Queue などの他のプロトコルでは、コンテキストをメッセージ ペイロードにエンコードする必要があります。

Note

コンポーネント間のトレースは、キューではまだサポートされていません。

HTTP を使用すると、プロデューサーとコンシューマーが、異なる Application Insights リソースにテレメトリを送信する場合、トランザクションの診断エクスペリエンスとアプリケーション マップでトランザクションが表示され、エンドツーエンドでマップされます。 キューの場合、この機能はまだサポートされていません。

Service Bus キュー

トレース情報については、「Azure Service Bus メッセージングを介した分散トレースと相関付け」を参照してください。

Azure Storage キュー

Azure Storage キューの操作を追跡し、プロデューサー、コンシューマー、Azure Storage 間でテレメトリを相互に関連付ける例を次に示します。

Storage キューには HTTP API があります。 キューに対するすべての呼び出しは、Application Insights の HTTP 要求の依存関係コレクターによって追跡されます。 既定では、ASP.NET と ASP.NET Core アプリケーションで構成されます。 他の種類のアプリケーションについては、コンソール アプリケーションに関するドキュメントを参照してください。

Application Insights の操作 ID を Storage の要求 ID に関連付けることもできます。 Storage の要求クライアントとサーバーの要求 ID の設定および取得方法については、「Azure Storage の監視、診断、およびトラブルシューティング」を参照してください。

Enqueue

Storage キューは HTTP API をサポートしているため、キューを使ったすべての操作は自動的に ApplicationInsights によって追跡されます。 多くのケースには、このインストルメンテーションで対応することができます。 コンシューマー側のトレースとプロデューサー側のトレースを相互に関連付けるには、関連付け用の HTTP プロトコルでの実行方法に似た関連付けコンテキストを渡す必要があります。

この例は、Enqueue 操作を追跡する方法を示しています。 次のようにすることができます。

  • 再試行を関連付ける (存在する場合) :すべての再試行には、Enqueue 操作という共通の親が 1 つ存在します。 また、受信要求の子として追跡されます。 キューに対する論理要求が複数存在する場合、どの呼び出しが再試行されたかを見極めることは難しいことがあります。
  • Storage ログを関連付ける (存在する場合に必要に応じて) :Storage ログは Application Insights のテレメトリに関連付けられます。

Enqueue 操作は、親の操作の子です。 たとえば、受信 HTTP 要求です。 HTTP の依存関係呼び出しは、Enqueue 操作の子であり、受信要求の孫でもあります。

public async Task Enqueue(CloudQueue queue, string message)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("enqueue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Enqueue " + queue.Name;

    // MessagePayload represents your custom message and also serializes correlation identifiers into payload.
    // For example, if you choose to pass payload serialized to JSON, it might look like
    // {'RootId' : 'some-id', 'ParentId' : '|some-id.1.2.3.', 'message' : 'your message to process'}
    var jsonPayload = JsonConvert.SerializeObject(new MessagePayload
    {
        RootId = operation.Telemetry.Context.Operation.Id,
        ParentId = operation.Telemetry.Id,
        Payload = message
    });
    
    CloudQueueMessage queueMessage = new CloudQueueMessage(jsonPayload);

    // Add operation.Telemetry.Id to the OperationContext to correlate Storage logs and Application Insights telemetry.
    OperationContext context = new OperationContext { ClientRequestID = operation.Telemetry.Id};

    try
    {
        await queue.AddMessageAsync(queueMessage, null, null, new QueueRequestOptions(), context);
    }
    catch (StorageException e)
    {
        operation.Telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.Telemetry.Success = false;
        operation.Telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}  

アプリケーションから報告されるテレメトリの量を減らすため、またはその他の理由で Enqueue 操作を追跡しない場合は、Activity API を直接使用します。

  • Application Insights 操作を開始する代わりに、新しい Activity を作成 (および開始) します。 操作名以外のプロパティを割り当てる必要はありません
  • operation.Telemetry.Id ではなく yourActivity.Id をメッセージ ペイロードに対してシリアル化します。 Activity.Current.Id を使用することもできます。

Dequeue

Enqueue と同様、Storage キューに対する実際の HTTP 要求は Application Insights によって自動的に追跡されます。 Enqueue 操作の発生元は親のコンテキスト (受信要求のコンテキストなど) であると推測できます。 Application Insights SDK では、このような操作やその HTTP 部分を、親要求や同じスコープ内で報告される他のテレメトリと自動的に関連付けます。

Dequeue 操作は注意が必要です。 Application Insights SDK は、自動的に HTTP 要求を追跡します。 ただし、メッセージが解析されるまでは、関連付けのコンテキストは不明です。 特に複数のメッセージが受信される場合、メッセージを取得するための HTTP 要求を他のテレメトリに関連付けることはできません。

public async Task<MessagePayload> Dequeue(CloudQueue queue)
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("dequeue " + queue.Name);
    operation.Telemetry.Type = "Azure queue";
    operation.Telemetry.Data = "Dequeue " + queue.Name;
    
    try
    {
        var message = await queue.GetMessageAsync();
    }
    catch (StorageException e)
    {
        operation.telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
        operation.telemetry.Success = false;
        operation.telemetry.ResultCode = e.RequestInformation.HttpStatusCode.ToString();
        telemetryClient.TrackException(e);
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }

    return null;
}

Process

次の例では、受信メッセージは受信 HTTP 要求と同様の方法で追跡されます。

public async Task Process(MessagePayload message)
{
    // After the message is dequeued from the queue, create RequestTelemetry to track its processing.
    RequestTelemetry requestTelemetry = new RequestTelemetry { Name = "process " + queueName };
    
    // It might also make sense to get the name from the message.
    requestTelemetry.Context.Operation.Id = message.RootId;
    requestTelemetry.Context.Operation.ParentId = message.ParentId;

    var operation = telemetryClient.StartOperation(requestTelemetry);

    try
    {
        await ProcessMessage();
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        throw;
    }
    finally
    {
        // Update status code and success as appropriate.
        telemetryClient.StopOperation(operation);
    }
}

他のキュー操作も、同じようにインストルメント化できます。 Peek 操作をインストルメント化する場合は、デキューと同様の方法を使用する必要があります。 キューの管理操作をインストルメント化する必要はありません。 HTTP などの操作は Application Insights によって追跡されます。ほとんどの場合はそれで十分です。

メッセージの削除をインストルメント化するときは、必ず操作 (関連付け) ID を設定してください。 別の方法として、Activity API を使用することもできます。 その場合は、テレメトリ項目に対する操作 ID を自分で設定する必要はありません。それは Application Insights SDK によって自動的に行われます。

  • キューから項目を取得した後、新しい Activity を作成します。
  • Activity.SetParentId(message.ParentId) を使用して、コンシューマーとプロデューサーのログを相互に関連付けます。
  • Activity を開始します。
  • Start/StopOperation ヘルパーを使用して、Dequeue、Process、Delete の各操作を追跡します。 追跡は、同じ非同期制御フロー (実行コンテキスト) から行ってください。 そうすることで、相互の関連付けが適切に行われます。
  • Activity を停止します。
  • Start/StopOperation を使用するか、Track テレメトリを手動で呼び出します。

依存関係の種類

Application Insights では、依存関係の種類を使用して UI エクスペリエンスがカスタマイズされます。 キューの場合、トランザクションの診断エクスペリエンスを向上させる次の種類の DependencyTelemetry が認識されます。

  • Azure Storage キューの Azure queue
  • Azure Event Hubs のAzure Event Hubs
  • Azure Service Bus の Azure Service Bus

バッチ処理

一部のキューでは、1 つの要求で複数のメッセージをデキューできます。 このようなメッセージの処理には依存関係はなく、異なる論理操作に属していると推測されます。 Dequeue 操作を処理対象の特定のメッセージに関連付けることはできません。

各メッセージは、独自の非同期制御フローの中で処理する必要があります。 詳細については、「出力方向の依存関係の追跡」セクションを参照してください。

長時間実行されるバックグラウンド タスク

アプリケーションの中には、ユーザーの要求によって発生する、長時間実行される操作を開始するものがあります。 トレース/インストルメンテーションの観点から見ると、これは要求や依存関係のインストルメンテーションと変わりません。

async Task BackgroundTask()
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>(taskName);
    operation.Telemetry.Type = "Background";
    try
    {
        int progress = 0;
        while (progress < 100)
        {
            // Process the task.
            telemetryClient.TrackTrace($"done {progress++}%");
        }
        // Update status code and success as appropriate.
    }
    catch (Exception e)
    {
        telemetryClient.TrackException(e);
        // Update status code and success as appropriate.
        throw;
    }
    finally
    {
        telemetryClient.StopOperation(operation);
    }
}

この例では、telemetryClient.StartOperationDependencyTelemetry を作成し、関連付けのコンテキストを設定しています。 操作のスケジュールが設定された受信要求によって作成された親操作があるとします。 BackgroundTask が受信要求と同じ非同期制御フローで開始されていれば、受信要求はその親操作と関連付けられます。 BackgroundTask と、入れ子になっているすべてのテレメトリ項目は、(要求の終了後も) それを発生させた要求と自動的に関連付けられます。

いずれの操作 (Activity) も関連付けられていないバックグラウンド スレッドからタスクが開始された場合、BackgroundTask には親が存在しません。 ただし、入れ子になった操作が存在する可能性があります。 そのタスクから報告されるすべてのテレメトリ項目は、BackgroundTask で作成された DependencyTelemetry に関連付けられます。

出力方向の依存関係の追跡

Application Insights がサポートしていない独自の依存関係や操作を追跡することができます。

このようなカスタム追跡の例として、Service Bus キューまたは Azure Storage キューの Enqueue メソッドを挙げることができます。

カスタム依存関係の追跡に使用される一般的な手法は次のとおりです。

  • 関連付けに必要な DependencyTelemetry プロパティと、start、time stamp、duration などのその他のプロパティを設定するTelemetryClient.StartOperation (拡張機能) メソッドを呼び出します。
  • DependencyTelemetry のその他のカスタム プロパティ (名前やその他の必要なプロパティ) を設定します。
  • 依存関係呼び出しを行い、応答が返るまで待機します。
  • 操作が完了したら、StopOperation を使用して操作を停止します。
  • 例外を処理します。
public async Task RunMyTaskAsync()
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>("task 1"))
    {
        try 
        {
            var myTask = await StartMyTaskAsync();
            // Update status code and success as appropriate.
        }
        catch(...) 
        {
            // Update status code and success as appropriate.
        }
    }
}

操作を破棄すると操作が停止されるため、StopOperation を呼び出す代わりに実行することもできます。

警告

場合によっては、未処理の例外によって呼び出しが妨げられるfinally可能性があるため、操作が追跡されないことがあります。

並列操作の処理と追跡

StopOperation の呼び出しは、開始された操作を停止するだけです。 現在実行中の操作が停止する操作と一致しない場合、StopOperation は何も行いません。 この状況は、同じ実行コンテキストで複数の操作を並行して開始した場合に起こることがあります。

var firstOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 1");
var firstTask = RunMyTaskAsync();

var secondOperation = telemetryClient.StartOperation<DependencyTelemetry>("task 2");
var secondTask = RunMyTaskAsync();

await firstTask;

// FAILURE!!! This will do nothing and will not report telemetry for the first operation
// as currently secondOperation is active.
telemetryClient.StopOperation(firstOperation); 

await secondTask;

並行して実行されている操作を分離するには、常に同じ async メソッドで StartOperation を呼び出して操作を処理する必要があります。 操作が同期の場合 (つまり非同期ではない場合) は、プロセスをラップし、Task.Run を使用して追跡します。

public void RunMyTask(string name)
{
    using (var operation = telemetryClient.StartOperation<DependencyTelemetry>(name))
    {
        Process();
        // Update status code and success as appropriate.
    }
}

public async Task RunAllTasks()
{
    var task1 = Task.Run(() => RunMyTask("task 1"));
    var task2 = Task.Run(() => RunMyTask("task 2"));
    
    await Task.WhenAll(task1, task2);
}

ApplicationInsights 操作と System.Diagnostics.Activity

System.Diagnostics.Activity は、分散トレース コンテキストを表し、プロセスの内部および外部でコンテキストを作成および伝達し、テレメトリ項目を関連付けるために、フレームワークおよびライブラリで使用されます。 Activity は、フレームワーク/ライブラリ間の通知メカニズムとして System.Diagnostics.DiagnosticSource と連携し、受信や送信要求、例外などの重要なイベントについて通知します。

アクティビティは、Application Insights の第一級オブジェクトです。 依存関係と要求の自動収集は、DiagnosticSource イベントと共にそれらに大きく依存しています。 アプリケーションで Activity を作成する場合、Application Insights テレメトリが作成される結果にはなりません。 Application Insights は、DiagnosticSource イベントを受信してイベント名とペイロードを認識し、Activity をテレメトリに変換する必要があります。

Application Insights 操作 (要求または依存関係) には、それぞれ Activity が含まれます。 StartOperation が呼び出されると、その下に Activity が作成されます。 StartOperation は、要求または依存関係テレメトリを追跡し、すべてが関連付けられていることを確認する推奨の方法です。

次のステップ