A less convoluted approach to the newly introduced high-performance logging Microsoft recently introduced.
Microsoft introduced a new warning CA1848: Use the LoggerMessage delegates
that links to the page describing
high-performance logging by either writing an Action
for your log message or, by using
their source generated approach.
As I'm not really a fan of either of those proposed ways I created this package.
FastLogr
tries to combine both approaches that MS suggests and do it in a more elegant way.
It also uses source generators to limit what you have to code, but instead of the MS approach with way less hassle.
The following sections compare the MS approach and the FastLogr
approach.
To get the MS source gen going you need to make your class partial
and also provide a static
, partial
method.
namespace FastLogr.Example.WorkerService;
public partial class MsApproach
{
[LoggerMessage(
EventId = 0,
Level = LogLevel.Critical,
Message = "Could not open socket to `{hostName}`")]
public static partial void CouldNotOpenSocket(
ILogger logger, string hostName);
}
The code generated by this looks like this:
// <auto-generated/>
#nullable enable
namespace FastLogr.Example.WorkerService
{
partial class MsApproach
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "7.0.8.27404")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Exception?> __CouldNotOpenSocketCallback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String>(global::Microsoft.Extensions.Logging.LogLevel.Critical, new global::Microsoft.Extensions.Logging.EventId(0, nameof(CouldNotOpenSocket)), "Could not open socket to `{hostName}`", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "7.0.8.27404")]
public static partial void CouldNotOpenSocket(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String hostName)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Critical))
{
__CouldNotOpenSocketCallback(logger, hostName, null);
}
}
}
}
This is just one example of how you can get the MS way going, more can be found in the documentation.
To get the FastLogr
source gen going you simply need to provide an Action
with the parameters you want to log.
using FastLogr.Attributes;
namespace FastLogr.Example.WorkerService;
public class FastLogrApproach
{
[LogMessage(MessageTemplate = "Writing log with FastLogr, my message was '{message}'", LogLevel = LogLevel.Critical, EventId = 0)]
private static Action<string> LogFastLogrMessage = default!;
}
No need to mark your class as partial
just for the sake of logging.
The generated code will look like this:
using System;
using Microsoft.Extensions.Logging;
using FastLogr.Attributes;
namespace SourceGenPlayground.Example.Worker;
public static class LogFastLogrMessageExtensions
{
private static readonly Action<ILogger, string, Exception> s_logFastLogrMessage = LoggerMessage.Define<string>(LogLevel.Critical, new EventId(0, ""), "Writing log with FastLogr, my message was '{message}'");
public static void LogFastLogrMessage(this ILogger logger, string arg1) => s_logFastLogrMessage(logger, arg1, default !);
}
The usage of the FastLogr
approach is simple, as it provides you with an extension method on your ILogger
instance:
using FastLogr.Attributes;
using SourceGenPlayground.Example.Worker;
namespace FastLogr.Example.WorkerService;
public class FastLogrApproach
{
private readonly ILogger<FastLogrApproach> _logger;
[LogMessage(MessageTemplate = "Writing log with FastLogr, my message was '{message}'", LogLevel = LogLevel.Critical, EventId = 0)]
private static Action<string> LogFastLogrMessage = default!;
public FastLogrApproach(ILogger<FastLogrApproach> logger)
{
_logger = logger;
_logger.LogFastLogrMessage("MyCustomMessage");
}
}
FastLogr
is published as a nuget package, so installation is simple:
dotnet add package FastLogr
There are also YOLO packages pushed here on this Github repository.
You define your log message as a combination of the LogMessage
attribute and an Action
with the parameter types
you want to pass to your log message.
using FastLogr.Attributes;
using SourceGenPlayground.Example.Worker;
namespace FastLogr.Example.WorkerService;
public class FastLogrApproach
{
private readonly ILogger<FastLogrApproach> _logger;
[LogMessage(MessageTemplate = "Writing log with FastLogr, my message was '{message}'", LogLevel = LogLevel.Critical, EventId = 0)]
private static Action<string> LogFastLogrMessage = default!;
public FastLogrApproach(ILogger<FastLogrApproach> logger)
{
_logger = logger;
_logger.LogFastLogrMessage("MyCustomMessage");
}
}
As shown above FastLogr
will generate an extension method for ILogger
and you can use it right away.
The types you want to log can be pretty much anything, e.g.:
using FastLogr.Attributes;
namespace FastLogr.Example.WorkerService;
public class FastLogrApproach
{
private readonly ILogger<FastLogrApproach> _logger;
[LogMessage(MessageTemplate = "Writing log with FastLogr, my message was '{message}'", LogLevel = LogLevel.Critical, EventId = 0)]
private static Action<string, FancyType> LogFastLogrMessage = default!;
public FastLogrApproach(ILogger<FastLogrApproach> logger)
{
_logger = logger;
_logger.LogFastLogrMessage("MyCustomMessage", new FancyType("fany message", 420, true));
}
}
public record FancyType(string FancyTypeString, int FancyTypeInt, bool FancyTypeBool);
Any usings
that your class
has will also be available to the ILogger
extension built by FastLogr
.
Thanks "Pause08" for providing the icon via "flaticon"! Growth icons created by Pause08 - Flaticon