Compartir a través de


Almacenamiento en búfer de registros en .NET

.NET proporciona funcionalidades de almacenamiento en búfer de registros que permiten retrasar la emisión de registros hasta que se cumplan determinadas condiciones. El almacenamiento en búfer de registros es útil en escenarios en los que desea:

  • Recopile todos los registros de una operación específica antes de decidir si desea emitirlos.
  • Impedir que se emitan registros durante la operación normal, pero emitirlos cuando se produzcan errores.
  • Optimice el rendimiento al reducir el número de registros escritos en el almacenamiento.

Los registros están almacenados en búferes circulares temporales en la memoria del proceso, y se aplican las siguientes condiciones:

  • Si el búfer está lleno, los registros más antiguos se quitan y nunca se emiten.
  • Si desea emitir los registros almacenados en búfer, puede llamar a Flush() en la clase GlobalLogBuffer o la clase PerRequestLogBuffer.
  • Si nunca vacía los búferes, los registros almacenados en búfer se perderán eventualmente durante la ejecución de la aplicación, por lo que se comporta efectivamente como si esos registros estuvieran deshabilitados.

Hay dos estrategias de almacenamiento en búfer disponibles.

  • Almacenamiento en búfer global: gestiona los registros en toda la aplicación.
  • Almacenamiento por solicitud: almacena los registros de cada solicitud HTTP individual si es posible; de lo contrario, los almacena en el búfer global.

Nota:

El almacenamiento en búfer de registros está disponible en .NET 9 y versiones posteriores.

El almacenamiento en búfer de registros funciona con todos los proveedores de registro. Si un proveedor de registro que usa no implementa la IBufferedLogger interfaz, el almacenamiento en búfer de registros llamará a los métodos de registro directamente en cada registro almacenado en búfer al vaciar el búfer.

El almacenamiento en búfer de registros amplía las funcionalidades de filtrado al permitirle capturar y almacenar registros temporalmente. En lugar de tomar una decisión inmediata de emisión o descarte, el almacenamiento en búfer le permite almacenar los registros en la memoria y decidir más adelante si desea emitirlos.

Comienza

Para empezar, instale el 📦 paquete NuGet Microsoft.Extensions.Telemetry para el almacenamiento en búfer global. O bien, instale el 📦 paquete NuGet Microsoft.AspNetCore.Diagnostics.Middleware para el almacenamiento en búfer por solicitud.

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

Para obtener más información sobre cómo agregar paquetes, consulte dotnet add package o Manage package dependencies in .NET applications (Administración de dependencias de paquetes en aplicaciones .NET).

Almacenamiento en búfer global

El búfer global permite almacenar registros a lo largo de toda la aplicación. Puede configurar qué registros se almacenarán en búfer mediante reglas de filtro y, a continuación, vaciar el búfer según sea necesario para emitir esos registros.

Configuración sencilla

Para habilitar el almacenamiento en búfer global en o por debajo de un nivel de registro específico, especifique ese nivel:

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

La configuración anterior habilita el almacenamiento en búfer de los registros con nivel LogLevel.Information o inferior.

Configuración basada en archivos

Cree una sección de configuración en el appsettings.json, por ejemplo:

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

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

La configuración anterior:

  • Almacena en búfer los registros de las categorías que comienzan con BufferingDemo y que tienen un nivel de LogLevel.Information o inferior.
  • Almacena en búfer todos los registros con el identificador de evento 1001.
  • Establece el tamaño máximo del búfer en aproximadamente 100 MB.
  • Establece el tamaño máximo del registro en 50 KB.
  • Establece una duración de vaciado automático de 30 segundos después del vaciado manual.

Para registrar el almacenamiento en búfer de registros con la configuración, considere el siguiente código:

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

Configuración de código insertado

// 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));
});

La configuración anterior:

  • Almacena en búfer los registros de las categorías que comienzan con BufferingDemo y que tienen un nivel de LogLevel.Information o inferior.
  • Almacena en búfer todos los registros con el identificador de evento 1001.
  • Establece el tamaño máximo del búfer en aproximadamente 100 MB.
  • Establece el tamaño máximo del registro en 50 KB.
  • Establece una duración de vaciado automático de 30 segundos después del vaciado manual.

Vaciado del búfer

Para vaciar los registros almacenados en búfer, inserte la GlobalLogBuffer clase abstracta y llame al 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.
    }
}

Buffering por solicitud

El almacenamiento en búfer por solicitud es específico de ASP.NET aplicaciones principales y permite almacenar en búfer los registros de forma independiente para cada solicitud HTTP. El búfer de cada solicitud respectiva se crea cuando se inicia y elimina cuando finaliza la solicitud, por lo que si no vacía el búfer, los registros se perderán cuando finalice la solicitud. De este modo, resulta útil vaciar los búferes solo cuando realmente lo necesites, como cuando se produce un error.

El almacenamiento en búfer por solicitud está estrechamente unido al almacenamiento en búfer global. Si se supone que una entrada de registro se va a almacenar en un búfer específico de la solicitud, pero no hay ningún contexto HTTP activo en el momento del intento de almacenamiento en búfer, se almacenará en el búfer global en su lugar. Si se desencadena el vaciado de búfer, el búfer por solicitud se vaciará primero, seguido del búfer global.

