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.
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.
}
}
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.