Skip to content

Commit 74b3518

Browse files
authored
Introducing new event for failed http requests #106284 (#106964)
* Introduce new event for failed http requests * remove response code, because it is not applicable * fix log level from error to information and message * fix formating * fix message template and add tests * minor fixes
1 parent 06f0b37 commit 74b3518

File tree

4 files changed

+127
-12
lines changed

4 files changed

+127
-12
lines changed

src/libraries/Microsoft.Extensions.Http/src/Logging/LogHelper.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ private static class EventIds
2525
public static readonly EventId RequestHeader = new EventId(102, "RequestHeader");
2626
public static readonly EventId ResponseHeader = new EventId(103, "ResponseHeader");
2727

28+
public static readonly EventId RequestFailed = new EventId(104, "RequestFailed");
29+
2830
public static readonly EventId PipelineStart = new EventId(100, "RequestPipelineStart");
2931
public static readonly EventId PipelineEnd = new EventId(101, "RequestPipelineEnd");
3032

3133
public static readonly EventId RequestPipelineRequestHeader = new EventId(102, "RequestPipelineRequestHeader");
3234
public static readonly EventId RequestPipelineResponseHeader = new EventId(103, "RequestPipelineResponseHeader");
35+
36+
public static readonly EventId PipelineFailed = new EventId(104, "RequestPipelineFailed");
3337
}
3438

3539
public static readonly Func<string, bool> ShouldRedactHeaderValue = (header) => true;
@@ -45,6 +49,11 @@ private static class EventIds
4549
EventIds.RequestEnd,
4650
"Received HTTP response headers after {ElapsedMilliseconds}ms - {StatusCode}");
4751

52+
private static readonly Action<ILogger, double, Exception?> _requestFailed = LoggerMessage.Define<double>(
53+
LogLevel.Information,
54+
EventIds.RequestFailed,
55+
"HTTP request failed after {ElapsedMilliseconds}ms");
56+
4857
private static readonly Func<ILogger, HttpMethod, string?, IDisposable?> _beginRequestPipelineScope = LoggerMessage.DefineScope<HttpMethod, string?>("HTTP {HttpMethod} {Uri}");
4958

5059
private static readonly Action<ILogger, HttpMethod, string?, Exception?> _requestPipelineStart = LoggerMessage.Define<HttpMethod, string?>(
@@ -57,6 +66,11 @@ private static class EventIds
5766
EventIds.PipelineEnd,
5867
"End processing HTTP request after {ElapsedMilliseconds}ms - {StatusCode}");
5968

69+
private static readonly Action<ILogger, double, Exception?> _requestPipelineFailed = LoggerMessage.Define<double>(
70+
LogLevel.Information,
71+
EventIds.PipelineFailed,
72+
"HTTP request failed after {ElapsedMilliseconds}ms");
73+
6074
private static bool GetDisableUriRedactionSettingValue()
6175
{
6276
if (AppContext.TryGetSwitch("System.Net.Http.DisableUriRedaction", out bool value))
@@ -112,6 +126,9 @@ public static void LogRequestEnd(this ILogger logger, HttpResponseMessage respon
112126
}
113127
}
114128

129+
public static void LogRequestFailed(this ILogger logger, TimeSpan duration, HttpRequestException exception) =>
130+
_requestFailed(logger, duration.TotalMilliseconds, exception);
131+
115132
public static IDisposable? BeginRequestPipelineScope(this ILogger logger, HttpRequestMessage request, out string? formattedUri)
116133
{
117134
formattedUri = GetRedactedUriString(request.RequestUri);
@@ -148,6 +165,9 @@ public static void LogRequestPipelineEnd(this ILogger logger, HttpResponseMessag
148165
}
149166
}
150167

168+
public static void LogRequestPipelineFailed(this ILogger logger, TimeSpan duration, HttpRequestException exception) =>
169+
_requestPipelineFailed(logger, duration.TotalMilliseconds, exception);
170+
151171
internal static string? GetRedactedUriString(Uri? uri)
152172
{
153173
if (uri is null)

src/libraries/Microsoft.Extensions.Http/src/Logging/LoggingHttpMessageHandler.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,25 @@ async Task<HttpResponseMessage> Core(HttpRequestMessage request, bool useAsync,
5959
// not really anything to surround.
6060
_logger.LogRequestStart(request, shouldRedactHeaderValue);
6161
var stopwatch = ValueStopwatch.StartNew();
62-
HttpResponseMessage response = useAsync
63-
? await base.SendAsync(request, cancellationToken).ConfigureAwait(false)
62+
63+
try
64+
{
65+
HttpResponseMessage response = useAsync
66+
? await base.SendAsync(request, cancellationToken).ConfigureAwait(false)
6467
#if NET
65-
: base.Send(request, cancellationToken);
68+
: base.Send(request, cancellationToken);
6669
#else
67-
: throw new NotImplementedException("Unreachable code");
70+
: throw new NotImplementedException("Unreachable code");
6871
#endif
69-
_logger.LogRequestEnd(response, stopwatch.GetElapsedTime(), shouldRedactHeaderValue);
72+
_logger.LogRequestEnd(response, stopwatch.GetElapsedTime(), shouldRedactHeaderValue);
7073

71-
return response;
74+
return response;
75+
}
76+
catch (HttpRequestException ex)
77+
{
78+
_logger.LogRequestFailed(stopwatch.GetElapsedTime(), ex);
79+
throw;
80+
}
7281
}
7382
}
7483

