分散トレースを収集する

この記事の対象: ✔️ .NET Core 2.1 以降のバージョン ✔️ .NET Framework 4.5 以降のバージョン

インストルメント化されたコードは、分散トレースの一部として Activity オブジェクトを作成できますが、後でトレース全体を確認できるように、これらのオブジェクトの情報を集中ストレージに収集する必要があります。 このチュートリアルでは、必要に応じてアプリケーションの問題を診断できるように、分散トレースのテレメトリをさまざまな方法で収集します。 新しいインストルメンテーションを追加する必要がある場合は、インストルメンテーションのチュートリアルを参照してください。

OpenTelemetry を使用してトレースを収集する

OpenTelemetry は、クラウドネイティブ ソフトウェアのテレメトリの生成と収集を標準化することを目的とした、Cloud Native Computing Foundation によりサポートされるベンダーに依存しないオープン ソース プロジェクトです。 これらの例では、分散トレース情報を収集してコンソールに表示します。 他の場所に情報を送信するように OpenTelemetry を構成する方法については、OpenTelemetry の概要に関するガイドを参照してください。

ASP.NET の例

前提条件

サンプル アプリケーションを作成する

まず、デモ アプリケーションとして使用する新しい ASP.NET Web アプリを作成します。

dotnet new webapp

このアプリでは Web ページを表示しますが、Web ページを参照しても、この時点では分散トレース情報は収集されません。

コレクションの構成

OpenTelemetry を使用するには、いくつかの NuGet パッケージへの参照を追加する必要があります。

dotnet add package OpenTelemetry --version 1.4.0-rc1
dotnet add package OpenTelemetry.Exporter.Console --version 1.4.0-rc1
dotnet add package OpenTelemetry.Extensions.Hosting --version 1.4.0-rc1
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --version 1.0.0-rc9.10

注意

執筆時点では、1.4.0 リリース候補 1 ビルドが OpenTelemetry の使用可能な最新バージョンでした。 最終的なバージョンが使用可能になったら、代わりにそれを使用します。

次に、Program.cs のソース コードを次のように変更します。

using OpenTelemetry;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddOpenTelemetry()
    .WithTracing(builder =>
    {
        builder.AddAspNetCoreInstrumentation();
        builder.AddConsoleExporter();
    }).StartWithHost();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

アプリを実行し、Web ブラウザーを使用して、ホストされている Web ページを参照します。 OpenTelemetry 分散トレースが有効になったため、コンソールに出力されたブラウザーの Web 要求に関する情報が表示されます。

Activity.TraceId:            9c4519ce65a667280daedb3808d376f0
Activity.SpanId:             727c6a8a6cff664f
Activity.TraceFlags:         Recorded
Activity.ActivitySourceName: Microsoft.AspNetCore
Activity.DisplayName:        /
Activity.Kind:               Server
Activity.StartTime:          2023-01-08T01:56:05.4529879Z
Activity.Duration:           00:00:00.1048255
Activity.Tags:
    net.host.name: localhost
    net.host.port: 5163
    http.method: GET
    http.scheme: http
    http.target: /
    http.url: http://localhost:5163/
    http.flavor: 1.1
    http.user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.76
    http.status_code: 200
Resource associated with Activity:
    service.name: unknown_service:demo

OpenTelemetry 構成はすべて、builder.Services.AddOpenTelemetry() で始まる新しいソース行で行われます。 .WithTracing(...) を使用して、分散トレースを有効にしました。 また、ASP.NET Core Web サーバーによって生成されたすべての分散トレース アクティビティを収集するために、AddAspNetCoreInstrumentation() で OpenTelemetry を有効にしました。そして、AddConsoleExporter() でその情報をコンソールへ送信するように OpenTelemetry に指示します。 あまり単純ではないアプリの場合は、データベース クエリや送信 HTTP 要求のトレースも収集するために、さらに多くのインストルメンテーション ライブラリを追加できます。 コンソール エクスポーターを、Jaeger、Zipken や使用することを選択した別の監視サービスのエクスポーターと置換します。

コンソール アプリの例

前提条件

サンプル アプリケーションを作成する

分散トレースのテレメトリを収集するには、事前にそれを生成する必要があります。 多くの場合、このインストルメンテーションはライブラリに含まれていますが、わかりやすくするために、StartActivity を使用して、インストルメンテーションの例を含む小さなアプリを作成します。 この時点では、収集が行われていません。StartActivity() による影響はなく、null 値が返されます。 詳細については、インストルメンテーションのチュートリアルを参照してください。

dotnet new console

.NET 5 以降をターゲットとするアプリケーションには、必要な分散トレース API が既に含まれています。 以前のバージョンの .NET をターゲットとするアプリの場合は、System.Diagnostics.DiagnosticSource NuGet パッケージ バージョン 5 以降を追加します。

dotnet add package System.Diagnostics.DiagnosticSource

