29

Background

In a new project where Serilog was chosen as the logger I automatically started passing around ILogger interface. The code accesses Log.Logger once and from then the classes that desire logging accept ILogger via constructor injection.

I was challenged on this practice and the advice was to use the static methods on the Log class, e.g. Serilog.Log.Debug(...). The argument is that there is a set; on Log.Logger so mocking is easy.

Looking at the api I can see that one of the benefits of passing ILogger are the ForContext methods.

I spent some time on the webs and in the Serilog's documentation but I could not find information about a canonical way to access the log throughout the application code.

Question

Is there a canonical, i.e. better-in-most-cases, way to access/pass around the Serilog logger and if there is, is it passing ILogger or using the static api on the Serilog.Log class?

1 Answer 1

10

There is also a ForContext on Log.Logger, so I would not decide on that basis. If you're doing mocking/testing of logging, you don't want to do that via the single global instance. Instead, any library code that will do logging should admit an ILogger parameter as input, enabling the caller to instrument and/or just pass in Log.Logger as they see fit (don't add defaulting to Log.Logger internally, for the same reasons that having a default constructor that auto-constructs dependencies that you're seeking to decouple from is a bad idea). If you don't do this, you won't be able to meaningfully test logging outputs are correct (because any concurrent tests that end up running in parallel will be writing to the exact same logger instance), which is a big thing to give up.

For me, the main tradeoff is actually whether you're willing to use Enrich.FromLogContext and the LogContext.* interfaces, which hang state off the .NET ExecutionContext, which you need to be careful to not go crazy with. (Yes, arguably you can use a collector sequestered in the ExecutionContext to hack around my previous point, but don't even go there.)

Depending on how your DI is rigged, you may want to take an ILogger<T> in your inputs, but again, unless someone needs to be able to instrument to grab the info, having a static ILogger _logger = Log.ForContext<MyClass>() is fine as long as the Log is wired up early enough.

Sign up to request clarification or add additional context in comments.

4 Comments

This is the first project where I use Serilog and I just don't if we end using Enrich.FromLogContext or other advanced features. Yes, the parallelization was the bit I'm most worried about. I think ILogger it is.
Enrich.FromLogContext is powerful and not something to shy away from - it has a very important role to play and scales plenty well. My cautionary note is only to dissuade you from trying to write tests that use/abuse E.FLC as a way to handle parallelism - while that can be made to work, it's unnecessarily mind-bending and entirely avoidable. Using E.FLC and the static logger is perfectly reasonable to do where testing of logging is not a concern. (also, if trapping, write your own ILogSink, don't muck about whith the observable sink etc, unless you're a real RX-fiend)
I didn't catch why and in which case tests can never run in parallel??
@RubenBartelink Now the point became pretty clean for me! Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.