Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serilog Logcontext properties are gone after exception handler

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;
            }
        }
    }
like image 288
NiAu Avatar asked Mar 05 '23 08:03

NiAu


1 Answers

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);

like image 198
Tolyandre Avatar answered Apr 09 '23 20:04

Tolyandre