Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the current HttpContext in a SeriLog Sink?

I'm creating my own SeriLog sink implementing ILogEventSink using the Building a Simple Sink example with the intent of logging some information from the users claims. To get access to HttpContext in Core, I'd normally inject in an instance of IHttpContextAccessor but the example shows creating an instance of the sink in an extension method e.g.

public class MySink : ILogEventSink
{
    private readonly IFormatProvider _formatProvider;

    public MySink(IFormatProvider formatProvider)
    {
        _formatProvider = formatProvider;
    }

    public void Emit(LogEvent logEvent)
    {
        // How to get HttpContext here?
    }
}

public static class MySinkExtensions
{
    public static LoggerConfiguration MySink(
              this LoggerSinkConfiguration loggerConfiguration,
              IFormatProvider formatProvider = null)
    {
        return loggerConfiguration.Sink(new MySink(formatProvider));
    }
}

... then to use the sink ...

var log = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.MySink()
    .CreateLogger();

How can I get access to the current HttpContext in the Emit method of the sink? Or is it possible to have the sink created by the DI framework for example?!

I have an MVC site running Asp.Net Core 2 framework against .Net 4.6.2 runtime using Serilog.AspNetCore v2.1.0.

Update - Workaround

After the pointer from @tsimbalar I created middleware similar to the code below. In my StartUp.Configure method I add it using app.UseMiddleware<ClaimsMiddleware>(); after the app authentication step has happened (otherwise there will be no claims loaded).

public class ClaimsMiddleware
{
    private static readonly ILogger Log = Serilog.Log.ForContext<ClaimsMiddleware>();
    private readonly RequestDelegate next;

    public ClaimsMiddleware(RequestDelegate next)
    {
        this.next = next ?? throw new ArgumentNullException(nameof(next));
    }

    public async Task Invoke(HttpContext httpContext)
    {
        if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));

        // Get values from claims here
        var myVal = httpContext
                .User
                .Claims
                .Where(x => x.Type == "MyVal")
                .Select(x => x.Value)
                .DefaultIfEmpty(string.Empty)
                .SingleOrDefault();

        using (LogContext.PushProperty("MyVal", myVal))
        {
            try
            {
                await next(httpContext);
            }

            // Never caught, because `LogException()` returns false.
            catch (Exception ex) when (LogException(httpContext, ex)) { }
        }
    }

    private static bool LogException(HttpContext httpContext, Exception ex)
    {
        var logForContext = Log.ForContext("StackTrace", ex.StackTrace);

        logForContext.Error(ex, ex.Message);

        return false;
    }
}
like image 365
Gavin Sutherland Avatar asked Nov 02 '17 09:11

Gavin Sutherland


People also ask

What are enrichers in Serilog?

Log Context enricher - Built in to Serilog, this enricher ensures any properties added to the Log Context are pushed into log events. Environment enrichers - Enrich logs with the machine or current user name.

How Serilog works?

Flexible, structured events — log file convenience. NET, Serilog provides diagnostic logging to files, the console, and elsewhere. It is easy to set up, has a clean API, and is portable between recent . NET platforms. Unlike other logging libraries, Serilog is built with powerful structured event data in mind.

How Serilog works in. net Core?

Serilog is a . NET library that provides diagnostic logging to files, the console, and almost everywhere you would like. Serilog can be used in classic . NET Framework applications and for applications running on the latest and greatest .

What is sink in Serilog?

Serilog provides sinks for writing log events to storage in various formats. Many of the sinks listed below are developed and supported by the wider Serilog community; please direct questions and issues to the relevant repository. More sinks can be found by searching within the serilog tag on NuGet.


2 Answers

I have been struggling trying to do the same and I finally found a proper solution.

Do not add the enricher when creating the Logger. You will have to add the enricher in the middleware where you can access the IServiceProvider. The key is that LogContext has a method, Push, that can add an enricher:

public async Task Invoke(HttpContext httpContext)
{
    IServiceProvider serviceProvider = httpContext.RequestServices;
    using (LogContext.Push(new LogEnricher(serviceProvider))) {
        await _next(httpContext);
    }
}

In the ConfigureServices, I add a services.AddScoped<HttpContextToLog>() call.

Then, I populate the HttpContextToLog object in several places, accessing it like this:

HttpContextToLog contextToLog = _serviceProvider.GetService<HttpContextToLog>();

in the Enrich method, in an IActionFilter, in an IPageFilter, etc.

like image 166
Manu A.B. Avatar answered Oct 20 '22 06:10

Manu A.B.


UPDATE I think you may want to look at this article : http://mylifeforthecode.github.io/enriching-serilog-output-with-httpcontext-information-in-asp-net-core/

The idea is to register a custom middleware that will add all the contextual information to the current LogContext during the request.

For it to work you must configure your logger with

Log.Logger = new LoggerConfiguration()
      // snip ....MinimumLevel.Debug()
      .Enrich.FromLogContext()                
      // snip ...
.CreateLogger(); 

This article by Nicholas Blumhardt may also help : https://blog.getseq.net/smart-logging-middleware-for-asp-net-core/


WARNING - Solution below does not work in this case

The solution below cannot work if the logger is registered early (in Program.Main() )

First of all, if you want to add extra information attached to the logged event, I believe what you want is an Enricher.

You could then :

  • Register IHttpContextAccessor into your ServiceCollection (for instance, using AddHttpContextAccessor()) : services.AddHttpContextAccessor();
  • Create an implementation of ILogEventEnricher that accepts IHttpContextAccessor in its constructor
  • When configuring your logger, inject IHttpContextAccessor (by adding an argument of type IHttpContextAccessor to Startup.Configure()
  • Add this enricher to your logger

The enricher could look something like https://github.com/serilog-web/classic/blob/master/src/SerilogWeb.Classic/Classic/Enrichers/ClaimValueEnricher.cs .

And you would configure your logger like this :

var logger = new LoggerConfiguration()
                .EnrichWith(new MyEnricher(contextAccessor))
                .WriteTo.Whatever()
                .CreateLogger();
like image 36
tsimbalar Avatar answered Oct 20 '22 08:10

tsimbalar