System.Net 库中的分布式跟踪

分布式跟踪 是一种诊断技术,可帮助工程师本地化应用程序中的故障和性能问题,尤其是分布在多台计算机或进程中的故障和性能问题。 此方法通过应用程序跟踪请求,方法是将不同组件完成的工作关联在一起,并将其与应用程序可能针对并发请求执行的其他工作分开。 例如,对典型 Web 服务的请求可能首先由负载均衡器接收,然后转发到 Web 服务器进程,然后向数据库发出多个查询。 分布式跟踪允许工程师区分这些步骤是否失败,以及每个步骤花费的时间。 它还可以在运行时记录每个步骤生成的消息。

.NET 中的跟踪系统设计为使用 OpenTelemetry (OTel),并使用 OTel 将数据导出到监视系统。 .NET 中的跟踪是使用 System.Diagnostics API 实现的,其中工作单元由 System.Diagnostics.Activity 类表示,该类对应于 OTel 范围。 OpenTelemetry 为范围(活动)及其属性(标记)定义了一个全行业的标准命名方案,称为语义约定。 .NET 遥测尽可能使用现有的语义约定。

注意

本文中的术语范围活动为同义词。 在 .NET 代码的上下文中,它们引用 System.Diagnostics.Activity 实例。 不要将 OTel 范围与 System.Span<T>混淆。

提示

有关所有内置活动及其标记/属性的综合列表,请参阅 .NET 中的内置活动

仪表

为了发出跟踪信息,System.Net 库会通过内置 ActivitySource 源进行检测,而这些源会创建 Activity 对象来跟踪已执行的工作。 只有订阅了 ActivitySource 的侦听器才会创建活动。

内置检测随 .NET 版本的演变。

  • 在 .NET 8 及更早版本中,检测仅限于创建一个空的 HTTP 客户端请求活动。 这意味着用户必须依靠 OpenTelemetry.Instrumentation.Http 库为活动填充所需的信息(例如标记),以发出有用的跟踪。
  • .NET 9 根据 OTel HTTP 客户端语义约定,在 HTTP 客户端请求活动中发出名称、状态、异常信息和最重要的标记,从而扩展了仪器功能。 这意味着,在 .NET 9+ 上,可以省略 OpenTelemetry.Instrumentation.Http 依赖项,除非需要更高级的功能,如 扩充
  • .NET 9 还引入了 试验性连接跟踪,在 System.Net 库中添加新活动以支持诊断连接问题。

收集 System.Net 跟踪

最低级别AddActivityListener 方法可支持跟踪收集,该方法可注册包含用户定义逻辑的 ActivityListener 对象。

但是,作为应用程序开发人员,你可能更愿意依靠由 OpenTelemetry .NET SDK 提供的功能所构建的丰富生态系统来收集、导出和监控跟踪。

使用 .NET Aspire 收集日志

在 ASP.NET 应用程序中收集跟踪和指标的一种简单方法是使用 .NET Aspire。 .NET Aspire 是 .NET 的一组扩展,便于创建和使用分布式应用程序。 使用 .NET Aspire 的好处之一是,遥测是内置的,使用适用于 .NET 的 OpenTelemetry 库。

.NET Aspire 的默认项目模板包含 ServiceDefaults 项目。 .NET Aspire 解决方案中的每个服务都有对服务默认值项目的引用。 服务使用它来设置和配置 OTel。

服务默认值项目模板包括 OTel SDK、ASP.NET、HttpClient 和运行时检测包。 这些检测组件在 Extensions.cs 文件中配置。 为了支持 Aspire 仪表板中的遥测可视化,服务默认值项目还默认包含 OTLP 导出程序。

Aspire 仪表板旨在将遥测观察引入本地调试周期,使开发人员能够确保应用程序生成遥测数据。 遥测可视化还有助于在本地诊断这些应用程序。 能够查看服务之间的调用在调试时与在生产中一样有用。 F5 Visual Studio 中的 AppHost 项目或从命令行 dotnet run AppHost 项目时,将自动启动 .NET Aspire 仪表板。

Aspire 仪表板

