ASP.NET Core 10 handled exception metrics can be confusing: you may still see a 500 response and an HTTP request-duration measurement, yet no error.type tag for an exception handled by IExceptionHandler. The missing tag can look like a broken metrics pipeline. It is a deliberate change to the default diagnostics for handled exceptions. Here is a small way to verify the behavior and decide whether to retain those diagnostics.

ASP.NET Core 10 handled exception metrics: why error.type disappears

In ASP.NET Core 10, an IExceptionHandler that returns true marks the exception as handled. By default, the exception-handler middleware suppresses its exception diagnostics for that case, including the error.type tag on the built-in http.server.request.duration metric. Microsoft documents the change for logging, EventSource events, and the metric tag. The response does not become successful: a handler can still return HTTP 500, and the request-duration measurement still exists. Do not equate a missing exception tag with a missing request or a healthy request.

The boundary matters. This behavior concerns exceptions that the middleware actually handles. An exception for which all handlers return false follows the unhandled path; an error after the response has started is another case. Diagnose those paths separately instead of applying this article’s handled-exception result to every failure.

Reproduce the behavior locally

The linked .NET 10 console-hosted web app registers a handler that returns true, calls an endpoint that throws InvalidOperationException, and listens directly to the framework’s Microsoft.AspNetCore.Hosting meter. It binds to an available loopback port and exits after one request, so it needs neither an Azure resource nor an exporter. The essential middleware choice is:

builder.Services.AddExceptionHandler<HandledExceptionHandler>();
builder.Services.AddProblemDetails();
var app = builder.Build();

if (retain)
{
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        SuppressDiagnosticsCallback = _ => false
    });
}
else
{
    app.UseExceptionHandler(); // ASP.NET Core 10 default
}

app.MapGet("/boom", () => { throw new InvalidOperationException("proof"); });

The callback returning false asks the middleware to retain exception diagnostics. The handler itself writes a 500 response and returns true. With the default middleware configuration, there is no callback override. The demo’s MeterListener records the status-code and error.type tags on http.server.request.duration; its assertions also require exactly one captured measurement.

Run and check both modes

Install a .NET 10 SDK, clone the demo, and run these commands from its root. The program starts and stops its own local server; do not start a second server or configure credentials.

dotnet run --project DncExceptionMetricsProof.csproj
dotnet run --project DncExceptionMetricsProof.csproj -- --retain

Both runs should report response=500, measurements=1, and PROOF PASS. In the default run, error.type=<none>; in the retained run, error.type=System.InvalidOperationException. A nonzero exit code or PROOF FAIL means the comparison did not hold in that environment. We verified both paths locally with the .NET 10 SDK; the demo deliberately does not test an OpenTelemetry exporter or Application Insights, so it does not prove the configuration of either downstream pipeline.

If the tag is still absent when you retain diagnostics in your own app, first check that your exception handler returns true, the callback is configured on the active exception-handler middleware, the Microsoft.AspNetCore.Hosting meter is collected, and the downstream view has not filtered or aggregated away the tag. Compare a direct local listener with your exporter before assigning the cause to ASP.NET Core. If you also maintain a direct listener while targeting .NET 11, our guide to collecting observable HTTP metrics covers a separate collection change.

Choose what to keep in production

For ASP.NET Core 10 handled exception metrics, returning false from SuppressDiagnosticsCallback restores diagnostics for the handled exceptions it covers. This includes logging, so enabling it broadly can add noise or duplicate reports if your handler already records the same failure. Keep the default when the handled outcome is expected and you have adequate application-level signals. Retain diagnostics only for exceptions whose investigation needs them, and review your logging and alert rules before rolling out a broader callback.

The local proof intentionally uses a global toggle to make the difference obvious. In a production app, evaluate the callback’s ExceptionHandlerSuppressDiagnosticsContext to make a narrower decision for your handlers and exception types. That choice is application-specific; the sample makes no claim that every handled 500 should be emitted as an exception metric. Keep tracking status codes and request duration separately from exception details.

References

Demo source code: ASP.NET Core 10 handled-exception metric proof.

Found this useful? Support more practical developer content.

Author

Practical .NET, Angular, Azure, Blazor, and AI engineering for real-world development.

Write A Comment

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
Best Wordpress Adblock Detecting Plugin | CHP Adblock