生成された Program.cs の内容を次のソース例に置き換えます。

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        static ActivitySource s_source = new ActivitySource("Sample.DistributedTracing");

        static async Task Main(string[] args)
        {
            await DoSomeWork();
            Console.WriteLine("Example work done");
        }

        static async Task DoSomeWork()
        {
            using (Activity a = s_source.StartActivity("SomeWork"))
            {
                await StepOne();
                await StepTwo();
            }
        }

        static async Task StepOne()
        {
            using (Activity a = s_source.StartActivity("StepOne"))
            {
                await Task.Delay(500);
            }
        }

        static async Task StepTwo()
        {
            using (Activity a = s_source.StartActivity("StepTwo"))
            {
                await Task.Delay(1000);
            }
        }
    }
}

アプリを実行しても、まだトレース データは収集されません。

> dotnet run
Example work done

コレクションの構成

OpenTelemetry.Exporter.Console NuGet パッケージを追加します。

dotnet add package OpenTelemetry.Exporter.Console

OpenTelemetry の追加の using ディレクティブを使用して、Program.cs を更新します。

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

Main() を更新して、OpenTelemetry の TracerProvider を作成します。

        public static async Task Main()
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                .AddSource("Sample.DistributedTracing")
                .AddConsoleExporter()
                .Build();

            await DoSomeWork();
            Console.WriteLine("Example work done");
        }

これで、アプリは分散トレース情報を収集して、コンソールに表示します。

> dotnet run
Activity.Id:          00-7759221f2c5599489d455b84fa0f90f4-6081a9b8041cd840-01
Activity.ParentId:    00-7759221f2c5599489d455b84fa0f90f4-9a52f72c08a9d447-01
Activity.DisplayName: StepOne
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:46:46.8649754Z
Activity.Duration:    00:00:00.5069226
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 909a4624-3b2e-40e4-a86b-4a2c8003219e

Activity.Id:          00-7759221f2c5599489d455b84fa0f90f4-d2b283db91cf774c-01
Activity.ParentId:    00-7759221f2c5599489d455b84fa0f90f4-9a52f72c08a9d447-01
Activity.DisplayName: StepTwo
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:46:47.3838737Z
Activity.Duration:    00:00:01.0142278
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 909a4624-3b2e-40e4-a86b-4a2c8003219e

Activity.Id:          00-7759221f2c5599489d455b84fa0f90f4-9a52f72c08a9d447-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:46:46.8634510Z
Activity.Duration:    00:00:01.5402045
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 909a4624-3b2e-40e4-a86b-4a2c8003219e

Example work done
変換元

次のコード例では、コード内に既に存在する ActivitySource によって生成されたアクティビティが OpenTelemetry により取り込まれるように、AddSource("Sample.DistributedTracing") を呼び出しています。

static ActivitySource s_source = new ActivitySource("Sample.DistributedTracing");

任意の ActivitySource からのテレメトリは、ソースの名前を指定して AddSource() を呼び出すことによって取り込むことができます。

エクスポーター

コンソール エクスポーターは、簡単な例やローカル開発に役立ちますが、運用環境の展開では、集中管理されたストアにトレースを送信することをお勧めします。 OpenTelemetry では、異なるエクスポーターを使用したさまざまな送信先がサポートされています。 OpenTelemetry の構成の詳細については、OpenTelemetry の概要ガイドを参照してください。

Application Insights を使用してトレースを収集する

分散トレースのテレメトリは、ASP.NET または ASP.NET Core アプリ用に Application Insights SDK を構成した後、あるいはコードのないインストルメンテーションを有効にすることで自動的に取り込まれます。

詳細については、Application Insights の分散トレースのドキュメントを参照してください。

注意

現在、Application Insights では、特定の既知のアクティビティ インストルメンテーションの収集のみがサポートされており、ユーザーが追加した新しいアクティビティは無視されます。 Application Insights には、カスタム分散トレース情報を追加するためのベンダー固有の API として TrackDependency が用意されています。

カスタム ロジックを使用してトレースを収集する

開発者は、アクティビティ トレース データ用にカスタマイズされた独自の収集ロジックを自由に作成できます。 この例では、.NET によって提供される System.Diagnostics.ActivityListener API を使用してテレメトリを収集し、コンソールに出力しています。

前提条件

サンプル アプリケーションを作成する

まず、いくつかの分散トレース インストルメンテーションを含むサンプル アプリケーションを作成しますが、トレース データは収集されません。

dotnet new console

.NET 5 以降をターゲットとするアプリケーションには、必要な分散トレース API が既に含まれています。 以前のバージョンの .NET をターゲットとするアプリの場合は、System.Diagnostics.DiagnosticSource NuGet パッケージ バージョン 5 以降を追加します。

dotnet add package System.Diagnostics.DiagnosticSource