有关 .NET Aspire 的详细信息,请参阅:

在不使用 .NET Aspire 业务流程的情况下重复使用服务默认设置项目

Aspire Service Defaults 项目提供了一种简单的方式来为 ASP.NET 项目配置 OTel,即使不使用 .NET Aspire 的其余部分(例如用于业务流程的 AppHost)。 服务默认值项目通过 Visual Studio 或 dotnet new作为项目模板提供。 它配置 OTel 并设置 OTLP 导出程序。 然后,可以使用 OTel 环境变量 将 OTLP 终结点配置为向其发送遥测数据,并为应用程序提供资源属性。

在 .NET Aspire 之外使用 ServiceDefaults 的步骤如下:

  1. 使用 Visual Studio 中的“添加新项目”将 ServiceDefaults 项目添加到解决方案,或使用 dotnet new

    dotnet new aspire-servicedefaults --output ServiceDefaults
    
  2. 从 ASP.NET 应用程序中引用 ServiceDefaults 项目。 在 Visual Studio 中,选择 添加>项目引用 并选择 ServiceDefaults 项目”

  3. 调用 OpenTelemetry 安装程序函数 ConfigureOpenTelemetry() 作为应用程序生成器初始化的一部分。

    var builder = WebApplication.CreateBuilder(args)
    builder.ConfigureOpenTelemetry(); // Extension method from ServiceDefaults.
    var app = builder.Build();
    app.MapGet("/", () => "Hello World!");
    app.Run();
    

要获取完整演示,请参阅 示例:使用 OpenTelemetry 结合 OTLP 和独立的 Aspire 仪表板

实验性连接跟踪

排查 HttpClient 问题或瓶颈时,查看发送 HTTP 请求时花费的时间可能至关重要。 通常,此问题发生在 HTTP 连接建立期间,通常分解为 DNS 查找、TCP 连接和 TLS 握手。

.NET 9 引入了实验性连接跟踪,添加了一个 HTTP connection setup 跨度,其中包含三个子范围,表示连接建立的 DNS、TCP 和 TLS 阶段。 连接跟踪的 HTTP 部分在 SocketsHttpHandler内实现,这意味着活动模型必须遵循基础连接池行为。

注意

SocketsHttpHandler中,连接和请求具有独立的生命周期。 共用连接 可以长时间存在,并处理许多请求。 发出请求时,如果连接池中没有立即可用的连接,则会将请求添加到请求队列中,等待可用连接。 等待请求与连接之间没有直接关系。 当另一个连接可供使用时,连接过程可能已启动,在这种情况下使用释放的连接。 因此,HTTP connection setup 范围并未作为 HTTP client request 范围的子代建模,而是使用了范围链接。

.NET 9 引入了以下范围,用于收集详细的连接信息:

名字 ActivitySource 描述
HTTP wait_for_connection Experimental.System.Net.Http.Connections HTTP client request 范围的子范围,表示请求在请求队列中等待可用连接的时间间隔。
HTTP connection_setup Experimental.System.Net.Http.Connections 表示建立 HTTP 连接。 单独的跟踪根范围,有自己的 TraceIdHTTP client request 范围可能包含指向 HTTP connection_setup的链接。
DNS lookup Experimental.System.Net.NameResolution DNS 查找由 Dns 类执行。
socket connect Experimental.System.Net.Sockets 建立 Socket 连接。
TLS handshake Experimental.System.Net.Security SslStream 执行的 TLS 客户端或服务器握手。

注意

相应的 ActivitySource 名称以前缀 Experimental 开头,因为随着我们对这些范围在生产中的运行情况有了更多了解,它们可能会在未来版本中进行修改。

这些范围过于详细,不适合在工作负荷较高的生产场景中全天候使用 - 它们干扰较大,而且通常不需要这种级别的检测。 但是,如果尝试诊断连接问题或更深入地了解网络和连接延迟如何影响服务,则它们会提供难以通过其他方式收集的见解。

