.NET 中的网络指标
指标 是一段时间内报告的数值指标。 它们通常用于监视应用的运行状况并生成警报。
从 .NET 8 开始,将对 System.Net.Http
和 System.Net.NameResolution
组件启用监测,以便使用 .NET 的新 System.Diagnostics.Metrics API发布指标。
这些指标是与 OpenTelemetry 合作设计的,以确保它们与标准一致,并能与常用工具(如 Prometheus 和 Grafana)良好配合。
它们也是多维的,这意味着度量值与称为标记(也称为属性或标签)的键值对相关联。 标记允许对度量进行分类以帮助分析。
提示
有关所有内置工具及其属性的综合列表,请参阅 System.Net 指标。
收集 System.Net 指标
若要利用内置指标检测,需要将 .NET 应用配置为收集这些指标。 这通常意味着将它们转换为外部存储和分析,例如,转换为监视系统。
可通过多种方式在 .NET 中收集网络指标。
- 有关使用简单自包含示例的快速概述,请参阅使用 dotnet-counters 收集指标。
- 对于生产时间指标收集和监视,可以将 Grafana 与 OpenTelemetry 和 Prometheus 或 Azure Monitor Application Insights 配合使用。 但是,由于这些工具的复杂性,在开发时可能不方便使用。
- 对于 开发时 指标收集和故障排除,我们建议使用 .NET Aspire,它提供了一种简单但可扩展的方法来启动应用程序中的指标和分布式跟踪,以及在本地诊断问题。
- 还可以 重复使用 Aspire Service Defaults 项目而不使用 Aspire 业务流程,这是将 OpenTelemetry 跟踪和指标配置 API 引入到 ASP.NET 项目中的一种便捷方法。
使用 dotnet-counters 收集指标
dotnet-counters
是一种跨平台命令行工具,用于临时检查 .NET 指标和一级性能调查。
为完成本教程,请创建一个应用,以并行的方式将 HTTP 请求发送到各种终结点。
dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics
将 Program.cs
的内容替换为以下示例代码:
using System.Net;
string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
using HttpClient client = new()
{
DefaultRequestVersion = HttpVersion.Version20
};
Console.WriteLine("Press any key to start.");
Console.ReadKey();
while (!Console.KeyAvailable)
{
await Parallel.ForAsync(0, Random.Shared.Next(20), async (_, ct) =>
{
string uri = uris[Random.Shared.Next(uris.Length)];
try
{
byte[] bytes = await client.GetByteArrayAsync(uri, ct);
await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
}
catch { await Console.Out.WriteLineAsync($"{uri} - failed."); }
});
}
确保已安装 dotnet-counters
:
dotnet tool install --global dotnet-counters
启动 HelloBuiltinMetrics 应用。
dotnet run -c Release
在单独的 CLI 窗口中启动 dotnet-counters
并指定要监视的进程名称和计量,然后按 HelloBuiltinMetrics 应用中的键,以便开始发送请求。 测量数据一传入,dotnet-counters
就使用最新数据连续刷新控制台:
dotnet-counters monitor --counters System.Net.Http,System.Net.NameResolution -n HelloBuiltinMetrics
使用 .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 仪表板。
快速指南
使用
dotnet new
创建 .NET Aspire 9 入门应用:dotnet new aspire-starter-9 --output AspireDemo
或在 Visual Studio 中,创建新项目并选择 .NET Aspire 9 初学者应用 模板:
中创建 .NET Aspire 9 初学者应用
在
ServiceDefaults
项目中打开Extensions.cs
,然后滚动到ConfigureOpenTelemetry
方法。 请注意订阅网络计量的AddHttpClientInstrumentation()
调用。.WithMetrics(metrics => { metrics.AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() .AddRuntimeInstrumentation(); })
请注意,在 .NET 8+ 上,
AddHttpClientInstrumentation()
可以替换为手动计量订阅:.WithMetrics(metrics => { metrics.AddAspNetCoreInstrumentation() .AddMeter("System.Net.Http") .AddMeter("System.Net.NameResolution") .AddRuntimeInstrumentation(); })
运行
AppHost
项目。 这应启动 Aspire 仪表板。导航到
webfrontend
应用的“天气”页,以生成一个指向apiservice
的HttpClient
请求。 多次刷新页面以发送多个请求。返回到仪表板,导航到 指标 页,然后选择
webfrontend
资源。 向下滚动后,应该能够浏览内置System.Net
指标。
有关 .NET Aspire 的详细信息,请参阅:
在不使用 .NET Aspire 编排的情况下复用服务默认值项目
Aspire Service Defaults 项目提供了一种简单的方式来为 ASP.NET 项目配置 OTel,即使不使用 .NET Aspire 的其余部分(例如用于业务流程的 AppHost)。 服务默认值项目通过 Visual Studio 或 dotnet new
作为项目模板提供。 它配置 OTel 并设置 OTLP 导出程序。 然后,可以使用 OTel 环境变量 将 OTLP 终结点配置为向其发送遥测数据,并为应用程序提供资源属性。
使用 ServiceDefaults 在 .NET Aspire 外部的步骤如下:
使用 Visual Studio 中的“添加新项目”将 ServiceDefaults 项目添加到解决方案,或使用
dotnet new
:dotnet new aspire-servicedefaults --output ServiceDefaults
从 ASP.NET 应用程序中引用 ServiceDefaults 项目。 在 Visual Studio 中,选择 添加>项目引用 并选择 ServiceDefaults 项目”
调用 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 仪表板配合使用。
使用 OpenTelemetry 和 Prometheus 查看 Grafana 中的指标
若要了解如何将示例应用与 Prometheus 和 Grafana 连接,请按照将 OpenTelemetry 与 Prometheus、Grafana 和 Jaeger 结合使用中的演练进行。
若要通过向各种终结点发送并行请求来强调 HttpClient
,请使用以下终结点扩展示例应用:
app.MapGet("/ClientStress", async Task<string> (ILogger<Program> logger, HttpClient client) =>
{
string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
await Parallel.ForAsync(0, 50, async (_, ct) =>
{
string uri = uris[Random.Shared.Next(uris.Length)];
try
{
await client.GetAsync(uri, ct);
logger.LogInformation($"{uri} - done.");
}
catch { logger.LogInformation($"{uri} - failed."); }
});
return "Sent 50 requests to example.com and httpbin.org.";
});
通过选择顶部工具栏上的 + 图标,然后选择 仪表板来创建 Grafana 仪表板。 在出现的仪表板编辑器中,在“标题”框中输入“打开 HTTP/1.1 连接”,并在 PromQL 表达式字段中输入以下查询:
sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})
选择“应用”保存并查看新仪表板。 它显示池中的活动与空闲 HTTP/1.1 连接数。
在 Grafana 中的 HTTP/1.1 连接
扩充
扩充 是向指标添加自定义标记(也称为属性或标签)。 如果应用想要向使用指标生成的仪表板或警报添加自定义分类,则这非常有用。
http.client.request.duration
检测通过向 HttpMetricsEnrichmentContext 注册回调来支持扩充。
请注意,这是一个低级 API,每个 HttpRequestMessage
都需要一个单独的回调注册。
若要在单个位置进行回调注册,一种简单的方法是实现自定义 DelegatingHandler。 这样,就可以在请求转发到内部处理程序并发送到服务器之前截获和修改请求:
using System.Net.Http.Metrics;
using HttpClient client = new(new EnrichmentHandler() { InnerHandler = new HttpClientHandler() });
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");
sealed class EnrichmentHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpMetricsEnrichmentContext.AddCallback(request, static context =>
{
if (context.Response is not null) // Response is null when an exception occurs.
{
// Use any information available on the request or the response to emit custom tags.
string? value = context.Response.Headers.GetValues("Enrichment-Value").FirstOrDefault();
if (value != null)
{
context.AddCustomTag("enrichment_value", value);
}
}
});
return base.SendAsync(request, cancellationToken);
}
}
如果您正在使用 IHttpClientFactory
,则可以使用 AddHttpMessageHandler 来注册 EnrichmentHandler
:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Net.Http.Metrics;
ServiceCollection services = new();
services.AddHttpClient(Options.DefaultName).AddHttpMessageHandler(() => new EnrichmentHandler());
ServiceProvider serviceProvider = services.BuildServiceProvider();
HttpClient client = serviceProvider.GetRequiredService<HttpClient>();
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");
注意
出于性能原因,仅在启用 http.client.request.duration
检测时才调用扩充回调,这意味着应该有某些东西正在收集指标。
这可以是 dotnet-monitor
、Prometheus Exporter、MeterListener
或 MetricCollector<T>
。
IMeterFactory
和 IHttpClientFactory
集成
HTTP 指标设计时考虑到了隔离性和可测试性。 使用 IMeterFactory 就能够支持这些方面,这样就可以通过自定义 Meter 实例发布指标,以使计量彼此隔离。
默认情况下,全局标识符 Meter 用于发出所有指标。 此 Meter 是 System.Net.Http
库的内部部分。 通过将自定义 IMeterFactory 实例分配给 SocketsHttpHandler.MeterFactory 或 HttpClientHandler.MeterFactory,可以覆盖此行为。
注意
对于 HttpClientHandler
和 SocketsHttpHandler
发出的所有指标,Meter.Name 是 System.Net.Http
。
使用 .NET 8+ 上的 Microsoft.Extensions.Http
和 IHttpClientFactory
时,默认 IHttpClientFactory
实现会自动选取在 IServiceCollection 中注册的 IMeterFactory
实例,并将其分配给在内部创建的主处理程序。
注意
从 .NET 8 开始,AddHttpClient 方法会自动调用 AddMetrics 来初始化指标服务,并使用 IServiceCollection注册默认 IMeterFactory 实现。 默认 IMeterFactory 按名称缓存 Meter 实例,这意味着每个 IServiceCollection都有一个名称为 System.Net.Http
的 Meter。
测试指标
以下示例演示如何使用 xUnit、IHttpClientFactory
和 MetricCollector<T>
从 Microsoft.Extensions.Diagnostics.Testing
NuGet 包验证单元测试中的内置指标:
[Fact]
public async Task RequestDurationTest()
{
// Arrange
ServiceCollection services = new();
services.AddHttpClient();
ServiceProvider serviceProvider = services.BuildServiceProvider();
var meterFactory = serviceProvider.GetService<IMeterFactory>();
var collector = new MetricCollector<double>(meterFactory,
"System.Net.Http", "http.client.request.duration");
var client = serviceProvider.GetRequiredService<HttpClient>();
// Act
await client.GetStringAsync("http://example.com");
// Assert
await collector.WaitForMeasurementsAsync(minCount: 1).WaitAsync(TimeSpan.FromSeconds(5));
Assert.Collection(collector.GetMeasurementSnapshot(),
measurement =>
{
Assert.Equal("http", measurement.Tags["url.scheme"]);
Assert.Equal("GET", measurement.Tags["http.request.method"]);
});
}
Metrics 与EventCounters
Metrics 比 EventCounters 功能更丰富,最明显的是因为它们具有多维性质。 借助此多维性,可以在 Prometheus 等工具中创建复杂的查询,并获取 EventCounters 无法实现的级别的见解。
然而,从 .NET 8 开始,仅使用指标检测 System.Net.Http
和 System.Net.NameResolutions
组件,这意味着,如果需要堆栈较低级别的计数器(如 System.Net.Sockets
或 System.Net.Security
),则必须使用 EventCounters。
此外,指标与其匹配的 EventCounters 之间存在一些语义差异。
例如,使用 HttpCompletionOption.ResponseContentRead
时,current-requests
EventCounter 将请求视为处于活动状态,直到读取请求正文的最后一个字节为止。
其指标对应 http.client.active_requests
不包括在计算活动请求时读取响应正文所用的时间。
需要更多的指标?
如果对可以通过指标公开的其他有用信息有建议,请创建 dotnet/运行时问题。