生成された Program.cs の内容を次のソース例に置き換えます。

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        static ActivitySource s_source = new ActivitySource("Sample.DistributedTracing");

        static async Task Main(string[] args)
        {
            await DoSomeWork();
            Console.WriteLine("Example work done");
        }

        static async Task DoSomeWork()
        {
            using (Activity a = s_source.StartActivity("SomeWork"))
            {
                await StepOne();
                await StepTwo();
            }
        }

        static async Task StepOne()
        {
            using (Activity a = s_source.StartActivity("StepOne"))
            {
                await Task.Delay(500);
            }
        }

        static async Task StepTwo()
        {
            using (Activity a = s_source.StartActivity("StepTwo"))
            {
                await Task.Delay(1000);
            }
        }
    }
}

アプリを実行しても、まだトレース データは収集されません。

> dotnet run
Example work done

トレースを収集するためのコードを追加する

次のコードで Main() を更新します。

        static async Task Main(string[] args)
        {
            Activity.DefaultIdFormat = ActivityIdFormat.W3C;
            Activity.ForceDefaultIdFormat = true;

            Console.WriteLine("         {0,-15} {1,-60} {2,-15}", "OperationName", "Id", "Duration");
            ActivitySource.AddActivityListener(new ActivityListener()
            {
                ShouldListenTo = (source) => true,
                Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllDataAndRecorded,
                ActivityStarted = activity => Console.WriteLine("Started: {0,-15} {1,-60}", activity.OperationName, activity.Id),
                ActivityStopped = activity => Console.WriteLine("Stopped: {0,-15} {1,-60} {2,-15}", activity.OperationName, activity.Id, activity.Duration)
            });

            await DoSomeWork();
            Console.WriteLine("Example work done");
        }

出力にログが含まれるようになりました。

> dotnet run
         OperationName   Id                                                           Duration
Started: SomeWork        00-bdb5faffc2fc1548b6ba49a31c4a0ae0-c447fb302059784f-01
Started: StepOne         00-bdb5faffc2fc1548b6ba49a31c4a0ae0-a7c77a4e9a02dc4a-01
Stopped: StepOne         00-bdb5faffc2fc1548b6ba49a31c4a0ae0-a7c77a4e9a02dc4a-01      00:00:00.5093849
Started: StepTwo         00-bdb5faffc2fc1548b6ba49a31c4a0ae0-9210ad536cae9e4e-01
Stopped: StepTwo         00-bdb5faffc2fc1548b6ba49a31c4a0ae0-9210ad536cae9e4e-01      00:00:01.0111847
Stopped: SomeWork        00-bdb5faffc2fc1548b6ba49a31c4a0ae0-c447fb302059784f-01      00:00:01.5236391
Example work done

DefaultIdFormatForceDefaultIdFormat の設定は省略可能ですが、これは .NET ランタイム バージョンが違ってもサンプルで確実に同様の出力が生成されるようにするのに役立ちます。 .NET 5 では、既定で W3C TraceContext ID 形式が使用されますが、以前のバージョンの .NET では既定で Hierarchical ID 形式が使用されます。 詳細については、「活動 ID」を参照してください。

System.Diagnostics.ActivityListener は、アクティビティの有効期間中にコールバックを受信するために使用されます。

  • ShouldListenTo - 各アクティビティは、その名前空間およびプロデューサーとして機能する ActivitySource に関連付けられています。 このコールバックは、プロセス内の各 ActivitySource に対して 1 回ずつ呼び出されます。 このソースによって生成されたアクティビティの開始/停止イベントについて、サンプリングを実行したり通知を受けたりする場合は true を返します。
  • Sample - いずれかの ActivityListener でサンプリングが必要であると示されていない限り、既定で StartActivity はアクティビティ オブジェクトを作成しません。 AllDataAndRecorded が返される場合、アクティビティを作成する必要があり、IsAllDataRequested を true に設定する必要があり、ActivityTraceFlagsRecorded フラグが設定されることを示します。 インストルメント化されたコードによって IsAllDataRequested を監視すると、リスナーでアクティビティの補助情報 (タグやイベントなど) を確実に設定する必要があるというヒントになります。 記録されたフラグは W3C TraceContext ID でエンコードされ、分散トレースに関連する他のプロセスに対してこのトレースをサンプリングする必要があることを示唆します。
  • アクティビティが開始され、停止されると、それぞれ ActivityStartedActivityStopped が呼び出されます。 これらのコールバックによって、アクティビティについての関連情報を記録したり、場合によっては変更したりできます。 アクティビティが開始されたばかりのときは、データの多くがまだ不完全である場合があり、アクティビティが停止する前に設定されます。

ActivityListener が作成され、コールバックが設定されたら、ActivitySource.AddActivityListener(ActivityListener) を呼び出すと、コールバックの呼び出しが開始されます。 ActivityListener.Dispose() を呼び出すと、コールバックのフローが停止します。 マルチスレッドのコードでは、進行中のコールバック通知が Dispose() の実行中や、場合によってはそれが返された直後に受信される可能性があることにご注意ください。