Logger design decisions
I have been using Serilog since Microsoft introduced Microsoft.Extensions.Logging but I have to admit that eventhough it is a very powerfull library, I used it mostly because there is no built-in rolling file provider.
I would still be willing to use but I do not like the fact that I have to configure it in a way similar to Microsoft's logging, yet different.
So I have been looking for different implementations. The trick is that, in order to be efficient, you should queue the log message and then flush a batch of them.
Andrew Lock wrote a nice post about this a few years back. It is greatly inspired by Azure App Service Logging Provider from the ASP.NET team. His FileLoggerProvider
inherits from a BatchingLoggerProvider
. The former handles logging to a file and rolling them when they grow too large, the later handles batching the log messages. This design is fine, especially because other providers may need this batching mecanism, such as a provider that logs in a database for instance. But still, I would like to keep the package with the logger as focused as possible and I would not include a file logger and a database logger in the same package.
I also looked at NReco.Logging.File. The implementation is much simpler. No base class. Much more in line with what I was looking for.
Yet, there are a few features I would not keep:
- The configuration should use
ILoggerProviderConfiguration<T>
, like inConsoleLoggerConfigureOptions
Apppend
is not an option, I find this mandatory for logs. Resetting the file every time is the best way to loose previous debugging information.MinLevel
, orIsEnabled
like forFileLoggerProvider
is not an option for me either. It can already be set in the configuration. Also, I do not think thebool IsEnabled(LogLevel logLevel)
method in ILogger is about adding filtering, it more about handling current state. For instance theDebugLogger
checks if the debugger is attached.- There are more options I would not keep,
FilterLogEntry
obviously andFormatLogEntry
, because I would rather use the standard behavior. - Finally, the provider will not memoize the created logger. The logger factory already takes care of this.
Normaly, I would show code here. But not this time. This post is not about showing code. It is about researching and reading code, reflecting on it, and decide what to do with it.
Because when you have a clear idea of what your goals are, coding is easier and decisions, especially the small ones, are easier to make.
For instance, when logging, the top priorities are:
- Logging should not slow the normal flow of the program more than what is absolutly necessary.
- When the history of logs it too big, most recent logs should be preserved.
- If logging fails, it should not disrupt the normal flow of the program.
Priority #1 advocates for using queues and batching.
Priority #2 discards the idea of maxFileCountPerPeriodicity
.
Priority #3 tells us not to throw when the LogLevel
cannot be mapped to a string.
References & Useful Links
Creating a rolling file logging provider for ASP.NET Core 2.0
Andrew Lock's post on how to create a logging provider that writes logs to the file system.NetEscapades.Extensions.Logging
A rolling file provider for ASP.NET Core 2.1 Microsoft.Extensions.Logging, the logging subsystem used by ASP.NET Core. Writes logs to a set of text files, one per day.NReco.Logging.File
Simple and efficient file logger provider for .NET Core (any version) / NET5 / NET6 without additional dependencies.Serilog
Flexible, structured events — log file convenience.