Overview
These notes provide guidance on exposing logging in a library in a way that is consistent with other .NET libraries and frameworks.
ILoggerFactory
vs. Injecting ILogger<T>
- When you need a logger object that can be passed on to multiple classes in the library, use
ILoggerFactory
. - When you need a logger object that is only used in one class and never shared, use a constructor-injected
ILogger<T>
.
Prefer Source-Generated Logging
// This partial class is static so it can be used to create extensions on ILogger:
internal static partial class LogMessages
{
// The source-generated LoggerMessageAttribute:
[LoggerMessage(
Message = "Sold {quantity} of {description}",
Level = LogLevel.Information)]
internal static partial void LogProductSaleDetails(
this ILogger logger,
int quantity,
string description);
}
Use the Null Logger and No-Op Logging Defaults
Consider an example service that takes an ILogger
as a parameter:
public class SomeService
{
private readonly ILogger _logger;
public SomeService(ILogger logger = null)
{
_logger = logger ?? NullLogger.Instance;
}
public void DoSomething(string message)
{
_logger.LogTrace("Doing something: {Message}", message);
// ...
}
}
In the above example:
- The constructor uses a default value of
null
in the event that noILogger
is provided. - The null-coalescing operator is used to assign either a valid
ILogger
or aNullLogger.Instance
to_logger
. - The
LogTrace
call is a no-op if theNullLogger
is used.
NullLogger
is also generic and can be passed as NullLogger<ServiceType>.Instance
.