using System.Text.Json; using Microsoft.AspNetCore.Mvc; namespace ROLAC.API.Middleware; /// /// 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. /// public sealed class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IHostEnvironment _env; public ExceptionHandlingMiddleware( RequestDelegate next, ILogger 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, }; }