Diretrizes de registro em log para autores de bibliotecas .NET

Como autor de uma biblioteca, expor o registro em log é uma ótima maneira de fornecer aos consumidores informações sobre o funcionamento interno da sua biblioteca. Esta orientação ajuda você a expor o registro em log de forma consistente com outras bibliotecas e estruturas do .NET. Também ajuda a evitar gargalos de desempenho comuns que podem não ser óbvios de outra forma.

Quando usar a ILoggerFactory interface

Ao escrever uma biblioteca que emite logs, você precisa de um ILogger objeto para registrar os logs. Para obter esse objeto, sua API pode aceitar um ILogger<TCategoryName> parâmetro ou pode aceitar um ILoggerFactory após o qual você chama ILoggerFactory.CreateLogger. Que abordagem deve ser preferida?

  • Quando precisar de um objeto de registro em log que possa ser passado para várias classes para que todas elas possam emitir logs, use ILoggerFactory. É recomendável que cada classe crie logs com uma categoria separada, com o mesmo nome da classe. Para fazer isso, você precisa que a fábrica crie objetos exclusivos ILogger<TCategoryName> para cada classe que emite logs. Exemplos comuns incluem APIs de ponto de entrada público para uma biblioteca ou construtores públicos de tipos que podem criar classes auxiliares internamente.

  • Quando você precisar de um objeto de log que seja usado apenas dentro de uma classe e nunca compartilhado, use ILogger<TCategoryName>, onde TCategoryName é o tipo que produz os logs. Um exemplo comum disso é um construtor para uma classe criada por injeção de dependência.

Se você estiver projetando uma API pública que deve permanecer estável ao longo do tempo, lembre-se de que talvez deseje refatorar sua implementação interna no futuro. Mesmo que uma classe não crie nenhum tipo auxiliar interno inicialmente, isso pode mudar à medida que o código evolui. O uso ILoggerFactory acomoda a criação de novos ILogger<TCategoryName> objetos para quaisquer novas classes sem alterar a API pública.

Para obter mais informações, consulte Como as regras de filtragem são aplicadas.

Prefira o registro em log gerado pelo código-fonte

A ILogger API suporta duas abordagens para usar a API. Você pode chamar métodos como LoggerExtensions.LogError e e LoggerExtensions.LogInformationou pode usar o gerador de origem de log para definir métodos de log fortemente tipados. Para a maioria das situações, o gerador de origem é recomendado porque oferece desempenho superior e digitação mais forte. Ele também isola preocupações específicas de log, como modelos de mensagem, IDs e níveis de log do código de chamada. A abordagem não gerada pelo código-fonte é útil principalmente para cenários em que você está disposto a abrir mão dessas vantagens para tornar o código mais conciso.

using Microsoft.Extensions.Logging;

namespace Logging.LibraryAuthors;

internal static partial class LogMessages
{
    [LoggerMessage(
        Message = "Sold {Quantity} of {Description}",
        Level = LogLevel.Information)]
    internal static partial void LogProductSaleDetails(
        this ILogger logger,
        int quantity,
        string description);
}

O código anterior:

  • Define um partial class nome LogMessages, que é static para que ele possa ser usado para definir métodos de extensão no ILogger tipo.
  • Decora um LogProductSaleDetails método de extensão com o atributo e Message o LoggerMessage modelo.
  • Declara LogProductSaleDetails, que estende o ILogger e aceita a quantity e description.

Gorjeta

Você pode entrar no código-fonte gerado durante a depuração, porque ele faz parte do mesmo assembly que o código que o chama.

Use IsEnabled para evitar a avaliação dispendiosa de parâmetros

Pode haver situações em que a avaliação de parâmetros é cara. Expandindo o exemplo anterior, imagine que o description parâmetro é caro string para calcular. Talvez o produto que está sendo vendido obtenha uma descrição amigável do produto e dependa de uma consulta de banco de dados ou leitura de um arquivo. Nessas situações, você pode instruir o gerador de origem a ignorar o IsEnabled protetor e adicioná-lo IsEnabled manualmente no local de chamada. Isso permite que o usuário determine onde o protetor é chamado e garante que os parâmetros que podem ser caros para calcular sejam avaliados apenas quando realmente necessário. Considere o seguinte código:

using Microsoft.Extensions.Logging;

namespace Logging.LibraryAuthors;

internal static partial class LogMessages
{
    [LoggerMessage(
        Message = "Sold {Quantity} of {Description}",
        Level = LogLevel.Information,
        SkipEnabledCheck = true)]
    internal static partial void LogProductSaleDetails(
        this ILogger logger,
        int quantity,
        string description);
}

Quando o método de LogProductSaleDetails extensão é chamado, o protetor é invocado IsEnabled manualmente e a avaliação de parâmetros caros é limitada a quando é necessário. Considere o seguinte código:

if (_logger.IsEnabled(LogLevel.Information))
{
    // Expensive parameter evaluation
    var description = product.GetFriendlyProductDescription();

    _logger.LogProductSaleDetails(
        quantity,
        description);
}

Para obter mais informações, consulte Geração de origem de log em tempo de compilação e Log de alto desempenho no .NET.

Evite a interpolação de cadeias de caracteres no registro em log

Um erro comum é usar a interpolação de cadeia de caracteres para criar mensagens de log. A interpolação de cadeia de caracteres no log é problemática para o desempenho, pois a cadeia de caracteres é avaliada mesmo que a correspondente LogLevel não esteja habilitada. Em vez de interpolação de cadeia de caracteres, use o modelo de mensagem de log, formatação e lista de argumentos. Para obter mais informações, consulte Fazendo login no .NET: modelo de mensagem de log.

Usar padrões de log no-op

Pode haver momentos, ao consumir uma biblioteca que expõe APIs de log que esperam um ILogger ou ILoggerFactory, que você não deseja fornecer um registrador. Nesses casos, o pacote NuGet Microsoft.Extensions.Logging.Abstractions fornece padrões de log no-op.

Os consumidores de biblioteca podem usar como padrão o log nulo se não ILoggerFactory for fornecido. O uso de log nulo difere da definição de tipos como anuláveis (ILoggerFactory?), pois os tipos não são nulos. Esses tipos baseados em conveniência não registram nada e são essencialmente no-ops. Considere a utilização de qualquer um dos tipos de abstração disponíveis, quando aplicável: