InMemory Logging
Este conjunto de clases (InMemoryLoggerProvider
, InMemoryLoggerFactory
, y InMemoryLogger
) proporciona una solución de logging en memoria para entornos de prueba. Permite a los desarrolladores capturar y verificar mensajes de log generados por la aplicación durante la ejecución de pruebas unitarias y de integración, sin depender de un sistema de logging externo.
¿Cómo Funciona?
El logging en memoria funciona a través de una cadena de componentes:
InMemoryLoggerProvider
: Actúa como el proveedor central de loggers en memoria. Mantiene un diccionario deInMemoryLogger
s, asociando cada logger a una categoría específica.InMemoryLoggerFactory
: Es una fábrica de loggers que se encarga de crear instancias deInMemoryLogger
utilizando elInMemoryLoggerProvider
.InMemoryLogger
: Es el logger en sí, que almacena los mensajes de log en una lista en memoria.
Métodos
InMemoryLoggerProvider
CreateLogger(string categoryName)
: Crea una instancia deInMemoryLogger
o la recupera si ya existe para la categoría especificada.Dispose()
: Limpia la colección de loggers en memoria y libera los recursos asociados.
InMemoryLoggerFactory
CreateLogger(string categoryName)
: Crea una instancia deInMemoryLogger
utilizando elInMemoryLoggerProvider
.AddProvider(ILoggerProvider provider)
: Este método está implementado, pero no hace nada. La fabrica esta diseñada solo para usar unInMemoryLoggerProvider
.Dispose()
: Libera los recursos del proveedor.
InMemoryLogger
BeginScope<TState>(TState state)
: No implementado para este logger en memoria; retorna null.IsEnabled(LogLevel logLevel)
: Siempre retornatrue
, indicando que todos los niveles de log están habilitados.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
: Almacena el mensaje de log en una lista interna, formateándolo utilizando elformatter
proporcionado.
Implementación
Las clases de logging en memoria se implementan como sigue:
using System.Collections.Concurrent;
namespace CodeDesignPlus.Net.xUnit.Microservice.Server.Logger;
/// <summary>/// A logger provider that creates and manages in-memory loggers./// </summary>public class InMemoryLoggerProvider : ILoggerProvider{ private readonly ConcurrentDictionary<string, InMemoryLogger> loggers = new();
/// <summary> /// Gets the collection of in-memory loggers. /// </summary> public ConcurrentDictionary<string, InMemoryLogger> Loggers => loggers;
/// <summary> /// Creates a logger with the specified category name. /// </summary> /// <param name="categoryName">The category name for the logger.</param> /// <returns>An instance of <see cref="ILogger"/>.</returns> public ILogger CreateLogger(string categoryName) { return loggers.GetOrAdd(categoryName, _ => new InMemoryLogger()); }
/// <summary> /// Disposes the logger provider and suppresses finalization. /// </summary> public void Dispose() { loggers.Clear(); GC.SuppressFinalize(this); }}
namespace CodeDesignPlus.Net.xUnit.Microservice.Server.Logger;
/// <summary>/// A logger factory that uses an in-memory logger provider./// </summary>/// <param name="provider">The in-memory logger provider.</param>public class InMemoryLoggerFactory(InMemoryLoggerProvider provider) : ILoggerFactory{ /// <summary> /// Adds a logger provider to the factory. /// </summary> /// <param name="provider">The logger provider to add.</param> public void AddProvider(ILoggerProvider provider) {
}
/// <summary> /// Creates a logger with the specified category name. /// </summary> /// <param name="categoryName">The category name for the logger.</param> /// <returns>An instance of <see cref="ILogger"/>.</returns> public ILogger CreateLogger(string categoryName) { return provider.CreateLogger(categoryName); }
/// <summary> /// Disposes the logger factory and suppresses finalization. /// </summary> public void Dispose() { provider.Dispose(); GC.SuppressFinalize(this); }}
namespace CodeDesignPlus.Net.xUnit.Microservice.Server.Logger;
/// <summary>/// An in-memory logger that stores log messages in a list./// </summary>public class InMemoryLogger() : ILogger{ private readonly List<string> logs = [];
/// <summary> /// Begins a logical operation scope. /// </summary> /// <typeparam name="TState">The type of the state to begin scope for.</typeparam> /// <param name="state">The state to begin scope for.</param> /// <returns>An IDisposable that ends the logical operation scope on dispose.</returns> public IDisposable BeginScope<TState>(TState state) => null;
/// <summary> /// Checks if the given log level is enabled. /// </summary> /// <param name="logLevel">The log level to check.</param> /// <returns>True if the log level is enabled; otherwise, false.</returns> public bool IsEnabled(LogLevel logLevel) => true;
/// <summary> /// Writes a log entry. /// </summary> /// <typeparam name="TState">The type of the state object.</typeparam> /// <param name="logLevel">The log level.</param> /// <param name="eventId">The event ID.</param> /// <param name="state">The state object.</param> /// <param name="exception">The exception to log.</param> /// <param name="formatter">The function to create a log message from the state and exception.</param> public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { logs.Add(formatter(state, exception)); }
/// <summary> /// Gets the list of log messages. /// </summary> public List<string> Logs => logs;}
Ejemplo de Uso
Para usar el logging en memoria, se debe registrar el InMemoryLoggerProvider
y el InMemoryLoggerFactory
en la configuración de servicios de la aplicación de prueba. El siguiente ejemplo muestra cómo registrar estos servicios en el método ConfigureServices
de la clase Server<TProgram>
:
/// <summary> /// Configures the services for the application. /// </summary> /// <param name="services">The service collection.</param> public virtual void ConfigureServices(IServiceCollection services) { services.AddAuthentication("TestAuth") .AddScheme<AuthenticationSchemeOptions, AuthHandler>("TestAuth", options => { });
services.AddSingleton(this.LoggerProvider); services.AddSingleton<ILoggerFactory, InMemoryLoggerFactory>(); }
En este ejemplo:
- El
InMemoryLoggerProvider
se agrega como un singleton para que sea único en el scope de la aplicacion. - El
InMemoryLoggerFactory
se agrega como un singleton para que pueda ser injectado en cualquier parte de la aplicacion.
Luego, las pruebas pueden acceder al InMemoryLoggerProvider
para inspeccionar los mensajes de log generados durante la ejecución de la aplicación. Por ejemplo, desde la clase Server<TProgram>
:
/// <summary> /// Gets the in-memory logger provider for capturing log messages. /// </summary> public InMemoryLoggerProvider LoggerProvider { get; } = new();
Conclusiones
El logging en memoria proporciona una solución sencilla y efectiva para capturar y analizar mensajes de log durante las pruebas. Al no depender de un sistema de logging externo, se agiliza el proceso de pruebas y se facilita la verificación del comportamiento de la aplicación en relación con el registro de información.