Compartilhar via


Registro em buffer no .NET

O .NET fornece recursos de buffer de log que permitem atrasar a emissão de logs até que determinadas condições sejam atendidas. O buffer de log é útil em cenários em que você deseja:

  • Colete todos os logs de uma operação específica antes de decidir se deseja emitê-los.
  • Impedir que os logs sejam emitidos durante a operação normal, mas emiti-los quando ocorrerem erros.
  • Otimize o desempenho reduzindo o número de logs gravados no armazenamento.

Os logs em buffer são armazenados em buffers circulares temporários na memória do processo e as seguintes condições se aplicam:

  • Se o buffer estiver cheio, os logs mais antigos serão descartados e nunca emitidos.
  • Se você quiser emitir os logs em buffer, poderá chamar Flush() na classe GlobalLogBuffer ou PerRequestLogBuffer.
  • Se você nunca esvaziar os buffers, os logs em buffer eventualmente serão descartados à medida que o aplicativo for executado, portanto, ele agirá como se esses logs fossem desabilitados.

Há duas estratégias de buffer disponíveis:

  • Buffereamento global: regista logs em todo o aplicativo.
  • Buffering por solicitação: bufferiza logs para cada solicitação HTTP individual, se disponível; caso contrário, bufferiza para o buffer global.

Observação

O buffer de log está disponível no .NET 9 e versões posteriores.

O buffer de log funciona com todos os provedores de log. Se um provedor de log que você usa não implementar a IBufferedLogger interface, o buffer de log chamará diretamente os métodos de log em cada registro de log em buffer ao despejar o buffer.

O buffer de log amplia os recursos de filtragem permitindo que você capture e armazene logs temporariamente. Em vez de tomar uma decisão imediata de emitir ou descartar, o buffer permite que você mantenha os logs na memória e decida mais tarde se deseja emitê-los.

Comece agora

Para começar, instale o 📦 pacote NuGet Microsoft.Extensions.Telemetry para buffer global. Ou instale o📦 pacote NuGet Microsoft.AspNetCore.Diagnostics.Middleware para armazenamento em buffer por solicitação.

dotnet add package Microsoft.Extensions.Telemetry
dotnet add package Microsoft.AspNetCore.Diagnostics.Middleware

Para obter mais informações sobre como adicionar pacotes, consulte dotnet add package or Manage package dependencies in .NET applications.

Buffer global

O buffer global permite que você faça a bufferização de logs em toda a aplicação. Você pode configurar quais logs armazenar em buffer usando regras de filtro e, em seguida, esvaziar o buffer conforme necessário para emitir esses logs.

Configuração simples

Para habilitar o buffer global em ou abaixo de um nível de log específico, especifique esse nível:

// Add the Global buffer to the logging pipeline.
hostBuilder.Logging.AddGlobalBuffer(LogLevel.Information);

A configuração anterior permite o armazenamento em buffer dos logs com nível LogLevel.Information para baixo.

Configuração baseada em arquivo

Crie uma seção de configuração em seu appsettings.json, por exemplo:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    },

    "GlobalLogBuffering": {
      "MaxBufferSizeInBytes": 104857600,
      "MaxLogRecordSizeInBytes": 51200,
      "AutoFlushDuration": "00:00:30",
      "Rules": [
        {
          "CategoryName": "BufferingDemo",
          "LogLevel": "Information"
        },
        {
          "EventId": 1001
        }
      ]
    }
  }
}

A configuração anterior:

  • Buffer logs de categorias começando com BufferingDemo com nível LogLevel.Information e níveis inferiores.
  • Armazena em buffer todos os logs com a ID do evento 1001.
  • Define o tamanho máximo do buffer como aproximadamente 100 MB.
  • Define o tamanho máximo do registro de log como 50 KB.
  • Estabelece uma duração de limpeza automática de 30 segundos após a limpeza manual.

Para registrar o buffer de log com a configuração, considere o seguinte código:

// Add the Global buffer to the logging pipeline.
hostBuilder.Logging.AddGlobalBuffer(hostBuilder.Configuration.GetSection("Logging"));

Configuração de código em linha

// Add the Global buffer to the logging pipeline.
hostBuilder.Logging.AddGlobalBuffer(options =>
{
    options.MaxBufferSizeInBytes = 104857600; // 100 MB
    options.MaxLogRecordSizeInBytes = 51200; // 50 KB
    options.AutoFlushDuration = TimeSpan.FromSeconds(30);
    options.Rules.Add(new LogBufferingFilterRule(
        categoryName: "BufferingDemo",
        logLevel: LogLevel.Information));
    options.Rules.Add(new LogBufferingFilterRule(eventId: 1001));
});

