Compartilhar via


Implementar um provedor de log personalizado no .NET

Há muitos provedores de log disponíveis para necessidades comuns de log. Talvez seja necessário implementar um ILoggerProvider personalizado quando um dos provedores disponíveis não atender às necessidades do aplicativo. Neste artigo, você aprenderá a implementar um provedor de log personalizado que pode ser usado para colorir logs no console.

Dica

O código-fonte de exemplo do provedor de log personalizado está disponível no repositório GitHub do Docs. Saiba mais em GitHub: .NET Docs - Log personalizado do console.

Configuração de registrador personalizado de exemplo

O exemplo cria entradas de console de cores diferentes por nível de log e ID de evento usando o seguinte tipo de configuração:

using Microsoft.Extensions.Logging;

public sealed class ColorConsoleLoggerConfiguration
{
    public int EventId { get; set; }

    public Dictionary<LogLevel, ConsoleColor> LogLevelToColorMap { get; set; } = new()
    {
        [LogLevel.Information] = ConsoleColor.Green
    };
}

O código anterior define o nível padrão como Information, a cor como Greene o EventId é implicitamente 0.

Criar o registrador personalizado

O nome da categoria de implementação ILogger normalmente é a origem do log. Por exemplo, o tipo em que o agente é criado:

using Microsoft.Extensions.Logging;

public sealed class ColorConsoleLogger(
    string name,
    Func<ColorConsoleLoggerConfiguration> getCurrentConfig) : ILogger
{
    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;

    public bool IsEnabled(LogLevel logLevel) =>
        getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel);

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

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

            Console.ForegroundColor = config.LogLevelToColorMap[logLevel];
            Console.WriteLine($"[{eventId.Id,2}: {logLevel,-12}]");
            
            Console.ForegroundColor = originalColor;
            Console.Write($"     {name} - ");

            Console.ForegroundColor = config.LogLevelToColorMap[logLevel];
            Console.Write($"{formatter(state, exception)}");
            
            Console.ForegroundColor = originalColor;
            Console.WriteLine();
        }
    }
}

O código anterior:

  • Cria uma instância do agente por nome de categoria.
  • Verifica _getCurrentConfig().LogLevelToColorMap.ContainsKey(logLevel) em IsEnabled, para que cada logLevel tenha um agente exclusivo. Nesta implementação, cada nível de log requer uma entrada de configuração explícita para ser registrado.

É uma boa prática chamar ILogger.IsEnabled dentro das implementações de ILogger.Log, visto que Log pode ser chamado por qualquer consumidor, e não há garantias de que ele foi verificado anteriormente. O método IsEnabled deve ser muito rápido na maioria das implementações.

TState state,
Exception? exception,

O agente é instanciado com o name e um Func<ColorConsoleLoggerConfiguration>, que retorna a configuração atual. Isso trata das atualizações para os valores de configuração conforme monitorado por meio do retorno de chamada IOptionsMonitor<TOptions>.OnChange.

Importante

A implementação do ILogger.Log verifica se o valor do config.EventId está definido. Quando config.EventId não está definido ou quando corresponde exatamente ao logEntry.EventId, o registrador faz registros em cores.

Provedor de registrador personalizado

O objeto ILoggerProvider é responsável pela criação de instâncias do agente. Não é necessário criar uma instância de logger por categoria, mas faz sentido para alguns loggers, como o NLog ou o log4net. Essa estratégia permite que você escolha destinos de saída de log diferentes por categoria, como no exemplo a seguir:

using System.Collections.Concurrent;
using System.Runtime.Versioning;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

[UnsupportedOSPlatform("browser")]
[ProviderAlias("ColorConsole")]
public sealed class ColorConsoleLoggerProvider : ILoggerProvider
{
    private readonly IDisposable? _onChangeToken;
    private ColorConsoleLoggerConfiguration _currentConfig;
    private readonly ConcurrentDictionary<string, ColorConsoleLogger> _loggers =
        new(StringComparer.OrdinalIgnoreCase);

    public ColorConsoleLoggerProvider(
        IOptionsMonitor<ColorConsoleLoggerConfiguration> config)
    {
        _currentConfig = config.CurrentValue;
        _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
    }

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

    private ColorConsoleLoggerConfiguration GetCurrentConfig() => _currentConfig;

    public void Dispose()
    {
        _loggers.Clear();
        _onChangeToken?.Dispose();
    }
}

No código anterior, CreateLogger cria uma única instância do nome ColorConsoleLogger por categoria e o armazena no ConcurrentDictionary<TKey,TValue>. Além disso, a interface IOptionsMonitor<TOptions> é necessária para atualizar as alterações no objeto ColorConsoleLoggerConfiguration subjacente.

Para controlar a configuração do ColorConsoleLogger, defina um alias em seu provedor:

[UnsupportedOSPlatform("browser")]
[ProviderAlias("ColorConsole")]
public sealed class ColorConsoleLoggerProvider : ILoggerProvider

A classe ColorConsoleLoggerProvider define dois atributos com escopo de classe:

A configuração pode ser especificada com qualquer provedor de configuração válido. Considere o seguinte arquivo de appsettings.json:

{
    "Logging": {
        "ColorConsole": {
            "LogLevelToColorMap": {
                "Information": "DarkGreen",
                "Warning": "Cyan",
                "Error": "Red"
            }
        }
    }
}

Isso configura os níveis de log para os seguintes valores:

O nível de log Information é definido como DarkGreen, que substitui o valor padrão definido no objeto ColorConsoleLoggerConfiguration.

Uso e registro do logger personalizado

Por convenção, o registro de serviços para injeção de dependência acontece como parte da rotina de inicialização de um aplicativo. O registro ocorre na classe Program ou pode ser delegado a uma classe Startup. Neste exemplo, você se registrará diretamente do Program.cs.

Para adicionar o provedor de log personalizado e o agente correspondente, adicione um ILoggerProvider com ILoggingBuilder do HostingHostBuilderExtensions.ConfigureLogging(IHostBuilder, Action<ILoggingBuilder>):

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddColorConsoleLogger(configuration =>
{
    // Replace warning value from appsettings.json of "Cyan"
    configuration.LogLevelToColorMap[LogLevel.Warning] = ConsoleColor.DarkCyan;
    // Replace warning value from appsettings.json of "Red"
    configuration.LogLevelToColorMap[LogLevel.Error] = ConsoleColor.DarkRed;
});

using IHost host = builder.Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();

logger.LogDebug(1, "Does this line get hit?");    // Not logged
logger.LogInformation(3, "Nothing to see here."); // Logs in ConsoleColor.DarkGreen
logger.LogWarning(5, "Warning... that was odd."); // Logs in ConsoleColor.DarkCyan
logger.LogError(7, "Oops, there was an error.");  // Logs in ConsoleColor.DarkRed
logger.LogTrace(5, "== 120.");                    // Not logged

await host.RunAsync();

O ILoggingBuilder cria uma ou mais instâncias de ILogger. As instâncias de ILogger são usadas pelo framework para logar as informações.

A configuração do arquivo appsettings.json substitui os seguintes valores:

Por convenção, os métodos de extensão em ILoggingBuilder são usados para registrar o provedor personalizado:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class ColorConsoleLoggerExtensions
{
    public static ILoggingBuilder AddColorConsoleLogger(
        this ILoggingBuilder builder)
    {
        builder.AddConfiguration();

        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, ColorConsoleLoggerProvider>());

        LoggerProviderOptions.RegisterProviderOptions
            <ColorConsoleLoggerConfiguration, ColorConsoleLoggerProvider>(builder.Services);

        return builder;
    }

    public static ILoggingBuilder AddColorConsoleLogger(
        this ILoggingBuilder builder,
        Action<ColorConsoleLoggerConfiguration> configure)
    {
        builder.AddColorConsoleLogger();
        builder.Services.Configure(configure);

        return builder;
    }
}

A execução deste aplicativo simples renderizará a saída de cores para a janela do console semelhante à seguinte imagem:

Exemplo de saída do log de cores do console Saída da amostra do agente do console de cores

Consulte também