启用 Experimental.System.Net.Http.Connections ActivitySource 后,HTTP client request 跨度包含一个链接,该链接指向与处理请求的连接相对应的 HTTP connection_setup 跨度。 由于 HTTP 连接可能会持续较长时间,这可能会导致每个请求活动都有许多链接指向连接范围。 某些 APM 监控工具会积极地遍历跨度之间的链接来构建其视图,因此,在工具未设计为处理大量链接的情况下,包含此跨度可能会导致问题。

下图说明了跨度的行为及其关系:

连接会跨越时间。

演练:在 .NET 9 中使用试验性连接跟踪

本演练使用 .NET 9 Aspire Starter App 来演示连接跟踪,但使用其他监控工具进行设置也很简单。 关键步骤是启用 ActivitySources。

  1. 使用 dotnet new创建 .NET Aspire 9 入门应用

    dotnet new aspire-starter-9 --output ConnectionTracingDemo
    

    或在 Visual Studio 中:

    在 Visual Studio 中创建 .NET Aspire 9 初学者应用

  2. ServiceDefaults 项目中打开 Extensions.cs,然后编辑 ConfigureOpenTelemetry 方法,在跟踪配置回调中添加用于连接的 ActivitySources:

    .WithTracing(tracing =>
    {
        tracing.AddAspNetCoreInstrumentation()
            // Instead of using .AddHttpClientInstrumentation()
            // .NET 9 allows to add the ActivitySources directly.
            .AddSource("System.Net.Http")
            // Add the experimental connection tracking ActivitySources using a wildcard.
            .AddSource("Experimental.System.Net.*");
    });
    
  3. 启动解决方案。 这样将打开 .NET Aspire 仪表板

  4. 导航到 webfrontend 应用的“天气”页,以生成一个指向 apiserviceHttpClient 请求。

  5. 返回仪表板并导航到跟踪页。 打开 webfrontend: GET /weather 跟踪。

    在 Aspire 仪表板HttpClient 跨度

启用连接工具后发出 HTTP 请求时,您应会看到对客户端请求跨度的以下更改:

  • 如果需要建立连接,或者应用正在等待连接池的连接,则会显示一个额外的 HTTP wait_for_connection 时间段,表示等待建立连接时的延迟。 这有助于了解代码中发出的 HttpClient 请求之间的延迟,以及请求的处理实际开始的时间。 在上图中:
    • 所选范围为 HttpClient 请求。
    • 下面的范围表示请求等待建立连接所花费的时间。
    • 最后一个黄色范围来自处理请求的目标。
  • HttpClient 范围将具有指向 HTTP connection_setup 范围的链接,该范围表示创建请求使用的 HTTP 连接的活动。

在 Aspire 仪表板中,Aspire 仪表板中的连接设置范围

如前所述HTTP connection_setup 区间是一个单独的区间,拥有自己独立的TraceId,因为它的生命周期不依赖于每个单独的客户端请求。 此范围通常具有子范围 DNS lookup、(TCP)socket connectTLS client handshake

扩充

在某些情况下,必须增强现有的 System.Net 跟踪功能。 这通常意味着向内置活动注入其他标记/属性。 这被称为扩充

OpenTelemetry 检测工具库中的增强 API

若要向 HTTP 客户端请求活动添加其他标记/属性,最简单的方法是使用 OpenTelemetry HttpClient 和 HttpWebRequest 检测库 HttpClient 扩充 API。 这需要依赖 OpenTelemetry.Instrumentation.Http 包。

手动扩充

可以手动实现 HTTP client request 活动的扩充。 为此,需要在活动完成之前访问在请求活动范围内运行的代码中的 Activity.Current。 这可以通过实现 IObserver<DiagnosticListener> 并将其订阅到 AllListeners 来实现,以便在网络活动发生时获得回调。 事实上,OpenTelemetry HttpClient 和 HttpWebRequest 检测库 就是这样实现的。 有关代码示例,请参阅 DiagnosticSourceSubscriber.cs 中的订阅代码以及 HttpHandlerDiagnosticListener.cs 中的基础实现,其中通知是委托给该基础实现的。

需要更多的跟踪吗?

如果对可以通过跟踪公开的其他有用信息有建议,请创建 dotnet/运行时问题