@@ -127,13 +127,12 @@ private async Task HandleException(HttpContext context, ExceptionDispatchInfo ed
127
127
return ;
128
128
}
129
129
130
- DiagnosticsTelemetry . ReportUnhandledException ( _logger , context , edi . SourceException ) ;
131
-
132
130
// We can't do anything if the response has already started, just abort.
133
131
if ( context . Response . HasStarted )
134
132
{
135
133
_logger . ResponseStartedErrorHandler ( ) ;
136
134
135
+ DiagnosticsTelemetry . ReportUnhandledException ( _logger , context , edi . SourceException ) ;
137
136
_metrics . RequestException ( exceptionName , ExceptionResult . Skipped , handler : null ) ;
138
137
edi . Throw ( ) ;
139
138
}
@@ -168,52 +167,97 @@ private async Task HandleException(HttpContext context, ExceptionDispatchInfo ed
168
167
context . Response . StatusCode = _options . StatusCodeSelector ? . Invoke ( edi . SourceException ) ?? DefaultStatusCode ;
169
168
context . Response . OnStarting ( _clearCacheHeadersDelegate , context . Response ) ;
170
169
171
- string ? handler = null ;
172
- var handled = false ;
170
+ string ? handlerTag = null ;
171
+ var result = ExceptionHandledType . Unhandled ;
173
172
foreach ( var exceptionHandler in _exceptionHandlers )
174
173
{
175
- handled = await exceptionHandler . TryHandleAsync ( context , edi . SourceException , context . RequestAborted ) ;
176
- if ( handled )
174
+ if ( await exceptionHandler . TryHandleAsync ( context , edi . SourceException , context . RequestAborted ) )
177
175
{
178
- handler = exceptionHandler . GetType ( ) . FullName ;
176
+ result = ExceptionHandledType . ExceptionHandlerService ;
177
+ handlerTag = exceptionHandler . GetType ( ) . FullName ;
179
178
break ;
180
179
}
181
180
}
182
181
183
- if ( ! handled )
182
+ if ( result == ExceptionHandledType . Unhandled )
184
183
{
185
184
if ( _options . ExceptionHandler is not null )
186
185
{
187
186
await _options . ExceptionHandler ! ( context ) ;
187
+
188
+ // If the response has started, assume exception handler was successful.
189
+ if ( context . Response . HasStarted )
190
+ {
191
+ if ( _options . ExceptionHandlingPath . HasValue )
192
+ {
193
+ result = ExceptionHandledType . ExceptionHandlingPath ;
194
+ handlerTag = _options . ExceptionHandlingPath . Value ;
195
+ }
196
+ else
197
+ {
198
+ result = ExceptionHandledType . ExceptionHandlerDelegate ;
199
+ }
200
+ }
188
201
}
189
202
else
190
203
{
191
- handled = await _problemDetailsService ! . TryWriteAsync ( new ( )
204
+ if ( await _problemDetailsService ! . TryWriteAsync ( new ( )
192
205
{
193
206
HttpContext = context ,
194
207
AdditionalMetadata = exceptionHandlerFeature . Endpoint ? . Metadata ,
195
208
ProblemDetails = { Status = context . Response . StatusCode } ,
196
209
Exception = edi . SourceException ,
197
- } ) ;
198
- if ( handled )
210
+ } ) )
199
211
{
200
- handler = _problemDetailsService . GetType ( ) . FullName ;
212
+ result = ExceptionHandledType . ProblemDetailsService ;
213
+ handlerTag = _problemDetailsService . GetType ( ) . FullName ;
201
214
}
202
215
}
203
216
}
204
- // If the response has already started, assume exception handler was successful.
205
- if ( context . Response . HasStarted || handled || _options . StatusCodeSelector != null || context . Response . StatusCode != StatusCodes . Status404NotFound || _options . AllowStatusCode404Response )
217
+
218
+ if ( result != ExceptionHandledType . Unhandled || _options . StatusCodeSelector != null || context . Response . StatusCode != StatusCodes . Status404NotFound || _options . AllowStatusCode404Response )
206
219
{
207
- const string eventName = "Microsoft.AspNetCore.Diagnostics.HandledException" ;
208
- if ( _diagnosticListener . IsEnabled ( ) && _diagnosticListener . IsEnabled ( eventName ) )
220
+ var suppressDiagnostics = false ;
221
+
222
+ // Customers may prefer to handle the exception and to do their own diagnostics.
223
+ // In that case, it can be undesirable for the middleware to log the exception at an error level.
224
+ // Run the configured callback to determine if exception diagnostics in the middleware should be suppressed.
225
+ if ( _options . SuppressDiagnosticsCallback is { } suppressCallback )
226
+ {
227
+ var suppressDiagnosticsContext = new ExceptionHandlerSuppressDiagnosticsContext
228
+ {
229
+ HttpContext = context ,
230
+ Exception = edi . SourceException ,
231
+ ExceptionHandledBy = result
232
+ } ;
233
+ suppressDiagnostics = suppressCallback ( suppressDiagnosticsContext ) ;
234
+ }
235
+ else
236
+ {
237
+ // Default behavior is to suppress diagnostics if the exception was handled by an IExceptionHandler service instance.
238
+ suppressDiagnostics = result == ExceptionHandledType . ExceptionHandlerService ;
239
+ }
240
+
241
+ if ( ! suppressDiagnostics )
209
242
{
210
- WriteDiagnosticEvent ( _diagnosticListener , eventName , new { httpContext = context , exception = edi . SourceException } ) ;
243
+ // Note: Microsoft.AspNetCore.Diagnostics.HandledException is used by AppInsights to log errors.
244
+ // The diagnostics event is run together with standard exception logging.
245
+ const string eventName = "Microsoft.AspNetCore.Diagnostics.HandledException" ;
246
+ if ( _diagnosticListener . IsEnabled ( ) && _diagnosticListener . IsEnabled ( eventName ) )
247
+ {
248
+ WriteDiagnosticEvent ( _diagnosticListener , eventName , new { httpContext = context , exception = edi . SourceException } ) ;
249
+ }
250
+
251
+ DiagnosticsTelemetry . ReportUnhandledException ( _logger , context , edi . SourceException ) ;
211
252
}
212
253
213
- _metrics . RequestException ( exceptionName , ExceptionResult . Handled , handler ) ;
254
+ _metrics . RequestException ( exceptionName , ExceptionResult . Handled , handlerTag ) ;
214
255
return ;
215
256
}
216
257
258
+ // Exception is unhandled. Record diagnostics for the unhandled exception before it is wrapped.
259
+ DiagnosticsTelemetry . ReportUnhandledException ( _logger , context , edi . SourceException ) ;
260
+
217
261
edi = ExceptionDispatchInfo . Capture ( new InvalidOperationException ( $ "The exception handler configured on { nameof ( ExceptionHandlerOptions ) } produced a 404 status response. " +
218
262
$ "This { nameof ( InvalidOperationException ) } containing the original exception was thrown since this is often due to a misconfigured { nameof ( ExceptionHandlerOptions . ExceptionHandlingPath ) } . " +
219
263
$ "If the exception handler is expected to return 404 status responses then set { nameof ( ExceptionHandlerOptions . AllowStatusCode404Response ) } to true.", edi . SourceException ) ) ;
@@ -222,6 +266,9 @@ private async Task HandleException(HttpContext context, ExceptionDispatchInfo ed
222
266
{
223
267
// Suppress secondary exceptions, re-throw the original.
224
268
_logger . ErrorHandlerException ( ex2 ) ;
269
+
270
+ // There was an error handling the exception. Log original unhandled exception.
271
+ DiagnosticsTelemetry . ReportUnhandledException ( _logger , context , edi . SourceException ) ;
225
272
}
226
273
finally
227
274
{
0 commit comments