src/libraries/Microsoft.Extensions.Http/src/Logging/LoggingScopeHttpMessageHandler.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,25 @@ async Task<HttpResponseMessage> Core(HttpRequestMessage request, bool useAsync,
5959
using (_logger.BeginRequestPipelineScope(request, out string? formattedUri))
6060
{
6161
_logger.LogRequestPipelineStart(request, formattedUri, shouldRedactHeaderValue);
62-
HttpResponseMessage response = useAsync
63-
? await base.SendAsync(request, cancellationToken).ConfigureAwait(false)
62+
63+
try
64+
{
65+
HttpResponseMessage response = useAsync
66+
? await base.SendAsync(request, cancellationToken).ConfigureAwait(false)
6467
#if NET
65-
: base.Send(request, cancellationToken);
68+
: base.Send(request, cancellationToken);
6669
#else
67-
: throw new NotImplementedException("Unreachable code");
70+
: throw new NotImplementedException("Unreachable code");
6871
#endif
69-
_logger.LogRequestPipelineEnd(response, stopwatch.GetElapsedTime(), shouldRedactHeaderValue);
72+
_logger.LogRequestPipelineEnd(response, stopwatch.GetElapsedTime(), shouldRedactHeaderValue);
7073

71-
return response;
74+
return response;
75+
}
76+
catch (HttpRequestException ex)
77+
{
78+
_logger.LogRequestPipelineFailed(stopwatch.GetElapsedTime(), ex);
79+
throw;
80+
}
7281
}
7382
}
7483
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Linq;
5+
using System.Net.Http;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.Extensions.Logging.Testing;
11+
using Xunit;
12+
13+
namespace Microsoft.Extensions.Http.Tests.Logging
14+
{
15+
public class FailedHttpRequestLoggingTests
16+
{
17+
private const string OuterLoggerName = "System.Net.Http.HttpClient.test.LogicalHandler";
18+
private const string InnerLoggerName = "System.Net.Http.HttpClient.test.ClientHandler";
19+
20+
private static class EventIds
21+
{
22+
public static readonly EventId RequestFailed = new EventId(104, "RequestFailed");
23+
public static readonly EventId PipelineFailed = new EventId(104, "RequestPipelineFailed");
24+
}
25+
26+
[Fact]
27+
public async Task FailedHttpRequestLogged()
28+
{
29+
// Arrange
30+
var sink = new TestSink();
31+
32+
var serviceCollection = new ServiceCollection();
33+
serviceCollection.AddLogging();
34+
serviceCollection.AddSingleton<ILoggerFactory>(new TestLoggerFactory(sink, enabled: true));
35+
36+
// Act
37+
serviceCollection
38+
.AddHttpClient("test")
39+
.ConfigurePrimaryHttpMessageHandler(() => new FailMessageHandler());
40+
41+
// Assert
42+
var services = serviceCollection.BuildServiceProvider();
43+
44+
var client = services.GetRequiredService<IHttpClientFactory>().CreateClient("test");
45+
46+
var request = new HttpRequestMessage(HttpMethod.Get, "http://example.com");
47+
48+
await Assert.ThrowsAsync<HttpRequestException>(async () =>
49+
await client.SendAsync(request));
50+
51+
var messages = sink.Writes.ToArray();
52+
53+
var requestFailedMessage = Assert.Single(messages.Where(m =>
54+
{
55+
return
56+
m.EventId == EventIds.RequestFailed &&
57+
m.LoggerName == InnerLoggerName;
58+
}));
59+
var pipelineFailedMessage = Assert.Single(messages.Where(m =>
60+
{
61+
return
62+
m.EventId == EventIds.PipelineFailed &&
63+
m.LoggerName == OuterLoggerName;
64+
}));
65+
}
66+
67+
private const string ExceptionMessage = "Dummy error message";
68+
69+
private class FailMessageHandler : HttpClientHandler
70+
{
71+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
72+
{
73+
throw new HttpRequestException(ExceptionMessage);
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)