2222using Microsoft . Extensions . Hosting ;
2323using Microsoft . Extensions . Logging ;
2424using Microsoft . Extensions . Logging . Abstractions ;
25+ using Microsoft . Extensions . Logging . Testing ;
2526using Microsoft . Extensions . Options ;
2627using Moq ;
2728
@@ -81,6 +82,7 @@ public async Task ExceptionIsSetOnProblemDetailsContext()
8182 public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint ( )
8283 {
8384 // Arrange
85+ var sink = new TestSink ( ) ;
8486 var httpContext = CreateHttpContext ( ) ;
8587 httpContext . SetEndpoint ( new Endpoint ( ( _ ) => Task . CompletedTask , new EndpointMetadataCollection ( ) , "Test" ) ) ;
8688 httpContext . Request . RouteValues [ "John" ] = "Doe" ;
@@ -92,10 +94,39 @@ public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint()
9294 Assert . Null ( context . GetEndpoint ( ) ) ;
9395 return Task . CompletedTask ;
9496 } ) ;
95- var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor ) ;
97+ var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , loggerFactory : new TestLoggerFactory ( sink , true ) ) ;
9698
9799 // Act & Assert
98100 await middleware . Invoke ( httpContext ) ;
101+
102+ Assert . Collection ( sink . Writes , w => Assert . Equal ( "UnhandledException" , w . EventId . Name ) ) ;
103+ }
104+
105+ [ Fact ]
106+ public async Task Invoke_HasExceptionHandler_SuppressIExceptionHandlerLogging_HasLogs ( )
107+ {
108+ // Arrange
109+ var sink = new TestSink ( ) ;
110+ var httpContext = CreateHttpContext ( ) ;
111+
112+ var optionsAccessor = CreateOptionsAccessor (
113+ exceptionHandler : context =>
114+ {
115+ context . Features . Set < IHttpResponseFeature > ( new TestHttpResponseFeature ( ) ) ;
116+ return Task . CompletedTask ;
117+ } ,
118+ suppressLoggingHandlingException : true ) ;
119+ var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , loggerFactory : new TestLoggerFactory ( sink , true ) ) ;
120+
121+ // Act & Assert
122+ await middleware . Invoke ( httpContext ) ;
123+
124+ Assert . Collection ( sink . Writes , w => Assert . Equal ( "UnhandledException" , w . EventId . Name ) ) ;
125+ }
126+
127+ private sealed class TestHttpResponseFeature : HttpResponseFeature
128+ {
129+ public override bool HasStarted => true ;
99130 }
100131
101132 [ Fact ]
@@ -126,6 +157,7 @@ public async Task Invoke_ExceptionHandlerCaptureRouteValuesAndEndpoint()
126157 public async Task IExceptionHandlers_CallNextIfNotHandled ( )
127158 {
128159 // Arrange
160+ var sink = new TestSink ( ) ;
129161 var httpContext = CreateHttpContext ( ) ;
130162
131163 var optionsAccessor = CreateOptionsAccessor ( ) ;
@@ -137,14 +169,49 @@ public async Task IExceptionHandlers_CallNextIfNotHandled()
137169 new TestExceptionHandler ( true , "3" ) ,
138170 } ;
139171
140- var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , exceptionHandlers ) ;
172+ var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , exceptionHandlers , loggerFactory : new TestLoggerFactory ( sink , true ) ) ;
141173
142174 // Act & Assert
143175 await middleware . Invoke ( httpContext ) ;
144176
145177 Assert . True ( httpContext . Items . ContainsKey ( "1" ) ) ;
146178 Assert . True ( httpContext . Items . ContainsKey ( "2" ) ) ;
147179 Assert . True ( httpContext . Items . ContainsKey ( "3" ) ) ;
180+
181+ Assert . Collection ( sink . Writes , w => Assert . Equal ( "UnhandledException" , w . EventId . Name ) ) ;
182+ }
183+
184+ [ Theory ]
185+ [ InlineData ( true ) ]
186+ [ InlineData ( false ) ]
187+ public async Task IExceptionHandlers_SuppressLogging_TestLogs ( bool suppressedLogs )
188+ {
189+ // Arrange
190+ var sink = new TestSink ( ) ;
191+ var httpContext = CreateHttpContext ( ) ;
192+
193+ var optionsAccessor = CreateOptionsAccessor ( suppressLoggingHandlingException : suppressedLogs ) ;
194+
195+ var exceptionHandlers = new List < IExceptionHandler >
196+ {
197+ new TestExceptionHandler ( true , "1" )
198+ } ;
199+
200+ var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , exceptionHandlers , loggerFactory : new TestLoggerFactory ( sink , true ) ) ;
201+
202+ // Act & Assert
203+ await middleware . Invoke ( httpContext ) ;
204+
205+ Assert . True ( httpContext . Items . ContainsKey ( "1" ) ) ;
206+
207+ if ( suppressedLogs )
208+ {
209+ Assert . Empty ( sink . Writes ) ;
210+ }
211+ else
212+ {
213+ Assert . Collection ( sink . Writes , w => Assert . Equal ( "UnhandledException" , w . EventId . Name ) ) ;
214+ }
148215 }
149216
150217 [ Fact ]
@@ -247,32 +314,6 @@ public async Task Metrics_ExceptionThrown_Handled_Reported()
247314 m => AssertRequestException ( m , "System.InvalidOperationException" , "handled" , typeof ( TestExceptionHandler ) . FullName ) ) ;
248315 }
249316
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-
276317 [ Fact ]
277318 public async Task Metrics_ExceptionThrown_ResponseStarted_Skipped ( )
278319 {
@@ -471,6 +512,32 @@ public async Task Metrics_ExceptionThrown_Unhandled_Reported()
471512 m => AssertRequestException ( m , "System.InvalidOperationException" , "unhandled" ) ) ;
472513 }
473514
515+ [ Fact ]
516+ public async Task Metrics_ExceptionThrown_ErrorPathHandled_Reported ( )
517+ {
518+ // Arrange
519+ var httpContext = CreateHttpContext ( ) ;
520+ var optionsAccessor = CreateOptionsAccessor (
521+ exceptionHandler : context =>
522+ {
523+ context . Features . Set < IHttpResponseFeature > ( new TestHttpResponseFeature ( ) ) ;
524+ return Task . CompletedTask ;
525+ } ,
526+ exceptionHandlingPath : "/error" ) ;
527+ var meterFactory = new TestMeterFactory ( ) ;
528+ var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , meterFactory : meterFactory ) ;
529+ var meter = meterFactory . Meters . Single ( ) ;
530+
531+ using var diagnosticsRequestExceptionCollector = new MetricCollector < long > ( meterFactory , DiagnosticsMetrics . MeterName , "aspnetcore.diagnostics.exceptions" ) ;
532+
533+ // Act
534+ await middleware . Invoke ( httpContext ) ;
535+
536+ // Assert
537+ Assert . Collection ( diagnosticsRequestExceptionCollector . GetMeasurementSnapshot ( ) ,
538+ m => AssertRequestException ( m , "System.InvalidOperationException" , "handled" , "/error" ) ) ;
539+ }
540+
474541 private static void AssertRequestException ( CollectedMeasurement < long > measurement , string exceptionName , string result , string handler = null )
475542 {
476543 Assert . Equal ( 1 , measurement . Value ) ;
@@ -516,14 +583,19 @@ private HttpContext CreateHttpContext()
516583
517584 private IOptions < ExceptionHandlerOptions > CreateOptionsAccessor (
518585 RequestDelegate exceptionHandler = null ,
519- string exceptionHandlingPath = null )
586+ string exceptionHandlingPath = null ,
587+ bool ? suppressLoggingHandlingException = null )
520588 {
521589 exceptionHandler ??= c => Task . CompletedTask ;
522590 var options = new ExceptionHandlerOptions ( )
523591 {
524592 ExceptionHandler = exceptionHandler ,
525593 ExceptionHandlingPath = exceptionHandlingPath ,
526594 } ;
595+ if ( suppressLoggingHandlingException != null )
596+ {
597+ options . SuppressLoggingIExceptionHandler = suppressLoggingHandlingException . Value ;
598+ }
527599 var optionsAccessor = Mock . Of < IOptions < ExceptionHandlerOptions > > ( o => o . Value == options ) ;
528600 return optionsAccessor ;
529601 }
@@ -532,14 +604,15 @@ private ExceptionHandlerMiddlewareImpl CreateMiddleware(
532604 RequestDelegate next ,
533605 IOptions < ExceptionHandlerOptions > options ,
534606 IEnumerable < IExceptionHandler > exceptionHandlers = null ,
535- IMeterFactory meterFactory = null )
607+ IMeterFactory meterFactory = null ,
608+ ILoggerFactory loggerFactory = null )
536609 {
537610 next ??= c => Task . CompletedTask ;
538611 var listener = new DiagnosticListener ( "Microsoft.AspNetCore" ) ;
539612
540613 var middleware = new ExceptionHandlerMiddlewareImpl (
541614 next ,
542- NullLoggerFactory . Instance ,
615+ loggerFactory ?? NullLoggerFactory . Instance ,
543616 options ,
544617 listener ,
545618 exceptionHandlers ?? Enumerable . Empty < IExceptionHandler > ( ) ,
@@ -555,9 +628,4 @@ public object GetService(Type serviceType)
555628 throw new NotImplementedException ( ) ;
556629 }
557630 }
558-
559- private class TestHttpResponseFeature : HttpResponseFeature
560- {
561- public override bool HasStarted => true ;
562- }
563631}
0 commit comments