Serilog: Add support for injecting dynamic values into Enrichers.

Created on 10 Apr 2018  路  4Comments  路  Source: serilog/serilog

Does this issue relate to a new feature or an existing bug?

  • [ ] Bug
  • [X] New Feature

What version of Serilog is affected? Please list the related NuGet package.
All

**What is the target framework and operating system? See [target frameworks]

  • [X] netCore 2.0
  • [ ] netCore 1.0
  • [ ] 4.7
  • [ ] 4.6.x
  • [ ] 4.5.x

Please describe the current behavior?
Enrichers are a powerful tool but they enforce a parameterless constructor. This makes them very rigid and virtually impossible to inject anything.

Please describe the expected behavior?
Have a constructor that takes in a IHttpContextAccessor so that we can inject that object and have access to RequestServices services with a scoped or transient lifetime.

question

Most helpful comment

I don't think either of the suggestions posted by @nblumhardt is really useful.

Initialising Serilog as part of the DI buildup means that you don't get to capture any of the startup logic. Not good.

Adding your enricher to the log context in a middleware means that your enricher is not in place during all the ASP logging that occurs in the request pipeline prior to your middleware executing. Also not good.

A key requirement for a logging system is that it covers the entire operation of the program. We need a better solution to this issue.

All 4 comments

Hi Jose, thanks for the note!

There's no requirement that enrichers have a parameterless constructor:

.Enrich.With(new MyEnricher(...))

Serilog's also built to be DI-friendly; unfortunately containers and applications vary so much that you need to work through the container configuration yourself. What you're probably looking for is something like this Autofac-style example:

builder.Register(c => new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger())
  .SingleInstance()
  .Named<ILogger>("root");

builder.Register(c => c.ResolveNamed<ILogger>("root")
    .ForContext(new HttpContextEnricher(c.Resolve<IHttpContextAccessor>())))
  .As<ILogger>()
  .InstancePerLifetimeScope(); // or per-HTTP request

With this kind of configuration you'll get an enriched logger injected whenever you take a dependency on ILogger.

A second, and slightly nicer option, is to use some ASP.NET Core middleware to push your enricher onto the ambient log context within which a request executes:

class HttpContextLoggingMiddleware
{
    readonly RequestDelegate _next;

    public HttpContextLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        using (LogContext.Push(new HttpContextEnricher(httpContext))
        {
            await _next();
        }
    }
}

This will have the added benefit that the enrichment will work with any ILogger used during the request, even ones injected into singleton components. It'll also work better with https://github.com/nblumhardt/autofac-serilog-integration.

For the above to work, you need Enrich.FromLogContext().

Let me know how you go!
Nick

@nblumhardt We considered the second option -using a middleware and wrapping the request in a using block- but we feel that there will be a lot happening within that using block. Also, we have a situation where our microservice doesn't have an incoming request because is a HostedService. In that case that solution would not work.

Unfortunately we are dealing with .net core's dependency injection which is limited when compared to Autofac for example. Complex services registration is virtually impossible.

Since we need a solution that can be applied to both of our microservice types (http endpoints and IHostedServices) what we did was to create a class CustomLogger that "wraps" the logging and implements ILogger<> so that we can have all the extension methods for free. Then we inject that service ICustomLogger<TCategory> instead of the ILogger<TCategory> and internally, at the Log method, we enclose it with the using(LogContext.Push...

It's not the most elegant solution but it works well.

Is there a roadmap for adding support to .net core native dependency injection engine?

Hi Jose - glad you have a working solution.

Unfortunately we are dealing with .net core's dependency injection which is limited when compared to Autofac for example. Complex services registration is virtually impossible.

Is there a roadmap for adding support to .net core native dependency injection engine?

Serilog already has APIs to support just about every possible method of parameterizing/enriching loggers. There's not really anything missing on the Serilog side, that I can see.

Unfortunately, if you're stuck with an IoC container that doesn't provide adequate plug-in points then it's really not something Serilog can fix. Probably better to lobby for new features/APIs to be added to _Microsoft.Extensions.DependencyInjection_ or _Microsoft.Extensions.Logging_ ... or switch to Autofac ;-)

I don't think either of the suggestions posted by @nblumhardt is really useful.

Initialising Serilog as part of the DI buildup means that you don't get to capture any of the startup logic. Not good.

Adding your enricher to the log context in a middleware means that your enricher is not in place during all the ASP logging that occurs in the request pipeline prior to your middleware executing. Also not good.

A key requirement for a logging system is that it covers the entire operation of the program. We need a better solution to this issue.

Was this page helpful?
0 / 5 - 0 ratings