In my website I'm integrating Serilog to log my errors to a custom sink. The logging is enriched with a LogContext where some custom properties needs to be passed. If I use Log.Information() it arrives at my sink with the properties in the LogEvent. So this is working great.
The main purpose is to combine the logging system to a exception handler middleware. So in the exception handler the error is caught, which is thrown from a controller method. Anywhere I place the _logger.Log() in the exception handler, no custom properties are available in the Sink. While debugging it passes the LogContextFilter before it goes to the Sink, but no properties of the filter are found.
Does anyone as any idea?
Startup
Log.Logger = new LoggerConfiguration()
.WriteTo.PasSink(new SerLogServiceClient.SerLogServiceClient(new SerLogServiceClientOptions()))
.Enrich.FromLogContext()
.CreateLogger();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddMvcOptions(mo =>
{
mo.Filters.Add(typeof(LogContextFilter));
});
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<LogContextMiddleware>();
app.UseErrorHandler(o =>
{
o.ExceptionHandlingPath = "/Home/Error";
o.Context = ExceptionHandler.Context.MVC;
});
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "Content")),
RequestPath = "/Content"
});
app.UseAuthentication();
app.UseSession();
//app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
LogContextFilter
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
using (LogContext.Push(
new PropertyEnricher("UserCode", context.HttpContext.User.Claims.FirstOrDefault(s => s.ToString().StartsWith("UserCode"))?.Value),
new PropertyEnricher("Test", "Will this go through?")))
{
await next.Invoke();
}
}
ExceptionHandlerMiddleware
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (HttpRequestException hex)
{
//check response naar reynaersexception??
//deserialize naar re
throw new NotSupportedException(); //als test
}
catch (Exception ex)
{
if (context.Response.HasStarted)
{
throw ex;
}
_logger.LogError(ex.Message);
var originalPath = context.Request.Path;
try
{
if (_options.Context == Context.MVC)
{
context.Response.Clear();
context.Response.StatusCode = 500;
context.Response.OnStarting(Callback, context.Response);
//set features
var exceptionHandlerFeature = new ReynaersExceptionHandlerFeature()
{
Error = ex,
Path = context.Request.Path.Value,
};
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
//continue lifecycle with updated context
if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
}
await _next.Invoke(context);
}
}
catch (Exception ex2)
{
// Suppress secondary exceptions, re-throw the original.
Log.Error(ex2.Message);
context.Request.Path = originalPath;
throw ex;
}
}
}
This happens because the exception gets logged in a handler that runs outside of using (LogContext.Push(..))
, therefore custom properties already have gone from context.
...
// in mvc's OnActionExecutionAsync()
using (LogContext.Push(
new PropertyEnricher("UserCode", ".."),
new PropertyEnricher("Test", "Will this go through?")))
{
await next.Invoke(); // code that throws
}
...
// later in ExceptionHandlerMiddleware, no custom properties
_logger.LogError(ex.Message);
Some time ago I researched this problem and wrote ThrowContextEnricher.
This library captures context from a point where an exception was thrown. Then ThrowContextEnricher can be used to enrich the exception log with the original context.
Log.Logger = new LoggerConfiguration()
.Enrich.With<ThrowContextEnricher>() // Adds enricher globally
.Enrich.FromLogContext()
.WriteTo
...
.CreateLogger();
...
// in mvc's OnActionExecutionAsync()
// push your properties as normal
using (LogContext.Push(
new PropertyEnricher("UserCode", ".."),
new PropertyEnricher("Test", "Will this go through?")))
{
await next.Invoke(); // code that throws
}
...
// in exception handler
// properties get logged now
// notice the exception is passed too, not just message
_logger.LogError(ex, ex.Message);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With