/FastLogr

FastLogr is a different approach to tackle performance efficient logging that's less convoluted than the MS approach.

Primary LanguageC#MIT LicenseMIT

Pipeline GitHub issues

FastLogr

A less convoluted approach to the newly introduced high-performance logging Microsoft recently introduced.

Purpose

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.

MS 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.

FastLogr Approach

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");
    }
}

Installation

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.

Usage

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.

Attribution

Thanks "Pause08" for providing the icon via "flaticon"! Growth icons created by Pause08 - Flaticon