22
22
using Microsoft . Extensions . Hosting ;
23
23
using Microsoft . Extensions . Logging ;
24
24
using Microsoft . Extensions . Logging . Abstractions ;
25
+ using Microsoft . Extensions . Logging . Testing ;
25
26
using Microsoft . Extensions . Options ;
26
27
using Moq ;
27
28
@@ -81,6 +82,7 @@ public async Task ExceptionIsSetOnProblemDetailsContext()
81
82
public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint ( )
82
83
{
83
84
// Arrange
85
+ var sink = new TestSink ( ) ;
84
86
var httpContext = CreateHttpContext ( ) ;
85
87
httpContext . SetEndpoint ( new Endpoint ( ( _ ) => Task . CompletedTask , new EndpointMetadataCollection ( ) , "Test" ) ) ;
86
88
httpContext . Request . RouteValues [ "John" ] = "Doe" ;
@@ -92,10 +94,39 @@ public async Task Invoke_ExceptionThrownResultsInClearedRouteValuesAndEndpoint()
92
94
Assert . Null ( context . GetEndpoint ( ) ) ;
93
95
return Task . CompletedTask ;
94
96
} ) ;
95
- var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor ) ;
97
+ var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , loggerFactory : new TestLoggerFactory ( sink , true ) ) ;
96
98
97
99
// Act & Assert
98
100
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 ;
99
130
}
100
131
101
132
[ Fact ]
@@ -126,6 +157,7 @@ public async Task Invoke_ExceptionHandlerCaptureRouteValuesAndEndpoint()
126
157
public async Task IExceptionHandlers_CallNextIfNotHandled ( )
127
158
{
128
159
// Arrange
160
+ var sink = new TestSink ( ) ;
129
161
var httpContext = CreateHttpContext ( ) ;
130
162
131
163
var optionsAccessor = CreateOptionsAccessor ( ) ;
@@ -137,14 +169,49 @@ public async Task IExceptionHandlers_CallNextIfNotHandled()
137
169
new TestExceptionHandler ( true , "3" ) ,
138
170
} ;
139
171
140
- var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , exceptionHandlers ) ;
172
+ var middleware = CreateMiddleware ( _ => throw new InvalidOperationException ( ) , optionsAccessor , exceptionHandlers , loggerFactory : new TestLoggerFactory ( sink , true ) ) ;
141
173
142
174
// Act & Assert
143
175
await middleware . Invoke ( httpContext ) ;
144
176
145
177
Assert . True ( httpContext . Items . ContainsKey ( "1" ) ) ;
146
178
Assert . True ( httpContext . Items . ContainsKey ( "2" ) ) ;
147
179
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
+ }
148
215
}
149
216
150
217
[ Fact ]
@@ -445,6 +512,32 @@ public async Task Metrics_ExceptionThrown_Unhandled_Reported()
445
512
m => AssertRequestException ( m , "System.InvalidOperationException" , "unhandled" ) ) ;
446
513
}
447
514
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
+
448
541
private static void AssertRequestException ( CollectedMeasurement < long > measurement , string exceptionName , string result , string handler = null )
449
542
{
450
543
Assert . Equal ( 1 , measurement . Value ) ;
@@ -490,14 +583,19 @@ private HttpContext CreateHttpContext()
490
583
491
584
private IOptions < ExceptionHandlerOptions > CreateOptionsAccessor (
492
585
RequestDelegate exceptionHandler = null ,
493
- string exceptionHandlingPath = null )
586
+ string exceptionHandlingPath = null ,
587
+ bool ? suppressLoggingHandlingException = null )
494
588
{
495
589
exceptionHandler ??= c => Task . CompletedTask ;
496
590
var options = new ExceptionHandlerOptions ( )
497
591
{
498
592
ExceptionHandler = exceptionHandler ,
499
593
ExceptionHandlingPath = exceptionHandlingPath ,
500
594
} ;
595
+ if ( suppressLoggingHandlingException != null )
596
+ {
597
+ options . SuppressLoggingIExceptionHandler = suppressLoggingHandlingException . Value ;
598
+ }
501
599
var optionsAccessor = Mock . Of < IOptions < ExceptionHandlerOptions > > ( o => o . Value == options ) ;
502
600
return optionsAccessor ;
503
601
}
@@ -506,14 +604,15 @@ private ExceptionHandlerMiddlewareImpl CreateMiddleware(
506
604
RequestDelegate next ,
507
605
IOptions < ExceptionHandlerOptions > options ,
508
606
IEnumerable < IExceptionHandler > exceptionHandlers = null ,
509
- IMeterFactory meterFactory = null )
607
+ IMeterFactory meterFactory = null ,
608
+ ILoggerFactory loggerFactory = null )
510
609
{
511
610
next ??= c => Task . CompletedTask ;
512
611
var listener = new DiagnosticListener ( "Microsoft.AspNetCore" ) ;
513
612
514
613
var middleware = new ExceptionHandlerMiddlewareImpl (
515
614
next ,
516
- NullLoggerFactory . Instance ,
615
+ loggerFactory ?? NullLoggerFactory . Instance ,
517
616
options ,
518
617
listener ,
519
618
exceptionHandlers ?? Enumerable . Empty < IExceptionHandler > ( ) ,
0 commit comments