Configuración sencilla

Para almacenar en búfer solo los registros en un nivel de registro específico o por debajo de este:

builder.Logging.AddPerIncomingRequestBuffer(LogLevel.Information);

Configuración basada en archivos

Cree una sección de configuración en el appsettings.json:

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

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

La configuración anterior:

  • Almacena en búfer los registros de las categorías que comienzan con PerRequestLogBufferingFileBased. y que tienen un nivel de LogLevel.Information o inferior.
  • Establece una duración de vaciado automático de 5 segundos después del vaciado manual.

Para registrar el almacenamiento en búfer de registros con la configuración, considere el siguiente código:

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

Configuración de código insertado

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

La configuración anterior:

  • Almacena en búfer los registros de las categorías que comienzan con PerRequestLogBufferingFileBased. y que tienen un nivel de LogLevel.Information o inferior.
  • Establece una duración de vaciado automático de 5 segundos después del vaciado manual.

Vaciando el búfer para cada solicitud

Para vaciar los registros almacenados en búfer para la solicitud actual, inserte la PerRequestLogBuffer clase abstracta y llame a su 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.");
        }
    }
}

Nota:

Vaciar el búfer por solicitud también vacía el búfer global.

Cómo se aplican las reglas de administración de búfer

La evaluación de las reglas de buffering del log se realiza en cada registro. El siguiente algoritmo se usa para cada registro de log:

  1. Si una entrada de registro coincide con alguna regla, se guarda temporalmente en vez de emitirse inmediatamente.
  2. Si una entrada de registro no coincide con ninguna regla, se emite normalmente.
  3. Si se alcanza el límite de tamaño del búfer, se quitan las entradas de registro almacenadas en el búfer más antiguas (¡no emitidas!) para hacer espacio para nuevas.
  4. Si un tamaño de entrada de registro es mayor que el tamaño máximo del registro, no se almacenará en búfer y se emitirá normalmente.

Para cada registro, el algoritmo comprueba:

  • Si el nivel de registro coincide (es igual o inferior a) el nivel de registro de la regla.
  • Si el nombre de la categoría comienza con el prefijo CategoryName de la regla.
  • Si el identificador de evento coincide con el EventId valor de la regla.
  • Si el nombre del evento coincide con EventName de la regla.
  • Si algún atributo coincide con el criterio de la regla Attributes.

Cambiar las reglas de filtrado de búfer en una aplicación en ejecución

Tanto el almacenamiento en búfer global como el almacenamiento en búfer por solicitud admiten actualizaciones de configuración en tiempo de ejecución a través de la IOptionsMonitor<TOptions> interfaz . Si usa un proveedor de configuración que admite recargas(como el proveedor de configuración de archivos), puede actualizar las reglas de filtrado en tiempo de ejecución sin reiniciar la aplicación.

Por ejemplo, puede iniciar la aplicación con el siguiente appsettings.json, que permite el almacenamiento en búfer de registros para los registros con el nivel y la LogLevel.Information categoría a partir de PerRequestLogBufferingFileBased.:

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

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

Mientras se ejecuta la aplicación, puede actualizar el appsettings.json con la siguiente configuración:

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

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

Las nuevas reglas se aplican automáticamente. Por ejemplo, con la configuración anterior, todos los registros con el nivel LogLevel.Information serán almacenados en el búfer.

Consideraciones sobre el rendimiento

El almacenamiento en búfer de registros ofrece un equilibrio entre el uso de memoria y los costos de almacenamiento de registros. El uso de buffer de registros en memoria le permite:

  1. Emita registros de forma selectiva en función de las condiciones en tiempo de ejecución.
  2. Quite los registros innecesarios sin escribirlos en el almacenamiento.

Sin embargo, tenga en cuenta el consumo de memoria, especialmente en aplicaciones de alto rendimiento. Configure los límites de tamaño del búfer adecuados para evitar un uso excesivo de memoria.

procedimientos recomendados

  • Establezca los límites de tamaño de búfer adecuados en función de las restricciones de memoria de la aplicación.
  • Utiliza el almacenamiento en búfer para cada solicitud en las aplicaciones web para aislar los registros.
  • Configure cuidadosamente la duración del vaciado automático para equilibrar el uso de memoria y la disponibilidad del registro.
  • Implemente desencadenadores de vaciado explícitos para eventos importantes (como errores y advertencias).
  • Supervise el uso de memoria del búfer en producción para asegurarse de que permanece dentro de los límites aceptables.

Limitaciones

  • El almacenamiento en búfer de registros no se admite en .NET 8 ni en versiones anteriores.
  • No se garantiza que se conserve el orden de los registros. Sin embargo, se conservan las marcas de tiempo originales.
  • No se admite la configuración personalizada por cada proveedor de registro. La misma configuración se usa para todos los proveedores.
  • No se admiten ámbitos de registro. Esto significa que si usa el BeginScope método , los registros de registro almacenados en búfer no se asociarán al ámbito.
  • No se conserva toda la información del registro original. El registro en búfer utiliza internamente la clase BufferedLogRecord al vaciar, y las siguientes de sus propiedades siempre están vacías:

Consulte también