Search code examples
asp.net-coreexceptionasp.net-core-2.0error-loggingserilog

How to add HttpContext to enrich un-handled exception logs?


I have setup Serilog to log to MSSql using:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .MinimumLevel.Override("System", LogEventLevel.Information)
    .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
    .Enrich.FromLogContext()
    .WriteTo.Async(x => x.MSSqlServer(logConntectionString, tableName, LogEventLevel.Warning, autoCreateSqlTable: false, columnOptions: columnOptions))
    .CreateLogger();

Additionally I have added added a SerilogMiddleware in the pipeline that successfully adds LogContext from the HttpContext.

In a test controller, I have these 2 test methods:

public class TestController : ControllerBase
{
    [HttpGet, Route("test")]
    public IActionResult Get() {
        try
        {
            string[] sar = new string[0];                
            var errorgenerator = sar[2]; // Trigger exception
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Caught Exception");
            return StatusCode(500, "Custom 500 Error");
        }
        return Ok();
    }

    [HttpGet, Route("test2")]
    public IActionResult Get2() {

        string[] sar = new string[0];
        var errorgenerator = sar[2];// Trigger exception

        return Ok();
    }
}

The first method is not DRY, and so I would like to handle global/uncaught exceptions such as method 2.

What I have from here is:

public class GloablExceptionFilter : ActionFilterAttribute, IExceptionFilter
{    
    public void OnException(ExceptionContext context)
    {
        var httpContext = context.HttpContext;  //  This does not appear to have the actual HttpContext

        Log.Error(context.Exception, "Unhandled Exception");
    }
}

Problem is, my middleware that otherwise worked no longer does.. It does not edit the response body, etc... Further, when I access ExceptionContext's context.HttpContext, it does not contain the actual HttpContext when triggered from inside a controller method such as above.

  1. How do I inject or share HttpContext and or LogContext with this Filter?
  2. If thats not possible, how do I accomplish logging exceptions, while being DRY, and having context when its available?

Update 1: Current Middleware

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddSerilog();            

    app.UseAuthentication();

    // Logging Middleware is just after Authentication, to have access to 
    // user IsAuthorized, claims, etc..
    app.UseMiddleware<SerilogMiddleware>();

    app.UseCors("CORSPolicy");

    app.UseMvc();
}

In the middleware itself:

public class SerilogMiddleware
{
    readonly RequestDelegate _next;

    public SerilogMiddleware(RequestDelegate next)
    {
        if (next == null) throw new ArgumentNullException(nameof(next));
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
      // Do logging stuff with Request..
       await _next(httpContext);
      // Do logging stuff with Response but..
      // This point is never reached, when exception is unhandled.
    }
}

Solution

  • Based on code snippet you are not catching the exception when you pass the context down the pipeline.

    If you do not catch/handle the exception within the middleware then it wont reach your code after calling down stream.

    public class SerilogMiddleware {
        readonly RequestDelegate _next;
    
        public SerilogMiddleware(RequestDelegate next) {
            if (next == null) throw new ArgumentNullException(nameof(next));
            _next = next;
        }
    
        public async Task Invoke(HttpContext httpContext) {
            // Do logging stuff with Request..
            try {
                await _next(httpContext);
            } catch(Exception ex) {
                try {
                    //Do exception specific logging
    
                    // if you don't want to rethrow the original exception
                    // then call return:
                    // return;
                } catch (Exception loggingException) {
                    //custom
                }
    
                // Otherwise re -throw the original exception
                throw;
            }
            // Do logging stuff with Response      
        }
    }
    

    The above will re-throw the original error after logging it so that the other handler in the pipeline will catch it and do the out of the box handling.