Implement a custom logging provider in .NET

There are many logging providers available for common logging needs. You may need to implement a custom ILoggerProvider when one of the available providers doesn't suit your application needs. In this article, you'll learn how to implement a custom logging provider that can be used to colorize logs in the console.

Sample custom logger configuration

The sample creates different color console entries per log level and event ID using the following configuration type:

using System;
using Microsoft.Extensions.Logging;

public class ColorConsoleLoggerConfiguration
{
    public int EventId { get; set; }
    public LogLevel LogLevel { get; set; } = LogLevel.Information;
    public ConsoleColor Color { get; set; } = ConsoleColor.Green;
}

The preceding code sets the default level to Information, the color to Green, and the EventId is implicitly 0.

Create the custom logger

The ILogger implementation category name is typically the logging source. For example, the type where the logger is created:

using System;
using Microsoft.Extensions.Logging;

public class ColorConsoleLogger : ILogger
{
    private readonly string _name;
    private readonly ColorConsoleLoggerConfiguration _config;

    public ColorConsoleLogger(
        string name,
        ColorConsoleLoggerConfiguration config) =>
        (_name, _config) = (name, config);

    public IDisposable BeginScope<TState>(TState state) => default;

    public bool IsEnabled(LogLevel logLevel) =>
        logLevel == _config.LogLevel;

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception exception,
        Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        if (_config.EventId == 0 || _config.EventId == eventId.Id)
        {
            ConsoleColor originalColor = Console.ForegroundColor;

            Console.ForegroundColor = _config.Color;
            Console.WriteLine($"[{eventId.Id,2}: {logLevel,-12}]");
            
            Console.ForegroundColor = originalColor;
            Console.WriteLine($"     {_name} - {formatter(state, exception)}");
        }
    }
}

The preceding code:

  • Creates a logger instance per category name.
  • Checks logLevel == _config.LogLevel in IsEnabled, so each logLevel has a unique logger. Loggers should also be enabled for all higher log levels:
public bool IsEnabled(LogLevel logLevel) =>
    logLevel == _config.LogLevel;

Custom logger provider

The ILoggerProvider object is responsible for creating logger instances. Maybe it is not needed to create a logger instance per category, but this makes sense for some loggers, like NLog or log4net. Doing this you are also able to choose different logging output targets per category if needed:

using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;

public sealed class ColorConsoleLoggerProvider : ILoggerProvider
{
    private readonly ColorConsoleLoggerConfiguration _config;
    private readonly ConcurrentDictionary<string, ColorConsoleLogger> _loggers =
        new ConcurrentDictionary<string, ColorConsoleLogger>();

    public ColorConsoleLoggerProvider(ColorConsoleLoggerConfiguration config) =>
        _config = config;

    public ILogger CreateLogger(string categoryName) =>
        _loggers.GetOrAdd(categoryName, name => new ColorConsoleLogger(name, _config));

    public void Dispose() => _loggers.Clear();
}

In the preceding code, CreateLogger creates a single instance of the ColorConsoleLogger per category name and stores it in the ConcurrentDictionary<TKey,TValue>.

Usage and registration of the custom logger

To add the custom logging provider and corresponding logger, add an ILoggerProvider with ILoggingBuilder from the HostingHostBuilderExtensions.ConfigureLogging(IHostBuilder, Action<ILoggingBuilder>):

static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureLogging(builder =>
            builder.ClearProviders()
                .AddProvider(
                    new ColorConsoleLoggerProvider(
                        new ColorConsoleLoggerConfiguration
                        {
                            LogLevel = LogLevel.Error,
                            Color = ConsoleColor.Red
                        }))
                .AddColorConsoleLogger()
                .AddColorConsoleLogger(configuration =>
                {
                    configuration.LogLevel = LogLevel.Warning;
                    configuration.Color = ConsoleColor.DarkMagenta;
                }));

The ILoggingBuilder creates one or more ILogger instances. The ILogger instances are used by the framework to log the information.

For the preceding code, provide at least one extension method for the ILoggerFactory:

using System;
using Microsoft.Extensions.Logging;

public static class ColorConsoleLoggerExtensions
{
    public static ILoggingBuilder AddColorConsoleLogger(
        this ILoggingBuilder builder) =>
        builder.AddColorConsoleLogger(
            new ColorConsoleLoggerConfiguration());

    public static ILoggingBuilder AddColorConsoleLogger(
        this ILoggingBuilder builder,
        Action<ColorConsoleLoggerConfiguration> configure)
    {
        var config = new ColorConsoleLoggerConfiguration();
        configure(config);

        return builder.AddColorConsoleLogger(config);
    }

    public static ILoggingBuilder AddColorConsoleLogger(
        this ILoggingBuilder builder,
        ColorConsoleLoggerConfiguration config)
    {
        builder.AddProvider(new ColorConsoleLoggerProvider(config));
        return builder;
    }
}

Running this simple application will render similar to the following console window:

Color console logger sample output

See also