Skip to content

Commit 2b365b3

Browse files
committed
WIP
1 parent 12f24bb commit 2b365b3

File tree

5 files changed

+76
-24
lines changed

5 files changed

+76
-24
lines changed

src/Middleware/Diagnostics/src/DeveloperExceptionPage/DeveloperExceptionPageMiddlewareImpl.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public async Task Invoke(HttpContext context)
137137
return;
138138
}
139139

140+
_logger.UnhandledException(ex);
141+
140142
if (context.Response.HasStarted)
141143
{
142144
_logger.ResponseStartedErrorPageMiddleware();

src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddlewareImpl.cs

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -170,55 +170,67 @@ private async Task HandleException(HttpContext context, ExceptionDispatchInfo ed
170170
context.Response.StatusCode = _options.StatusCodeSelector?.Invoke(edi.SourceException) ?? DefaultStatusCode;
171171
context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
172172

173-
string? handler = null;
174-
var handled = false;
173+
string? handlerTag = null;
174+
var handler = Handler.None;
175175
foreach (var exceptionHandler in _exceptionHandlers)
176176
{
177-
handled = await exceptionHandler.TryHandleAsync(context, edi.SourceException, context.RequestAborted);
178-
if (handled)
177+
if (await exceptionHandler.TryHandleAsync(context, edi.SourceException, context.RequestAborted))
179178
{
180-
handler = exceptionHandler.GetType().FullName;
179+
handler = Handler.IExceptionHandler;
180+
handlerTag = exceptionHandler.GetType().FullName;
181181
break;
182182
}
183183
}
184184

185-
if (!handled)
185+
if (handler == Handler.None)
186186
{
187187
if (_options.ExceptionHandler is not null)
188188
{
189189
await _options.ExceptionHandler!(context);
190+
191+
// If the response has started, assume exception handler was successful.
192+
if (context.Response.HasStarted)
193+
{
194+
handler = Handler.ExceptionHandler;
195+
if (_options.ExceptionHandlingPath.HasValue)
196+
{
197+
handlerTag = _options.ExceptionHandlingPath.Value;
198+
}
199+
}
190200
}
191201
else
192202
{
193-
handled = await _problemDetailsService!.TryWriteAsync(new()
203+
if (await _problemDetailsService!.TryWriteAsync(new()
194204
{
195205
HttpContext = context,
196206
AdditionalMetadata = exceptionHandlerFeature.Endpoint?.Metadata,
197207
ProblemDetails = { Status = context.Response.StatusCode },
198208
Exception = edi.SourceException,
199-
});
200-
if (handled)
209+
}))
201210
{
202-
handler = _problemDetailsService.GetType().FullName;
211+
handler = Handler.IProblemDetailsService;
212+
handlerTag = _problemDetailsService.GetType().FullName;
203213
}
204214
}
205215
}
206216

207-
// If the response has already started, assume exception handler was successful.
208-
if (context.Response.HasStarted || handled || _options.StatusCodeSelector != null || context.Response.StatusCode != StatusCodes.Status404NotFound || _options.AllowStatusCode404Response)
217+
if (handler != Handler.None || _options.StatusCodeSelector != null || context.Response.StatusCode != StatusCodes.Status404NotFound || _options.AllowStatusCode404Response)
209218
{
210-
const string eventName = "Microsoft.AspNetCore.Diagnostics.HandledException";
211-
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(eventName))
219+
// Customers with an IExceptionHandler that reports it handled the exception could do their own logging.
220+
// Don't log IExceptionHandler handled exceptions if SuppressLoggingIExceptionHandler is set to true.
221+
// Note: Microsoft.AspNetCore.Diagnostics.HandledException is used by AppInsights to log errors so it's also skipped.
222+
if (handler != Handler.IExceptionHandler || !_options.SuppressLoggingIExceptionHandler)
212223
{
213-
WriteDiagnosticEvent(_diagnosticListener, eventName, new { httpContext = context, exception = edi.SourceException });
214-
}
224+
const string eventName = "Microsoft.AspNetCore.Diagnostics.HandledException";
225+
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(eventName))
226+
{
227+
WriteDiagnosticEvent(_diagnosticListener, eventName, new { httpContext = context, exception = edi.SourceException });
228+
}
215229

