< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Security.ServerErrorLoggingFilter
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Security/ServerErrorLoggingFilter.cs
Line coverage
100%
Covered lines: 12
Uncovered lines: 0
Coverable lines: 12
Total lines: 64
Line coverage: 100%
Branch coverage
68%
Covered branches: 11
Total branches: 16
Branch coverage: 68.7%
Method coverage
100%
Covered methods: 3
Fully covered methods: 3
Total methods: 3
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
OnResultExecuting(...)100%11100%
OnResultExecuted(...)68.75%1616100%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Security/ServerErrorLoggingFilter.cs

#LineLine coverage
 1using System.Diagnostics;
 2using Microsoft.AspNetCore.Mvc.Filters;
 3using Microsoft.Extensions.Logging;
 4
 5namespace ArturRios.Heimdall.WebApi.Security;
 6
 7/// <summary>
 8///     Writes one log line for every 5xx response an action produces — at Error, with the route
 9///     template, the status code and the trace id (#227).
 10/// </summary>
 11/// <remarks>
 12///     <para>
 13///         Handlers return persistence and other server faults to the client as a message and a status
 14///         code, and nothing else wrote them down: the response said "failed" and the log said nothing,
 15///         so the cause of a 500 could not be found from the trace id the caller held. A result filter
 16///         sees every such response whatever produced it, so no handler has to remember to log.
 17///     </para>
 18///     <para>
 19///         An exception that escapes the action never reaches a result filter: <c>ExceptionMiddleware</c>
 20///         catches it and already logs it at Error with its stack trace, inside the request's activity,
 21///         so this filter does not log it a second time. The two together leave one line per 5xx.
 22///     </para>
 23///     <para>
 24///         The route is the template (<c>api/person/{publicId}</c>), never the raw path, and the line
 25///         carries no query string, body, header or caller identity: ids and addresses in a log are
 26///         personal data the retention schedule would then have to cover (NFR-22). 503 is logged at
 27///         Warning instead — in this API it is always a deliberate answer (a saturated password-hash
 28///         gate, which <see cref="PasswordHashSaturationFilter" /> keeps out of the error log, or a
 29///         readiness probe reporting the database down), and a probe every few seconds would otherwise
 30///         bury real faults.
 31///     </para>
 32///     <para>
 33///         The readiness action carries <see cref="SuppressServerErrorLogAttribute" /> and gets no line
 34///         here at all: <c>ReadinessCache</c> holds the answer for a window, and the database check
 35///         writes one Warning per window with the cause, which is the only line an outage should cost.
 36///     </para>
 37/// </remarks>
 148438public class ServerErrorLoggingFilter(ILogger<ServerErrorLoggingFilter> logger) : IAlwaysRunResultFilter
 39{
 40    public void OnResultExecuting(ResultExecutingContext context)
 41    {
 147442    }
 43
 44    public void OnResultExecuted(ResultExecutedContext context)
 45    {
 148346        var statusCode = context.HttpContext.Response.StatusCode;
 47
 148348        if (statusCode < StatusCodes.Status500InternalServerError)
 49        {
 147250            return;
 51        }
 52
 1153        if (context.ActionDescriptor.EndpointMetadata?.OfType<SuppressServerErrorLogAttribute>().Any() == true)
 54        {
 255            return;
 56        }
 57
 958        var route = context.ActionDescriptor.AttributeRouteInfo?.Template ?? context.ActionDescriptor.DisplayName;
 959        var traceId = Activity.Current?.TraceId.ToString() ?? context.HttpContext.TraceIdentifier;
 960        var level = statusCode == StatusCodes.Status503ServiceUnavailable ? LogLevel.Warning : LogLevel.Error;
 61
 962        logger.Log(level, "Request to {Route} answered {StatusCode} (trace {TraceId})", route, statusCode, traceId);
 963    }
 64}