From 19a2a6cb5ed83359736cea49ab1c6109713b8a3b Mon Sep 17 00:00:00 2001 From: Dmytro Bohdanov Date: Mon, 26 Jan 2026 16:40:24 +0100 Subject: [PATCH 1/4] new api surface, first test iteration --- ...lientLoggingHttpClientBuilderExtensions.cs | 88 +++++++++- ...ttpClientLatencyTelemetryExtensionsTest.cs | 154 +++++++++++++++++- 2 files changed, 235 insertions(+), 7 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs index 24258bab36f..39aed688375 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs @@ -34,7 +34,30 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu { _ = Throw.IfNull(builder); - return AddExtendedHttpClientLoggingInternal(builder); + return AddExtendedHttpClientLoggingInternal(builder, configureOptionsBuilder: null, wrapHandlersPipeline: true); + } + + /// + /// Adds an to emit logs for outgoing requests for a named . + /// + /// The . + /// + /// Determines the logging behavior when resilience strategies (like retries or hedging) are configured. + /// When , logs one record per logical request with total duration including all retry attempts. + /// When , logs each individual attempt separately with per-attempt duration. + /// + /// The value of . + /// + /// All other loggers are removed - including the default one, registered via . + /// A lot of the information logged by this method (like bodies, methods, host, path, and duration) will be added as enrichment tags to the structured log. Make sure + /// you have a way of viewing structured logs in order to view this extra information. + /// + /// Argument is . + public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, bool wrapHandlersPipeline) + { + _ = Throw.IfNull(builder); + + return AddExtendedHttpClientLoggingInternal(builder, configureOptionsBuilder: null, wrapHandlersPipeline); } /// @@ -54,7 +77,32 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu _ = Throw.IfNull(builder); _ = Throw.IfNull(section); - return AddExtendedHttpClientLoggingInternal(builder, options => options.Bind(section)); + return AddExtendedHttpClientLoggingInternal(builder, options => options.Bind(section), wrapHandlersPipeline: true); + } + + /// + /// Adds an to emit logs for outgoing requests for a named . + /// + /// The . + /// The to use for configuring . + /// + /// Determines the logging behavior when resilience strategies (like retries or hedging) are configured. + /// When , logs one record per logical request with total duration including all retry attempts. + /// When , logs each individual attempt separately with per-attempt duration. + /// + /// The value of . + /// + /// All other loggers are removed - including the default one, registered via . + /// A lot of the information logged by this method (like bodies, methods, host, path, and duration) will be added as enrichment tags to the structured log. Make sure + /// you have a way of viewing structured logs in order to view this extra information. + /// + /// Any of the arguments is . + public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, IConfigurationSection section, bool wrapHandlersPipeline) + { + _ = Throw.IfNull(builder); + _ = Throw.IfNull(section); + + return AddExtendedHttpClientLoggingInternal(builder, options => options.Bind(section), wrapHandlersPipeline); } /// @@ -74,10 +122,38 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu _ = Throw.IfNull(builder); _ = Throw.IfNull(configure); - return AddExtendedHttpClientLoggingInternal(builder, options => options.Configure(configure)); + return AddExtendedHttpClientLoggingInternal(builder, options => options.Configure(configure), wrapHandlersPipeline: true); + } + + /// + /// Adds an to emit logs for outgoing requests for a named . + /// + /// The . + /// The delegate to configure with. + /// + /// Determines the logging behavior when resilience strategies (like retries or hedging) are configured. + /// When , logs one record per logical request with total duration including all retry attempts. + /// When , logs each individual attempt separately with per-attempt duration. + /// + /// The value of . + /// + /// All other loggers are removed - including the default one, registered via . + /// A lot of the information logged by this method (like bodies, methods, host, path, and duration) will be added as enrichment tags to the structured log. Make sure + /// you have a way of viewing structured logs in order to view this extra information. + /// + /// Any of the arguments is . + public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, Action configure, bool wrapHandlersPipeline) + { + _ = Throw.IfNull(builder); + _ = Throw.IfNull(configure); + + return AddExtendedHttpClientLoggingInternal(builder, options => options.Configure(configure), wrapHandlersPipeline); } - private static IHttpClientBuilder AddExtendedHttpClientLoggingInternal(IHttpClientBuilder builder, Action>? configureOptionsBuilder = null) + private static IHttpClientBuilder AddExtendedHttpClientLoggingInternal( + IHttpClientBuilder builder, + Action>? configureOptionsBuilder, + bool wrapHandlersPipeline) { var optionsBuilder = builder.Services .AddOptionsWithValidateOnStart(builder.Name); @@ -97,6 +173,6 @@ private static IHttpClientBuilder AddExtendedHttpClientLoggingInternal(IHttpClie .RemoveAllLoggers() .AddLogger( serviceProvider => serviceProvider.GetRequiredKeyedService(builder.Name), - wrapHandlersPipeline: true); + wrapHandlersPipeline); } -} +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs index 508a42d18f0..8195e8452a5 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs @@ -4,11 +4,16 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Net; using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http.Diagnostics; using Microsoft.Extensions.Http.Latency.Internal; +using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -104,6 +109,140 @@ public void RequestLatencyExtensions_Add_BindsToConfigSection() Assert.NotNull(options); Assert.Equal(expectedOptions.EnableDetailedLatencyBreakdown, options.EnableDetailedLatencyBreakdown); } + + [Fact] +public async Task LatencyInfo_IsPopulated_WhenLoggerWrapsHandlersPipeline() +{ + using var sp = new ServiceCollection() + .AddLatencyContext() + .AddRedaction() + .AddHttpClientLatencyTelemetry() + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) + .ConfigureAdditionalHttpMessageHandlers((handlers, _) => + { + handlers.Add(new ServerNameStubHandler("TestServer")); + }) + .AddExtendedHttpClientLogging(wrapHandlersPipeline: true) + .Services + .AddFakeLogging() + .BuildServiceProvider(); + + var client = sp.GetRequiredService().CreateClient("test"); + + using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var collector = sp.GetFakeLogCollector(); + var record = collector.LatestRecord; + Assert.NotNull(record); + + var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); + Assert.False(string.IsNullOrEmpty(latencyInfo)); + Assert.StartsWith("v1.0,", latencyInfo); + Assert.Contains("TestServer", latencyInfo); +} + +[Fact] +public async Task LatencyInfo_IsPopulated_WithConfigurationSection_AndWrapHandlersPipeline() +{ + var configSection = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "Logging:LogBody", "true" } + }) + .Build() + .GetSection("Logging"); + + using var sp = new ServiceCollection() + .AddLatencyContext() + .AddRedaction() + .AddHttpClientLatencyTelemetry() // ← Move here + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) + .ConfigureAdditionalHttpMessageHandlers((handlers, _) => + { + handlers.Add(new ServerNameStubHandler("TestServer")); + }) + .AddExtendedHttpClientLogging(configSection, wrapHandlersPipeline: true) + .Services + .AddFakeLogging() + .BuildServiceProvider(); + + var client = sp.GetRequiredService().CreateClient("test"); + + using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var collector = sp.GetFakeLogCollector(); + var record = collector.LatestRecord; + Assert.NotNull(record); + + var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); + Assert.False(string.IsNullOrEmpty(latencyInfo)); + Assert.StartsWith("v1.0,", latencyInfo); +} + +[Fact] +public async Task LatencyInfo_IsPopulated_WithActionConfiguration_AndWrapHandlersPipeline() +{ + using var sp = new ServiceCollection() + .AddLatencyContext() + .AddRedaction() + .AddHttpClientLatencyTelemetry() // ← Move here + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) + .ConfigureAdditionalHttpMessageHandlers((handlers, _) => + { + handlers.Add(new ServerNameStubHandler("TestServer")); + }) + .AddExtendedHttpClientLogging(o => o.LogBody = true, wrapHandlersPipeline: false) + .Services + .AddFakeLogging() + .BuildServiceProvider(); + + var client = sp.GetRequiredService().CreateClient("test"); + + using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var collector = sp.GetFakeLogCollector(); + var record = collector.LatestRecord; + Assert.NotNull(record); + + var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); + Assert.False(string.IsNullOrEmpty(latencyInfo)); + Assert.StartsWith("v1.0,", latencyInfo); +} + + [Fact] + public async Task LatencyInfo_IsNotPresent_WhenLatencyTelemetryNotAdded() + { + using var sp = new ServiceCollection() + .AddRedaction() + .AddExtendedHttpClientLogging() + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) + .ConfigureAdditionalHttpMessageHandlers((handlers, _) => + { + handlers.Add(new ServerNameStubHandler("TestServer")); + }) + .Services + .AddFakeLogging() + .BuildServiceProvider(); + + var client = sp.GetRequiredService().CreateClient("test"); + + using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var collector = sp.GetFakeLogCollector(); + var record = collector.LatestRecord; + Assert.NotNull(record); + + var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); + Assert.True(string.IsNullOrEmpty(latencyInfo)); + } private static IConfigurationSection GetConfigSection(HttpClientLatencyTelemetryOptions options) { @@ -115,4 +254,17 @@ private static IConfigurationSection GetConfigSection(HttpClientLatencyTelemetry .Build() .GetSection($"{nameof(HttpClientLatencyTelemetryOptions)}"); } -} + + private sealed class ServerNameStubHandler : DelegatingHandler + { + private readonly string _serverName; + public ServerNameStubHandler(string serverName) => _serverName = serverName; + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(HttpStatusCode.OK); + response.Headers.TryAddWithoutValidation(TelemetryConstants.ServerApplicationNameHeader, _serverName); + return Task.FromResult(response); + } + } +} \ No newline at end of file From 930c40db19c8f1e5afc507c768244ea2a63146d9 Mon Sep 17 00:00:00 2001 From: Dmytro Bohdanov Date: Mon, 26 Jan 2026 16:46:25 +0100 Subject: [PATCH 2/4] fix tests --- ...ttpClientLatencyTelemetryExtensionsTest.cs | 182 ++++++++---------- 1 file changed, 81 insertions(+), 101 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs index 8195e8452a5..0748bf4e8df 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http.Diagnostics; using Microsoft.Extensions.Http.Latency.Internal; -using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -109,111 +108,99 @@ public void RequestLatencyExtensions_Add_BindsToConfigSection() Assert.NotNull(options); Assert.Equal(expectedOptions.EnableDetailedLatencyBreakdown, options.EnableDetailedLatencyBreakdown); } - - [Fact] -public async Task LatencyInfo_IsPopulated_WhenLoggerWrapsHandlersPipeline() -{ - using var sp = new ServiceCollection() - .AddLatencyContext() - .AddRedaction() - .AddHttpClientLatencyTelemetry() - .AddHttpClient("test") - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) - .ConfigureAdditionalHttpMessageHandlers((handlers, _) => - { - handlers.Add(new ServerNameStubHandler("TestServer")); - }) - .AddExtendedHttpClientLogging(wrapHandlersPipeline: true) - .Services - .AddFakeLogging() - .BuildServiceProvider(); - var client = sp.GetRequiredService().CreateClient("test"); + [Fact] + public async Task LatencyInfo_IsPopulated_WhenLoggerWrapsHandlersPipeline() + { + using var sp = new ServiceCollection() + .AddLatencyContext() + .AddRedaction() + .AddHttpClientLatencyTelemetry() + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler(() => new ServerNameStubHandler("TestServer")) + .AddExtendedHttpClientLogging(wrapHandlersPipeline: true) + .Services + .AddFakeLogging() + .BuildServiceProvider(); - using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var client = sp.GetRequiredService().CreateClient("test"); - var collector = sp.GetFakeLogCollector(); - var record = collector.LatestRecord; - Assert.NotNull(record); + using var response = await client.GetAsync("http://localhost/api"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); - Assert.False(string.IsNullOrEmpty(latencyInfo)); - Assert.StartsWith("v1.0,", latencyInfo); - Assert.Contains("TestServer", latencyInfo); -} + var collector = sp.GetFakeLogCollector(); + var record = collector.LatestRecord; + Assert.NotNull(record); -[Fact] -public async Task LatencyInfo_IsPopulated_WithConfigurationSection_AndWrapHandlersPipeline() -{ - var configSection = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary - { - { "Logging:LogBody", "true" } - }) - .Build() - .GetSection("Logging"); - - using var sp = new ServiceCollection() - .AddLatencyContext() - .AddRedaction() - .AddHttpClientLatencyTelemetry() // ← Move here - .AddHttpClient("test") - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) - .ConfigureAdditionalHttpMessageHandlers((handlers, _) => + var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); + Assert.False(string.IsNullOrEmpty(latencyInfo)); + Assert.StartsWith("v1.0,", latencyInfo); + Assert.Contains("TestServer", latencyInfo); + } + + [Fact] + public async Task LatencyInfo_IsPopulated_WithConfigurationSection_AndWrapHandlersPipeline() + { + var configSection = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { - handlers.Add(new ServerNameStubHandler("TestServer")); + { "Logging:LogBody", "true" } }) - .AddExtendedHttpClientLogging(configSection, wrapHandlersPipeline: true) - .Services - .AddFakeLogging() - .BuildServiceProvider(); + .Build() + .GetSection("Logging"); - var client = sp.GetRequiredService().CreateClient("test"); + using var sp = new ServiceCollection() + .AddLatencyContext() + .AddRedaction() + .AddHttpClientLatencyTelemetry() + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler(() => new ServerNameStubHandler("TestServer")) + .AddExtendedHttpClientLogging(configSection, wrapHandlersPipeline: true) + .Services + .AddFakeLogging() + .BuildServiceProvider(); - using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var client = sp.GetRequiredService().CreateClient("test"); - var collector = sp.GetFakeLogCollector(); - var record = collector.LatestRecord; - Assert.NotNull(record); + using var response = await client.GetAsync("http://localhost/api"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); - Assert.False(string.IsNullOrEmpty(latencyInfo)); - Assert.StartsWith("v1.0,", latencyInfo); -} + var collector = sp.GetFakeLogCollector(); + var record = collector.LatestRecord; + Assert.NotNull(record); -[Fact] -public async Task LatencyInfo_IsPopulated_WithActionConfiguration_AndWrapHandlersPipeline() -{ - using var sp = new ServiceCollection() - .AddLatencyContext() - .AddRedaction() - .AddHttpClientLatencyTelemetry() // ← Move here - .AddHttpClient("test") - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) - .ConfigureAdditionalHttpMessageHandlers((handlers, _) => - { - handlers.Add(new ServerNameStubHandler("TestServer")); - }) - .AddExtendedHttpClientLogging(o => o.LogBody = true, wrapHandlersPipeline: false) - .Services - .AddFakeLogging() - .BuildServiceProvider(); + var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); + Assert.False(string.IsNullOrEmpty(latencyInfo)); + Assert.StartsWith("v1.0,", latencyInfo); + } + + [Fact] + public async Task LatencyInfo_IsPopulated_WithActionConfiguration_AndWrapHandlersPipeline() + { + using var sp = new ServiceCollection() + .AddLatencyContext() + .AddRedaction() + .AddHttpClientLatencyTelemetry() + .AddHttpClient("test") + .ConfigurePrimaryHttpMessageHandler(() => new ServerNameStubHandler("TestServer")) + .AddExtendedHttpClientLogging(o => o.LogBody = true, wrapHandlersPipeline: false) + .Services + .AddFakeLogging() + .BuildServiceProvider(); - var client = sp.GetRequiredService().CreateClient("test"); + var client = sp.GetRequiredService().CreateClient("test"); - using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + using var response = await client.GetAsync("http://localhost/api"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var collector = sp.GetFakeLogCollector(); - var record = collector.LatestRecord; - Assert.NotNull(record); + var collector = sp.GetFakeLogCollector(); + var record = collector.LatestRecord; + Assert.NotNull(record); - var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); - Assert.False(string.IsNullOrEmpty(latencyInfo)); - Assert.StartsWith("v1.0,", latencyInfo); -} + var latencyInfo = record.GetStructuredStateValue("LatencyInfo"); + Assert.False(string.IsNullOrEmpty(latencyInfo)); + Assert.StartsWith("v1.0,", latencyInfo); + } [Fact] public async Task LatencyInfo_IsNotPresent_WhenLatencyTelemetryNotAdded() @@ -222,18 +209,14 @@ public async Task LatencyInfo_IsNotPresent_WhenLatencyTelemetryNotAdded() .AddRedaction() .AddExtendedHttpClientLogging() .AddHttpClient("test") - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()) - .ConfigureAdditionalHttpMessageHandlers((handlers, _) => - { - handlers.Add(new ServerNameStubHandler("TestServer")); - }) + .ConfigurePrimaryHttpMessageHandler(() => new ServerNameStubHandler("TestServer")) .Services .AddFakeLogging() .BuildServiceProvider(); var client = sp.GetRequiredService().CreateClient("test"); - using var response = await client.GetAsync("http://localhost/api").ConfigureAwait(false); + using var response = await client.GetAsync("http://localhost/api"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var collector = sp.GetFakeLogCollector(); @@ -255,15 +238,12 @@ private static IConfigurationSection GetConfigSection(HttpClientLatencyTelemetry .GetSection($"{nameof(HttpClientLatencyTelemetryOptions)}"); } - private sealed class ServerNameStubHandler : DelegatingHandler + private sealed class ServerNameStubHandler(string serverName) : HttpMessageHandler { - private readonly string _serverName; - public ServerNameStubHandler(string serverName) => _serverName = serverName; - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var response = new HttpResponseMessage(HttpStatusCode.OK); - response.Headers.TryAddWithoutValidation(TelemetryConstants.ServerApplicationNameHeader, _serverName); + response.Headers.TryAddWithoutValidation(TelemetryConstants.ServerApplicationNameHeader, serverName); return Task.FromResult(response); } } From 3da8f986a24ffbe1128fb081b71526b9ce5fc18f Mon Sep 17 00:00:00 2001 From: Dmytro Bohdanov Date: Mon, 26 Jan 2026 17:17:57 +0100 Subject: [PATCH 3/4] add experimental attributes --- .../Logging/HttpClientLoggingHttpClientBuilderExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs index 39aed688375..55b938b9873 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Diagnostics/Logging/HttpClientLoggingHttpClientBuilderExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Net.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -10,6 +11,7 @@ using Microsoft.Extensions.Http.Logging.Internal; using Microsoft.Extensions.Options; using Microsoft.Extensions.Telemetry.Internal; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.DependencyInjection; @@ -53,6 +55,7 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu /// you have a way of viewing structured logs in order to view this extra information. /// /// Argument is . + [Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)] public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, bool wrapHandlersPipeline) { _ = Throw.IfNull(builder); @@ -97,6 +100,7 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu /// you have a way of viewing structured logs in order to view this extra information. /// /// Any of the arguments is . + [Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)] public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, IConfigurationSection section, bool wrapHandlersPipeline) { _ = Throw.IfNull(builder); @@ -142,6 +146,7 @@ public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBu /// you have a way of viewing structured logs in order to view this extra information. /// /// Any of the arguments is . + [Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)] public static IHttpClientBuilder AddExtendedHttpClientLogging(this IHttpClientBuilder builder, Action configure, bool wrapHandlersPipeline) { _ = Throw.IfNull(builder); From 4018e1a65ccd5dd8ec67adc5f3131fde782a16a5 Mon Sep 17 00:00:00 2001 From: Dmytro Bohdanov <41544793+rainsxng@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:23:53 +0100 Subject: [PATCH 4/4] Update test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Latency/HttpClientLatencyTelemetryExtensionsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs index 0748bf4e8df..eb5fb5ce74e 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Diagnostics.Tests/Latency/HttpClientLatencyTelemetryExtensionsTest.cs @@ -175,7 +175,7 @@ public async Task LatencyInfo_IsPopulated_WithConfigurationSection_AndWrapHandle } [Fact] - public async Task LatencyInfo_IsPopulated_WithActionConfiguration_AndWrapHandlersPipeline() + public async Task LatencyInfo_IsPopulated_WithActionConfiguration_WhenLoggerDoesNotWrapHandlersPipeline() { using var sp = new ServiceCollection() .AddLatencyContext()