216-
if (!_options.SuppressLoggingOnHandledException)
217-
{
218230
_logger.UnhandledException(edi.SourceException);
219231
}
220232

221-
_metrics.RequestException(exceptionName, ExceptionResult.Handled, handler);
233+
_metrics.RequestException(exceptionName, ExceptionResult.Handled, handlerTag);
222234
return;
223235
}
224236

@@ -273,4 +285,12 @@ private static Task ClearCacheHeaders(object state)
273285
headers.ETag = default;
274286
return Task.CompletedTask;
275287
}
288+
289+
private enum Handler
290+
{
291+
None,
292+
IExceptionHandler,
293+
IProblemDetailsService,
294+
ExceptionHandler
295+
}
276296
}

src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerOptions.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ public class ExceptionHandlerOptions
4949

5050
/// <summary>
5151
/// Gets or sets a value that determines if the exception handler middleware should suppress logging
52-
/// if the exception was handled by either <see cref="ExceptionHandler"/>, <see cref="IProblemDetailsService"/> or
53-
/// <see cref="IExceptionHandler"/> registered in the DI container.
52+
/// if the exception was handled by a <see cref="IExceptionHandler"/> registered in the DI container.
5453
/// </summary>
55-
public bool SuppressLoggingOnHandledException { get; set; }
54+
public bool SuppressLoggingIExceptionHandler { get; set; }
5655
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Builder.ExceptionHandlerOptions.SuppressLoggingOnHandledException.get -> bool
3-
Microsoft.AspNetCore.Builder.ExceptionHandlerOptions.SuppressLoggingOnHandledException.set -> void
2+
Microsoft.AspNetCore.Builder.ExceptionHandlerOptions.SuppressLoggingIExceptionHandler.get -> bool
3+
Microsoft.AspNetCore.Builder.ExceptionHandlerOptions.SuppressLoggingIExceptionHandler.set -> void

src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerMiddlewareTest.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,32 @@ public async Task Metrics_ExceptionThrown_Handled_Reported()
247247
m => AssertRequestException(m, "System.InvalidOperationException", "handled", typeof(TestExceptionHandler).FullName));
248248
}
249249

250+
[Fact]
251+
public async Task Metrics_ExceptionThrown_ErrorPathHandled_Reported()
252+
{
253+
// Arrange
254+
var httpContext = CreateHttpContext();
255+
var optionsAccessor = CreateOptionsAccessor(
256+
exceptionHandler: context =>
257+
{
258+
context.Features.Set<IHttpResponseFeature>(new TestHttpResponseFeature());
259+
return Task.CompletedTask;
260+
},
261+
exceptionHandlingPath: "/error");
262+
var meterFactory = new TestMeterFactory();
263+
var middleware = CreateMiddleware(_ => throw new InvalidOperationException(), optionsAccessor, meterFactory: meterFactory);
264+
var meter = meterFactory.Meters.Single();
265+
266+
using var diagnosticsRequestExceptionCollector = new MetricCollector<long>(meterFactory, DiagnosticsMetrics.MeterName, "aspnetcore.diagnostics.exceptions");
267+
268+
// Act
269+
await middleware.Invoke(httpContext);
270+
271+
// Assert
272+
Assert.Collection(diagnosticsRequestExceptionCollector.GetMeasurementSnapshot(),
273+
m => AssertRequestException(m, "System.InvalidOperationException", "handled", "/error"));
274+
}
275+
250276
[Fact]
251277
public async Task Metrics_ExceptionThrown_ResponseStarted_Skipped()
252278
{
@@ -529,4 +555,9 @@ public object GetService(Type serviceType)
529555
throw new NotImplementedException();
530556
}
531557
}
558+
559+
private class TestHttpResponseFeature : HttpResponseFeature
560+
{
561+
public override bool HasStarted => true;
562+
}
532563
}

0 commit comments

Comments
 (0)