Skip to main content

Fixed: IExceptionHandler Exceptions Aren't Logged in ASP.NET Core 10

· 4 min read
Jagdish Kumawat
Founder @ Dewiride

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:

src/Dewiride.Inventory.Api/Infrastructure/DatabaseUnavailableExceptionHandler.cs (excerpt)
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:

src/Dewiride.Inventory.Api/Program.cs (excerpt)
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:

Response
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:

API log (the errors)
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]

The API log showing only fail: Microsoft.EntityFrameworkCore.Database.Command[20102], with no line from ExceptionHandlerMiddleware.

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:

src/Dewiride.Inventory.Api/Program.cs (excerpt)
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:

API log (the errors)
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.

Common Mistakes​

MistakeWhat happensDo this instead
Logging by hand in every handlerIt works, and you lose the middleware's request detailsUse the callback
Returning false from TryHandleAsync to get the logThe client gets a 500Return true, and use the callback
Assuming no errors in the log means no errorsHandled ones are hiddenCheck after upgrading to .NET 10

Troubleshooting​

SymptomLikely causeFix
SuppressDiagnosticsCallback doesn't existThe app isn't on .NET 10Earlier versions log by default
Some handled exceptions should stay quietThe callback returns false for allDecide from its context argument
The handler never runsNo UseExceptionHandler, or it's registered too lateHow 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​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.