A configuração anterior:

  • Buffer logs de categorias começando com BufferingDemo com nível LogLevel.Information e níveis inferiores.
  • Armazena em buffer todos os logs com a ID do evento 1001.
  • Define o tamanho máximo do buffer como aproximadamente 100 MB.
  • Define o tamanho máximo do registro de log como 50 KB.
  • Estabelece uma duração de limpeza automática de 30 segundos após a limpeza manual.

Liberando o buffer

Para liberar os logs em buffer, injete a GlobalLogBuffer classe abstrata e chame o Flush() método:

public class MyService
{
    private readonly GlobalLogBuffer _buffer;

    public MyService(GlobalLogBuffer buffer)
    {
        _buffer = buffer;
    }

    public void HandleException(Exception ex)
    {
        _buffer.Flush();

        // After flushing, log buffering will be temporarily suspended (= all logs will be emitted immediately)
        // for the duration specified by AutoFlushDuration.
    }
}

Buffer por solicitação

O buffer por solicitação é específico para aplicativos do ASP.NET Core e permite que você faça buffer de logs de forma independente para cada solicitação HTTP. O buffer para cada solicitação respectiva é criado quando a solicitação é iniciada e descartada quando a solicitação termina, portanto, se você não liberar o buffer, os logs serão perdidos quando a solicitação terminar. Dessa forma, é útil apenas liberar buffers quando você realmente precisa, como quando ocorre um erro.

O buffer por solicitação é fortemente acoplado ao buffer global. Se uma entrada de log deve ser colocada em um buffer por solicitação, mas não houver nenhum contexto HTTP ativo no momento da tentativa de colocação em buffer, ela será colocada no buffer global. Se o esvaziamento do buffer for disparado, o buffer por solicitação será esvaziado primeiro, seguido pelo buffer global.

Configuração simples

Para armazenar em buffer apenas logs em ou abaixo de um nível de log específico:

builder.Logging.AddPerIncomingRequestBuffer(LogLevel.Information);

Configuração baseada em arquivo

Crie uma seção de configuração em seu appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.*": "None"
    },

    "PerIncomingRequestLogBuffering": {
      "AutoFlushDuration": "00:00:05",
      "Rules": [
        {
          "CategoryName": "PerRequestLogBufferingFileBased.*",
          "LogLevel": "Information"
        }
      ]
    }
  }
}

A configuração anterior:

  • Buffer logs de categorias começando com PerRequestLogBufferingFileBased. com nível LogLevel.Information e níveis inferiores.
  • Define uma duração de liberação automática de 5 segundos após a liberação manual.

Para registrar o buffer de log com a configuração, considere o seguinte código:

builder.Logging.AddPerIncomingRequestBuffer(builder.Configuration.GetSection("Logging"));

Configuração de código em linha

builder.Logging.AddPerIncomingRequestBuffer(options =>
{
    options.AutoFlushDuration = TimeSpan.FromSeconds(5);
    options.Rules.Add(new Microsoft.Extensions.Diagnostics.Buffering.LogBufferingFilterRule("PerRequestLogBufferingCodeBased.*", LogLevel.Information));
});

A configuração anterior:

  • Buffer logs de categorias começando com PerRequestLogBufferingFileBased. com nível LogLevel.Information e níveis inferiores.
  • Define uma duração de liberação automática de 5 segundos após a liberação manual.

Liberando o buffer a cada solicitação

Para liberar os logs em buffer para a solicitação atual, injete a PerRequestLogBuffer classe abstrata e chame seu Flush() método:

[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
    private readonly ILogger<HomeController> _logger;
    private readonly PerRequestLogBuffer _buffer;

    public HomeController(ILogger<HomeController> logger, PerRequestLogBuffer buffer)
    {
        _logger = logger;
        _buffer = buffer;
    }

    [HttpGet("index/{id}")]
    public IActionResult Index(int id)
    {
        try
        {
            _logger.RequestStarted(id);

            // Simulate exception every 10th request
            if (id % 10 == 0)
            {
                throw new Exception("Simulated exception in controller");
            }

            _logger.RequestEnded(id);

            return Ok();
        }
        catch
        {
            _logger.ErrorMessage(id);
            _buffer.Flush();

            _logger.ExceptionHandlingFinished(id);

            return StatusCode(500, "An error occurred.");
        }
    }
}

