Skip to content

Commit 2a5fa39

Browse files
Merge pull request #49307 from dotnet/main
Merge main into live
2 parents 16be0f6 + fa2db7c commit 2a5fa39

File tree

13 files changed

+409
-17
lines changed

13 files changed

+409
-17
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
---
2+
title: Monitor and analyze HTTP client performance
3+
description: Learn how to use the HttpClientLatency with dependency injection in your .NET workloads.
4+
author: IEvangelist
5+
ms.author: dapine
6+
ms.date: 09/29/2025
7+
ai-usage: ai-assisted
8+
---
9+
10+
# HTTP client latency telemetry in .NET
11+
12+
When you build applications that communicate over HTTP, it's important to observe request performance characteristics.
13+
The <xref:Microsoft.Extensions.DependencyInjection.HttpClientLatencyTelemetryExtensions.AddHttpClientLatencyTelemetry*>
14+
extension enables collection of detailed timing information for outgoing HTTP calls with no changes to calling code.
15+
It plugs into the existing `HttpClientFactory` pipeline to capture stage timings across the request lifecycle, record
16+
HTTP protocol details, measure garbage collection impact where the runtime exposes that data, and emit a uniform
17+
telemetry shape suitable for performance analysis and tuning.Enable them by calling `AddHttpClientLatencyTelemetry()` extension method.
18+
The built‑in handler creates an `ILatencyContext` per outbound request and populates measures by the time the inner
19+
pipeline completes. Consume them after `await base.SendAsync(...)` in a later delegating handler (added after telemetry)
20+
and export to your metrics backend. Example:
21+
22+
Register extension method:
23+
24+
:::code language="csharp" source="snippets/http/latency/RegisterHandler.cs" range="3-24" highlight="11":::
25+
26+
Access the context:
27+
28+
:::code language="csharp" source="snippets/http/latency/HttpLatencyExportHandler.cs" range="4-23" highlight="12,15":::
29+
30+
### [.NET CLI](#tab/dotnet-cli)
31+
32+
```dotnetcli
33+
dotnet add package Microsoft.Extensions.Http.Diagnostics --version 9.10.0
34+
```
35+
36+
### [PackageReference](#tab/package-reference)
37+
38+
```xml
39+
<PackageReference Include="Microsoft.Extensions.Http.Diagnostics" Version="9.10.0" />
40+
```
41+
42+
---
43+
44+
For more information, see [dotnet package add](../tools/dotnet-package-add.md) or [Manage package dependencies in .NET applications](../tools/dependencies.md).
45+
46+
### Register HTTP client latency telemetry
47+
48+
To add HTTP client latency telemetry to your application, call the <xref:Microsoft.Extensions.DependencyInjection.HttpClientLatencyTelemetryExtensions.AddHttpClientLatencyTelemetry*> extension method when configuring your services:
49+
50+
:::code language="csharp" source="snippets/http/latency/Program.cs" id="extensions" highlight="7":::
51+
52+
This registration adds a `DelegatingHandler` to all HTTP clients created through <xref:System.Net.Http.IHttpClientFactory>, collecting detailed latency information for each request.
53+
54+
### Configure telemetry options
55+
56+
You configure telemetry collection through the <xref:Microsoft.Extensions.Http.Latency.HttpClientLatencyTelemetryOptions> ([standard options pattern](https://learn.microsoft.com/dotnet/core/extensions/options)).
57+
You can supply values either with a delegate or by binding configuration (for example, `appsettings.json`).
58+
The options instance is resolved once per handler pipeline so changes apply to new clients/handlers.
59+
60+
:::code language="csharp" source="snippets/http/latency/Program.cs" range="23-31" highlight="1-5,8,9":::
61+
62+
### Configuration options
63+
64+
The <xref:Microsoft.Extensions.Http.Latency.HttpClientLatencyTelemetryOptions> class offers the following settings:
65+
66+
| Option | Type | Default | Description | When to disable |
67+
|--------------------------------|---------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
68+
| EnableDetailedLatencyBreakdown | Boolean | `true` | Enables fine-grained phase timing for each HttpClient request (for example, connection establishment, headers sent, first byte, completion) to produce a breakdown of total latency. Adds a small extra CPU/time measurement cost, no wire overhead. | Set to `false` only in very high-throughput scenarios where minimal overhead is required and total duration alone is sufficient. |
69+
70+
### Collected telemetry data
71+
72+
When HTTP client latency telemetry is enabled, it captures phase timestamps, selected measures (where supported), and protocol attributes used for performance analysis.
73+
74+
#### Timing checkpoints
75+
76+
Timestamps are recorded for key stages of the HTTP request lifecycle:
77+
78+
| Phase | Start Event | End Event | Notes |
79+
|-------------------------|----------------------------|----------------------------|---------------------------------------------------------|
80+
| DNS resolution | Http.NameResolutionStart | Http.NameResolutionEnd | Host name lookup (may be cached and skipped). |
81+
| Socket connection | Http.SocketConnectStart | Http.SocketConnectEnd | CP (and TLS handshake if combined by handler). |
82+
| Connection establishment| | Http.ConnectionEstablished | Marks usable connection after handshake. |
83+
| Request headers | Http.RequestHeadersStart | Http.RequestHeadersEnd | Writing request headers to the wire/buffer. |
84+
| Request content | Http.RequestContentStart | Http.RequestContentEnd | Streaming or buffering request body. |
85+
| Response headers | Http.ResponseHeadersStart | Http.ResponseHeadersEnd | First byte to completion of header parsing. |
86+
| Response content | Http.ResponseContentStart | Http.ResponseContentEnd | Reading full response body (to completion or disposal). |
87+
88+
#### Measures (platform dependent)
89+
90+
Measures quantify latency contributors that raw phase checkpoints cannot (GC pause overlap, connection churn, other
91+
accumulated counts or durations). They are collected in an in‑memory latency context created when you call
92+
`AddHttpClientLatencyTelemetry()`. Nothing is emitted automatically: the context simply accumulates checkpoints, measures,
93+
and tags until the request completes. If you also enable HTTP client logging enrichment with `AddExtendedHttpClientLogging()`,
94+
the completed context is flattened into a single structured log field named `LatencyInfo` (version marker, server name if available, then tag, checkpoint, and measure name/value sequences).
95+
96+
That log field is the only built‑in output artifact; no metrics or traces are produced unless you add your own exporter.
97+
To surface them as metrics, read the context after the request pipeline returns and record (for example) GC pause overlap
98+
to a histogram and connection initiations to a counter, optionally dimensioned by protocol version.
99+
100+
| Name | Description |
101+
|--------------------------|-------------------------------------------------------------------------|
102+
| Http.GCPauseTime | Total GC pause duration overlapping the request. |
103+
| Http.ConnectionInitiated | Emitted when a new underlying connection (not pooled reuse) is created. |
104+
105+
#### Tags
106+
107+
Use tags to attach stable categorical dimensions to each request so you can segment, filter, and aggregate metrics
108+
and logs without reprocessing raw data. They are low‑cardinality classification labels (not timings) captured
109+
in the latency context and, if HTTP client log enrichment is enabled, serialized into a single LatencyInfo log field.
110+
Enable enrichment at application startup by adding the logging extension (for all clients or per client) along with
111+
latency telemetry, for example:
112+
113+
:::code language="csharp" source="snippets/http/latency/Program.cs" range="36-39" highlight="2,3":::
114+
115+
After this, outbound requests logged through the structured logging pipeline will include the `LatencyInfo` property
116+
containing the flattened tags, checkpoints, and measures. No metrics or traces are emitted automatically for tags;
117+
export them yourself (e.g., turn tag values into metric dimensions or `Activity` tags) if you need them outside logs.
118+
119+
| Tag | Description |
120+
|--------------|-----------------------------------------------------------------|
121+
| Http.Version | HTTP protocol version negotiated/used (for example, 1.1, 2, 3). |
122+
123+
## Usage example
124+
125+
These components enable tracking and reporting the latency of HTTP client request processing.
126+
127+
You can register the services using the following methods:
128+
129+
:::code language="csharp" source="snippets/http/latency/Program.cs" range="44-53" highlight="1,2,4-6, 8-10":::
130+
131+
For example:
132+
133+
:::code language="csharp" source="snippets/http/latency/Program.cs" range="58-75" highlight="10,13,16":::
134+
135+
### Platform considerations
136+
137+
HTTP client latency telemetry runs on all supported targets (.NET 9, .NET 8, .NET Standard 2.0, and .NET Framework 4.6.2).
138+
Core timing checkpoints are always collected. The GC pause metric (Http.GCPauseTime) is emitted only when running on .NET 8 or .NET 9.
139+
The implementation detects platform capabilities at run time and enables what is supported without additional configuration.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace GeneratedHttp.Example;
2+
3+
4+
public sealed class HttpLatencyExportHandler : DelegatingHandler
5+
{
6+
// ILatencyContextAccessor is just an example of some accessor that is able to read latency context
7+
private readonly ILatencyContextAccessor _latency;
8+
9+
public HttpLatencyExportHandler(ILatencyContextAccessor latency) => _latency = latency;
10+
11+
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
12+
{
13+
var rsp = await base.SendAsync(request, ct).ConfigureAwait(false);
14+
15+
var ctx = _latency.Current;
16+
if (ctx != null)
17+
{
18+
var data = ctx.LatencyData;
19+
// Record/export gc and conn with version as a dimension here.
20+
}
21+
return rsp;
22+
}
23+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Http.Diagnostics;
4+
5+
6+
internal class Program
7+
{
8+
private static void ConfigureHttpClientLatency(HostApplicationBuilder builder)
9+
{
10+
// <extensions>
11+
var builder = WebApplication.CreateBuilder(args);
12+
13+
// Add HTTP client factory
14+
builder.Services.AddHttpClient();
15+
16+
// Add HTTP client latency telemetry
17+
builder.Services.AddHttpClientLatencyTelemetry();
18+
// </extensions>
19+
}
20+
21+
private static void ConfigureWithDelegate(HostApplicationBuilder builder)
22+
{
23+
// Configure with delegate
24+
builder.Services.AddHttpClientLatencyTelemetry(options =>
25+
{
26+
options.EnableDetailedLatencyBreakdown = true;
27+
});
28+
29+
// Or configure from configuration
30+
builder.Services.AddHttpClientLatencyTelemetry(
31+
builder.Configuration.GetSection("HttpClientTelemetry"));
32+
}
33+
34+
private static void EnableLatencyContext(HostApplicationBuilder builder)
35+
{
36+
var builder = Host.CreateApplicationBuilder(args);
37+
builder.Services.AddHttpClientLatencyTelemetry(); // enables latency context + measures/tags
38+
builder.Services.AddExtendedHttpClientLogging();
39+
var app = builder.Build();
40+
}
41+
42+
private static void RegistrationOptions(HostApplicationBuilder builder)
43+
{
44+
public static IServiceCollection AddHttpClientLatencyTelemetry(
45+
this IServiceCollection services);
46+
47+
public static IServiceCollection AddHttpClientLatencyTelemetry(
48+
this IServiceCollection services,
49+
IConfigurationSection section);
50+
51+
public static IServiceCollection AddHttpClientLatencyTelemetry(
52+
this IServiceCollection services,
53+
Action<HttpClientLatencyTelemetryOptions> configure);
54+
}
55+
56+
private static void HttpClientLatency(HostApplicationBuilder builder)
57+
{
58+
var builder = Host.CreateApplicationBuilder(args);
59+
60+
// Register IHttpClientFactory:
61+
builder.Services.AddHttpClient();
62+
63+
// Register redaction services:
64+
builder.Services.AddRedaction();
65+
66+
// Register latency context services:
67+
builder.Services.AddLatencyContext();
68+
69+
// Register HttpClient logging enrichment & redaction services:
70+
builder.Services.AddExtendedHttpClientLogging();
71+
72+
// Register HttpClient latency telemetry services:
73+
builder.Services.AddHttpClientLatencyTelemetry();
74+
75+
var host = builder.Build();
76+
}
77+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace GeneratedHttp.Example;
2+
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Http.Diagnostics;
5+
using Microsoft.Extensions.Hosting;
6+
7+
var builder = Host.CreateApplicationBuilder(args);
8+
9+
// An example of some accessor that is able to read latency context
10+
builder.Services.AddSingleton<ILatencyContextAccessor, LatencyContextAccessor>();
11+
12+
// Register HTTP client latency telemetry first so its delegating handler runs earlier.
13+
builder.Services.AddHttpClientLatencyTelemetry();
14+
15+
// Register export handler (runs after telemetry; sees finalized ILatencyContext).
16+
builder.Services.AddTransient<HttpLatencyExportHandler>();
17+
18+
// Register an HttpClient that will emit and export latency measures.
19+
builder.Services
20+
.AddHttpClient("observed")
21+
.AddHttpMessageHandler<HttpLatencyExportHandler>();
22+
23+
var host = builder.Build();
24+
await host.RunAsync();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>true</ImplicitUsings>
7+
<OutputType>Exe</OutputType>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<None Remove="appsettings.json" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<Content Include="appsettings.json">
16+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
17+
</Content>
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<PackageReference Include="Microsoft.Extensions.Http.Diagnostics" Version="9.10.0" />
22+
</ItemGroup>
23+
24+
</Project>

0 commit comments

Comments
 (0)