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 exclusivosILogger<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>
, ondeTCategoryName
é 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
nomeLogMessages
, que éstatic
para que ele possa ser usado para definir métodos de extensão noILogger
tipo. - Decora um
LogProductSaleDetails
método de extensão com o atributo eMessage
oLoggerMessage
modelo. - Declara
LogProductSaleDetails
, que estende oILogger
e aceita aquantity
edescription
.
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: