High performance logging in .NET

May 10th 2024 Logging .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:

Logger message delegate warnings

CA1848: For improved performance, use the LoggerMessage delegates instead of calling LoggerExtensions.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 the LoggerMessage attribute in .NET 6 and later versions. The LoggerMessageAttribute 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 the LoggerMessage 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.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License