收集分布式跟踪

本文适用范围:✔️ .NET Core 2.1 及更高版本 ✔️ .NET Framework 4.5 及更高版本

检测代码可以创建 Activity 对象作为分布式跟踪的一部分,但需要将这些对象中的信息收集到集中存储中,以便稍后可以查看整个跟踪。 本教程将以不同的方式收集分布式跟踪遥测,以便在需要时可用于诊断应用程序问题。 如果需要添加新的检测,请参阅检测教程

使用 OpenTelemetry 收集跟踪

OpenTelemetry 是由云本机计算基础支持的与供应商无关的开源项目,旨在标准化为云原生软件生成和收集遥测的过程。 在这些示例中,你将在主机上收集并显示分布式跟踪信息。 若要了解如何配置 OpenTelemetry 以将信息发送到其他位置,请参阅 OpenTelemetry 入门指南

ASP.NET 示例

先决条件

创建一个示例应用程序

首先,创建新的 ASP.NET Web 应用以用作演示应用程序。

dotnet new webapp

此应用会显示一个网页,但我们浏览该网页时尚未收集分布式跟踪信息。

配置集合

若要使用 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 Release Candidate 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 浏览器浏览到正在托管的网页。 现在 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(...) 启用分布式跟踪。 AddAspNetCoreInstrumentation() 启用 OpenTelemetry 来收集 ASP.NET Core Web 服务器生成的所有分布式跟踪活动,并且 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

此示例代码调用了 AddSource("Sample.DistributedTracing"),这样 OpenTelemetry 就可以捕获代码中已经存在的 ActivitySource 所生成的 Activity:

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

可以通过使用源名称调用 AddSource() 来捕获任何 ActivitySource 中的遥测。

导出工具

控制台导出工具对简单的示例或本地开发非常有用,但在生产部署中,可能需要将跟踪发送到集中存储。 OpenTelemetry 支持使用不同导出工具的各种目标。 有关如何配置 OpenTelemetry 的详细信息,请参阅 OpenTelemetry 入门指南

使用 Application Insights 收集跟踪

ASP.NETASP.NET Core 应用配置 Application Insights SDK 或者启用无代码检测后,系统会自动捕获分布式跟踪遥测。

有关详细信息,请参阅 Application Insights 分布式跟踪文档

注意

目前,Application Insights 仅支持收集特定的已知 Activity 检测,并忽略新用户添加的 Activity。 Application Insights 提供 TrackDependency 作为供应商特定的 API,用于添加自定义分布式跟踪信息。

使用自定义逻辑收集跟踪

开发人员可随意为活动跟踪数据创建自定义收集逻辑。 此示例使用 .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 格式。 有关详细信息,请参阅 Activity ID

System.Diagnostics.ActivityListener 用于在活动的生存期内接收回调。

  • ShouldListenTo - 每个 Activity 都与 ActivitySource 关联,后者可充当 Activity 的命名空间和生成方。 对于进程中的每个 ActivitySource,都会调用一次此回调。 如果你有兴趣执行采样或收到有关此源产生的活动的启动/停止事件的通知,则返回 true。
  • Sample - 默认情况下,StartActivity 不会创建 Activity 对象,除非某个 ActivityListener 指示应对其进行采用。 返回 AllDataAndRecorded 表示应创建活动,IsAllDataRequested 应设置为 true,并且 ActivityTraceFlags 将设置 Recorded 标志。 IsAllDataRequested 可被检测代码观测到,这提示侦听器需要确保填充辅助 Activity 信息(如标记和事件)。 记录的标志在 W3C TraceContext ID 中进行编码,暗示分布式跟踪中涉及的其他进程应对此跟踪进行采样。
  • ActivityStartedActivityStopped 分别在启动和停止活动时调用。 可以通过这些回调记录 Activity 的相关信息或对其进行修改。 当 Activity 刚启动时,许多数据可能仍然不完整,在 Activity 停止之前,系统会对这些数据进行填充。

创建 ActivityListener 并填充回调之后,即可通过调用 ActivitySource.AddActivityListener(ActivityListener) 启动调用回调操作。 调用 ActivityListener.Dispose() 可停止回调流。 请注意,在多线程代码中,当 Dispose() 运行时,甚至在它返回后不久,都可能会收到正在进行的回调通知。