Original post: https://siderite.dev/blog/generate-readable-and-efficient-logging-methods
Intro
Finally a technical blog post after so long, right? Well, don't get used to it 😝
I just learned about a .NET 6 feature, improved in .NET 9, that can help organize your logging, making it both more efficient and readable in the process. This is Compile-time logging source generation, a way to define logger messages in partial classes using attributes decorating method stubs. Source code generators will then generate the methods, which will be efficient, have a readable name and not pollute your code with a log of logging logic.
There are some constraints that you must follow:
- Logging methods must be partial and return void.
- Logging method names must not start with an underscore.
- Parameter names of logging methods must not start with an underscore.
- Logging methods cannot be generic.
- If a logging method is static, the ILogger instance is required as a parameter
- Code must be compiled with a modern C# compiler, version 9 (made available in .NET 5) or later
As a general rule, the first instance of ILogger, LogLevel, and Exception are treated specially in the log method signature of the source generator. Subsequent instances are treated like normal parameters to the message template.
Details
Here is an example:
public static partial class Log
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{HostName}`")]
public static partial void CouldNotOpenSocket(
this ILogger logger, string hostName);
}
// use like this:
_logger.CouldNotOpenSocket(hostName);
There is support to specifying any of the parameters required in the logger message as method parameters, if you want a hybrid approach.
You can use both static and instance methods - as long as they conform to the rules above.
You can use other attributes to define sensitive logging parameters, a thing called Redaction, like this:
// if you have a log message that has a parameter that is considered private:
[LoggerMessage(0, LogLevel.Information, "User SSN: {SSN}")]
public static partial void LogPrivateInformation(
this ILogger logger,
[MyTaxonomyClassifications.Private] string SSN);
// You will need to have a setting similar to this:
using Microsoft.Extensions.Telemetry;
using Microsoft.Extensions.Compliance.Redaction;
var services = new ServiceCollection();
services.AddLogging(builder =>
{
// Enable redaction.
builder.EnableRedaction();
});
services.AddRedaction(builder =>
{
// configure redactors for your data classifications
builder.SetRedactor<StarRedactor>(MyTaxonomyClassifications.Private);
});
public void TestLogging()
{
LogPrivateInformation("MySSN");
}
// output will be: User SSN: *****
The generator gives warnings to help developers do the right thing.
You can supply alternative names for the template placeholders and use format specifiers.
Top comments (0)