High performance logging in .NET
You're probably used to standard extension methods for logging:
logger.LogInformation("Hello, world from {Class}!", nameof(Program));
If you use them in recent versions of .NET, you might encounter the following warning:
CA1848: For improved performance, use the
LoggerMessage
delegates instead of callingLoggerExtensions.LogInformation(ILogger, string?, params object?[])
To see it, you need to have the analysis level for .NET analyzers set to at least latest recommended:
<PropertyGroup>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>
You can learn more about the LoggerMessage
delegates if you follow the link on the code analyzer warning page. Essentially, they can provide better performance by parsing the message template only once and having strongly typed parameters.
To create a delegate, you must call the LoggerMessagae.Define
method:
private static readonly Action<ILogger, string, Exception?> hello =
LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(1, "Hello"),
"Hello, world from {Class}!"
);
To use it more conveniently, you can create an extension method as a wrapper:
public static void Hello(this ILogger logger, string @class)
{
hello(logger, @class, null);
}
This allows you to call it as if it were a logger instance method:
logger.Hello(nameof(Program));
However, if you read the linked page carefully, you should notice the following:
Instead of using the
LoggerMessage
class to create high-performance logs, you can use theLoggerMessage
attribute in .NET 6 and later versions. TheLoggerMessageAttribute
provides source-generation logging support designed to deliver a highly usable and highly performant logging solution for modern .NET applications. For more information, see Compile-time logging source generation (.NET Fundamentals).
By taking advantage of the source generator as suggested, you need can achieve the same by writing even less code:
- You make the static wrapper partial and only define a signature for it.
- Instead of calling
LoggerMessage.Define
, you put all the necessary information in theLoggerMessage
attribute for the static method.
[LoggerMessage(
EventId = 1,
Level = LogLevel.Information,
Message = "Hello, world from {Class}!"
)]
public static partial void Hello(this ILogger logger, string @class);
You can find a minimal working sample in my GitHub repository. The final commit uses the source generator approach, but you can find other approaches in previous commits.
.NET analyzers are a good way to learn about .NET, especially if you choose a higher analysis level. Description pages for individual warnings provide links to pages with more information about related topics. They are worth a read if you're not already familiar with them as you might learn about features you didn't even know they existed.