Observação

A liberação do buffer por solicitação também libera o buffer global.

Como as regras de buffer são aplicadas

A avaliação das regras de bufferização do log é realizada em cada registro de log. O algoritmo a seguir é usado para cada registro de log:

  1. Se uma entrada de log corresponder a qualquer regra, ela será armazenada em buffer em vez de ser emitida imediatamente.
  2. Se uma entrada de log não corresponder a nenhuma regra, ela será emitida normalmente.
  3. Se o limite de tamanho do buffer for atingido, as entradas de log em buffer mais antigas serão descartadas (não emitidas!) para abrir espaço para novas.
  4. Se um tamanho de entrada de log for maior que o tamanho máximo do registro de log, ele não será armazenado em buffer e será emitido normalmente.

Para cada registro de log, o algoritmo verifica:

  • Se o nível de log corresponder (é igual ou inferior) ao nível de log da regra.
  • Se o nome da categoria começar com o CategoryName prefixo da regra.
  • Se a ID do evento corresponder à regra EventId.
  • Se o nome do evento corresponder ao da regraEventName.
  • Se algum atributo corresponder ao da Attributesregra.

Alterar regras de filtragem de buffer em um aplicativo em execução

O buffer global e o buffer por solicitação dão suporte a atualizações de configuração em tempo de execução por meio da IOptionsMonitor<TOptions> interface. Se você estiver usando um provedor de configuração que dê suporte a recarregamentos, como o Provedor de Configuração de Arquivos, você poderá atualizar as regras de filtragem em tempo de execução sem reiniciar o aplicativo.

Por exemplo, você pode iniciar seu aplicativo com o seguinte appsettings.json, que permite o buffer de log para logs com o nível e a LogLevel.Information categoria começando com PerRequestLogBufferingFileBased.:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.*": "None"
    },

    "PerIncomingRequestLogBuffering": {
      "AutoFlushDuration": "00:00:05",
      "Rules": [
        {
          "CategoryName": "PerRequestLogBufferingFileBased.*",
          "LogLevel": "Information"
        }
      ]
    }
  }
}

Enquanto o aplicativo estiver em execução, você pode atualizar o appsettings.json com a seguinte configuração:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.*": "None"
    },

    "PerIncomingRequestLogBuffering": {
      "Rules": [
        {
          "LogLevel": "Information"
        }
      ]
    }
  }
}

As novas regras são aplicadas automaticamente. Por exemplo, com a configuração anterior, todos os logs com o LogLevel.Information nível serão armazenados em buffer.

Considerações sobre desempenho

O buffer de log oferece uma compensação entre o uso de memória e os custos de armazenamento de log. O armazenamento de logs em memória permite que você:

  1. Emita logs seletivamente com base em condições de tempo de execução.
  2. Elimine logs desnecessários sem armazená-los.

No entanto, esteja atento ao consumo de memória, especialmente em aplicativos de alta taxa de transferência. Configure os limites de tamanho de buffer apropriados para evitar o uso excessivo de memória.

Práticas recomendadas

  • Defina os limites de tamanho de buffer apropriados com base nas restrições de memória do aplicativo.
  • Use o buffer por solicitação para aplicativos Web para isolar logs por solicitação.
  • Configure a duração da liberação automática cuidadosamente para equilibrar o uso de memória e a disponibilidade de log.
  • Implemente gatilhos de liberação explícitos para eventos importantes (como erros e avisos).
  • Monitore o uso de memória do buffer na produção para garantir que ele permaneça dentro dos limites aceitáveis.

Limitações

  • Não há suporte para buffer de log no .NET 8 e versões anteriores.
  • Não há garantia de que a ordem dos logs seja preservada. No entanto, os carimbos de data/hora originais são preservados.
  • Não há suporte para a configuração personalizada por cada provedor de log. A mesma configuração é usada para todos os provedores.
  • Não há suporte para escopos de log. Isso significa que, se você usar o BeginScope método, os registros de log em buffer não serão associados ao escopo.
  • Nem todas as informações do registro de log original são preservadas. O buffer de log usa internamente a classe BufferedLogRecord ao liberar, e as seguintes propriedades dela estão sempre vazias:

Consulte também