Fixed: IExceptionHandler Exceptions Aren't Logged in ASP.NET Core 10
My IExceptionHandler turned database errors into a 503, and the client got exactly what it should. Then I looked at the log for the error it had handled, and the middleware's line wasn't there. That's a change in .NET 10, and one option brings the line back.
What I Was Doing
My .NET C# Web API runs on ASP.NET Core 10. An exception handler answers 503 when the database can't do something right now:
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
if (!IsTemporary(exception))
{
return false;
}
It's registered in Program.cs, along with the middleware:
builder.Services.AddExceptionHandler<DatabaseUnavailableExceptionHandler>();
An order then waited too long for a row lock.
What I Expected
A 503 for the client, and in the log an An unhandled exception has occurred while executing the request. line with the stack, as I'd seen for every other exception.
What I Got
The 503 was right:
HTTP/1.1 503 Service Unavailable
Content-Type: application/problem+json
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.4",
"title": "The database can't do this right now.",
"status": 503,
"detail": "Try again in 5 seconds. Send the same Idempotency-Key, so a change can't be applied twice.",
"traceId": "00-be9c3937ef6ae66c81c2ae2a3339273f-670d2b6b06441546-00"
}
The log had only EF Core's line about the failed command, and nothing from the middleware:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
![The API log showing only fail: Microsoft.EntityFrameworkCore.Database.Command[20102], with no line from ExceptionHandlerMiddleware.](https://strdewiridedevblogsprod.blob.core.windows.net/images/blog/programming/csharp/fixes/2026/fixed-exception-handler-log-01-error.webp)
An exception that doesn't come from a database command would leave no error in the log at all.
What I Checked
The handler's result. TryHandleAsync had returned true, because the response was the handler's 503.
The docs. It's a documented breaking change: "Starting in .NET 10, if IExceptionHandler.TryHandleAsync returns true, then exception diagnostics are no longer recorded by default." So it only affects the exceptions a handler has answered.
Why It Happens
Microsoft's reasoning, on the same page: "ASP.NET Core now follows the behavior expected by users by suppressing diagnostics when IExceptionHandler handles the exception." For an expected error that you've fully dealt with, that's reasonable. For an outage answered with a 503, I want it in the log.
The Fix
Set SuppressDiagnosticsCallback, as the page shows. It's asked for each handled exception, and returning false keeps the diagnostics:
app.UseExceptionHandler(new ExceptionHandlerOptions
{
// Keep logging the exceptions an IExceptionHandler answers, so an outage shows up in the logs.
SuppressDiagnosticsCallback = _ => false,
});
After the Fix
The same 503, and now the log has the middleware's line:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
An unhandled exception has occurred while executing the request.
MySql.Data.MySqlClient.MySqlException (0x80004005): Lock wait timeout exceeded; try restarting transaction
![The API log now also showing fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1] with An unhandled exception has occurred while executing the request.](https://strdewiridedevblogsprod.blob.core.windows.net/images/blog/programming/csharp/fixes/2026/fixed-exception-handler-log-02-fixed.webp)
Common Mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| Logging by hand in every handler | It works, and you lose the middleware's request details | Use the callback |
Returning false from TryHandleAsync to get the log | The client gets a 500 | Return true, and use the callback |
| Assuming no errors in the log means no errors | Handled ones are hidden | Check after upgrading to .NET 10 |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
SuppressDiagnosticsCallback doesn't exist | The app isn't on .NET 10 | Earlier versions log by default |
| Some handled exceptions should stay quiet | The callback returns false for all | Decide from its context argument |
| The handler never runs | No UseExceptionHandler, or it's registered too late | How to Write an IExceptionHandler in ASP.NET Core |
Conclusion
Since .NET 10, an exception handled by an IExceptionHandler leaves no diagnostics from the middleware. If you want them, set SuppressDiagnosticsCallback = _ => false.
Additional Resources
- Exception diagnostics are suppressed when IExceptionHandler.TryHandleAsync returns true — the breaking change.
- Handle errors in ASP.NET Core APIs — exception handlers.
