@@ -0,0 +1,78 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ROLAC.API.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Catches any unhandled exception from the downstream pipeline, logs it (which flows through
|
||||
/// the DB sink into SystemLogs at Error level with full stack + StatusCode 500), and returns a
|
||||
/// clean RFC7807 problem+json response. Stack traces are never leaked to the client outside
|
||||
/// Development. Registered as the FIRST middleware so it also catches auth/authorization faults.
|
||||
/// </summary>
|
||||
public sealed class ExceptionHandlingMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
|
||||
private readonly IHostEnvironment _env;
|
||||
|
||||
public ExceptionHandlingMiddleware(
|
||||
RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger, IHostEnvironment env)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
|
||||
{
|
||||
// The client went away — not a server error; don't log as 500.
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleAsync(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleAsync(HttpContext context, Exception exception)
|
||||
{
|
||||
// Logged here → picked up by the DB sink (Error ≥ Warning floor) with full ex.ToString().
|
||||
_logger.LogError(
|
||||
exception,
|
||||
"Unhandled exception for {Method} {Path} (traceId {TraceId})",
|
||||
context.Request.Method, context.Request.Path, context.TraceIdentifier);
|
||||
|
||||
if (context.Response.HasStarted)
|
||||
{
|
||||
// Too late to write a clean body; the log row above is still captured.
|
||||
return;
|
||||
}
|
||||
|
||||
var problem = new ProblemDetails
|
||||
{
|
||||
Status = StatusCodes.Status500InternalServerError,
|
||||
Title = "An unexpected error occurred.",
|
||||
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
|
||||
};
|
||||
problem.Extensions["traceId"] = context.TraceIdentifier;
|
||||
if (_env.IsDevelopment())
|
||||
problem.Detail = exception.ToString();
|
||||
|
||||
context.Response.Clear();
|
||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
context.Response.ContentType = "application/problem+json";
|
||||
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(problem, JsonSerializerOptions));
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user