Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Para cenários de registo de alto desempenho em .NET 6 e versões posteriores, use o LoggerMessageAttribute com geração de código fonte em tempo de compilação. Esta abordagem proporciona o melhor desempenho ao eliminar boxing, alocações temporárias e a análise de modelos de mensagens durante a execução.
O registo gerado pela fonte oferece as seguintes vantagens de desempenho em relação aos métodos de extensão do logger, tais como LogInformation e LogDebug:
-
Elimina o boxe: Os métodos de extensão do logger requerem "boxing" (convertendo) tipos de valor, como
int, emobject. A geração de registos pelo código-fonte evita o boxing ao usar parâmetros fortemente tipados. - Analisa os modelos em tempo de compilação: Os métodos de extensão do logger devem analisar o modelo de mensagem (string de formato nomeado) sempre que uma mensagem de log é escrita. O registo gerado pela fonte processa os templates uma vez durante a compilação.
- Reduz as alocações: O gerador de código-fonte cria código otimizado que minimiza as alocações de objetos e o uso temporário de memória.
A aplicação de exemplo demonstra funcionalidades de registo de alto desempenho com um serviço de trabalho de processamento de fila de prioridade. A aplicação processa os itens de trabalho por ordem de prioridade. À medida que estas operações ocorrem, as mensagens de registo são geradas através de logging gerado pela fonte.
Sugestão
Todo o código-fonte de exemplo de registro está disponível no Navegador de amostras para download. Para obter mais informações, consulte Procurar exemplos de código: fazendo login no .NET.
Defina mensagens do logger com geração de origem
Para criar mensagens de registo de alto desempenho em .NET 6 e posteriores, defina métodos partial decorados com LoggerMessageAttribute. O gerador de código-fonte cria a implementação em tempo de compilação.
Método básico de registo
Para uma mensagem de log simples, defina um método parcial com o atributo que especifica o ID do evento, o nível do log e o modelo da mensagem:
public static partial class Log
{
[LoggerMessage(
EventId = 13,
Level = LogLevel.Critical,
Message = "Epic failure processing item!")]
public static partial void FailedToProcessWorkItem(
ILogger logger, Exception ex);
}
O modelo de mensagem utiliza marcadores de posição preenchidos por parâmetros de método. Os nomes provisórios devem ser descritivos e consistentes entre os modelos. Servem como nomes de propriedades dentro de dados de registo estruturados. Recomendamos a capa Pascal para nomes provisórios. Por exemplo, {Item}, {DateTime}.
Chame o método de registo a partir do seu código. Por exemplo, quando ocorre uma exceção durante o processamento do item de trabalho:
try
{
// Process work item.
}
catch (Exception ex)
{
Log.FailedToProcessWorkItem(logger, ex);
}
Este código produz saídas de consola como:
crit: WorkerServiceOptions.Example.Worker[13]
Epic failure processing item!
System.Exception: Failed to verify communications.
Registo com parâmetros
Para passar parâmetros a uma mensagem de log, adicione-os como parâmetros de método. Os nomes dos parâmetros correspondem aos marcadores de posição no modelo da mensagem:
public static partial class Log
{
[LoggerMessage(
EventId = 1,
Level = LogLevel.Information,
Message = "Processing priority item: {Item}")]
public static partial void PriorityItemProcessed(
ILogger logger, WorkItem item);
}
Chame o método com os valores do logger e dos parâmetros:
var workItem = queue.Dequeue();
Log.PriorityItemProcessed(logger, workItem);
Este código produz saídas de consola como:
info: WorkerServiceOptions.Example.Worker[1]
Processing priority item: Priority-Extreme (50db062a-9732-4418-936d-110549ad79e4): 'Verify communications'
Os armazenamentos de registo estruturado podem usar o nome do evento quando o ID do evento é fornecido para enriquecer o registo. Por exemplo, o Serilog usa o nome do evento.
Defina o âmbito da mensagem do logger com geração de código-fonte
Pode definir escopos de log para envolver uma série de mensagens de log com contexto adicional. Com o registo gerado por código-fonte, combinas os LoggerMessageAttribute métodos com o método padrão ILogger.BeginScope .
Ative IncludeScopes na secção de logger da consola de appsettings.json:
{
"Logging": {
"Console": {
"IncludeScopes": true
},
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Crie métodos de registo gerados pela fonte e envolva-os num âmbito usando BeginScope:
public static partial class Log
{
[LoggerMessage(
EventId = 1,
Level = LogLevel.Information,
Message = "Processing priority item: {Item}")]
public static partial void PriorityItemProcessed(
ILogger logger, WorkItem item);
}
Use o método de registo dentro de um âmbito do seu código de aplicação:
using (_logger.BeginScope("Processing scope, started at: {DateTime}", DateTime.Now))
{
Log.PriorityItemProcessed(_logger, workItem);
}
Inspeciona as mensagens de registo na saída da consola da aplicação. O resultado seguinte mostra a ordem de prioridade das mensagens de registo com a mensagem de âmbito de registo incluída:
info: WorkerServiceOptions.Example.Worker[1]
=> Processing scope, started at: 04/11/2024 11:27:52
Processing priority item: Priority-Extreme (7d153ef9-8894-4282-836a-8e5e38319fb3): 'Verify communications'
Abordagem legada: LoggerMessage.Define (para .NET Framework e .NET Core 3.1)
Antes da introdução do registo gerado por código-fonte no .NET 6, a abordagem recomendada de registo de alto desempenho era usar o LoggerMessage.Define método para criar delegados cacheáveis. Embora esta abordagem ainda seja suportada para compatibilidade retroativa, o novo código deve usar registos gerados pelo código-fonte em vez disso LoggerMessageAttribute .
A LoggerMessage classe expõe funcionalidades para criar delegados cacheáveis que requerem menos alocações de objetos e menos sobrecarga computacional em comparação com métodos de extensão logger, como LogInformation e LogDebug. LoggerMessage oferece as seguintes vantagens de desempenho em relação aos métodos de extensão do logger:
- Os métodos de extensão do Logger exigem tipos de valor "boxing" (conversão), como
int, emobject. O LoggerMessage padrão evita o boxe usando campos estáticos Action e métodos de extensão com parâmetros fortemente tipados. - Os métodos de extensão do logger devem analisar o modelo de mensagem (cadeia de caracteres de formato nomeada) sempre que uma mensagem de log é gravada. LoggerMessage só requer a análise de um modelo uma vez quando a mensagem é definida.
Observação
Se estiveres a manter código que usa LoggerMessage.Define, considera migrar para registos gerados pelo código-fonte. Para aplicações .NET Framework ou .NET Core 3.1, continue a usar LoggerMessage.Define.
Defina uma mensagem de log
Use o Define(LogLevel, EventId, String) para criar um Action delegado para registrar uma mensagem. Define Sobrecargas permitem passar até seis parâmetros de tipo a uma string de formato nomeada (template).
A cadeia fornecida ao Define método é um modelo e não uma cadeia interpolada. Os marcadores de posição são preenchidos na ordem em que os tipos são indicados. Os nomes provisórios no modelo devem ser descritivos e consistentes entre os modelos. Servem como nomes de propriedades dentro de dados de registo estruturados. Recomendamos a capa Pascal para nomes provisórios. Por exemplo, {Item}, {DateTime}.
Cada mensagem de registo é Action mantida num campo estático criado pelo LoggerMessage.Define. Por exemplo, a aplicação de exemplo cria um campo para descrever uma mensagem de registo para o processamento de itens de trabalho:
private static readonly Action<ILogger, Exception> s_failedToProcessWorkItem;
Para o Action, especifique:
- O nível de log.
- Um identificador de evento único (EventId) com o nome do método de extensão estática.
- O modelo de mensagem (chamado string de formato).
À medida que os itens de trabalho são desalinhados para processamento, a aplicação de serviço ao trabalhador define o:
- Nível de registo para LogLevel.Critical.
- ID do evento para
13com o nome do métodoFailedToProcessWorkItem. - Modelo de mensagem (denominado format string) para uma string.
s_failedToProcessWorkItem = LoggerMessage.Define(
LogLevel.Critical,
new EventId(13, nameof(FailedToProcessWorkItem)),
"Epic failure processing item!");
O LoggerMessage.Define método é usado para configurar e definir um Action delegado, que representa uma mensagem de log.
Os armazenamentos de registo estruturado podem usar o nome do evento quando o ID do evento é fornecido para enriquecer o registo. Por exemplo, o Serilog usa o nome do evento.
O Action é invocado através de um método de extensão fortemente tipado. O PriorityItemProcessed método regista uma mensagem sempre que um item de trabalho é processado.
FailedToProcessWorkItem é chamado se e quando ocorre uma exceção:
protected override async Task ExecuteAsync(
CancellationToken stoppingToken)
{
using (IDisposable? scope = logger.ProcessingWorkScope(DateTime.Now))
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
WorkItem? nextItem = priorityQueue.ProcessNextHighestPriority();
if (nextItem is not null)
{
logger.PriorityItemProcessed(nextItem);
}
}
catch (Exception ex)
{
logger.FailedToProcessWorkItem(ex);
}
await Task.Delay(1_000, stoppingToken);
}
}
}
Verificar a saída do console da aplicação:
crit: WorkerServiceOptions.Example.Worker[13]
Epic failure processing item!
System.Exception: Failed to verify communications.
at WorkerServiceOptions.Example.Worker.ExecuteAsync(CancellationToken stoppingToken) in
..\Worker.cs:line 27
Para passar parâmetros a uma mensagem logaritária, defina até seis tipos ao criar o campo estático. A aplicação de exemplo regista os detalhes do item de trabalho ao processar itens definindo um WorkItem tipo para o Action campo:
private static readonly Action<ILogger, WorkItem, Exception> s_processingPriorityItem;
O modelo de mensagem de registo do delegado recebe os seus valores provisórios dos tipos fornecidos. A aplicação de exemplo define um delegado para adicionar um item de trabalho onde o parâmetro do item é um WorkItem:
s_processingPriorityItem = LoggerMessage.Define<WorkItem>(
LogLevel.Information,
new EventId(1, nameof(PriorityItemProcessed)),
"Processing priority item: {Item}");
O método de extensão estática para registar que um item de trabalho está a ser processado, PriorityItemProcessed, recebe o valor do argumento do item de trabalho e passa-o ao Action delegado:
public static void PriorityItemProcessed(
this ILogger logger, WorkItem workItem) =>
s_processingPriorityItem(logger, workItem, default!);
No método ExecuteAsync do serviço de trabalhador, PriorityItemProcessed é chamado para registrar a mensagem:
protected override async Task ExecuteAsync(
CancellationToken stoppingToken)
{
using (IDisposable? scope = logger.ProcessingWorkScope(DateTime.Now))
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
WorkItem? nextItem = priorityQueue.ProcessNextHighestPriority();
if (nextItem is not null)
{
logger.PriorityItemProcessed(nextItem);
}
}
catch (Exception ex)
{
logger.FailedToProcessWorkItem(ex);
}
await Task.Delay(1_000, stoppingToken);
}
}
}
Inspecionar a saída do console da aplicação:
info: WorkerServiceOptions.Example.Worker[1]
Processing priority item: Priority-Extreme (50db062a-9732-4418-936d-110549ad79e4): 'Verify communications'
Otimizações controladas ao nível de registro
Pode otimizar o desempenho verificando o LogLevel com ILogger.IsEnabled(LogLevel) antes de invocar o método correspondente Log* . Quando o registo não está configurado para o dado LogLevel, ILogger.Log não é chamado. Além disso, evitam-se o boxing por tipo de valor e uma alocação de object[] (para representar os parâmetros).
Para obter mais informações, consulte: