When using Microsoft Logging this code allows you to create a FakeLoggerFactory that creates named FakeLoggers that all log to one Combined logger. This is useful for unit testing.
// You can define other methods, fields, classes and namespaces here
// What does this do? https://www.tutorialsteacher.com/core/fundamentals-of-logging-in-dotnet-core
/// <summary>
/// FakeLoggerFactory used in Unit testing. It creates loggers that log to a <see cref="CombinedLogging"/> object
/// </summary>
/// <inheritdoc />
public sealed class FakeLoggerFactory : ILoggerFactory
{
readonly CombinedLogging _combinedLogging = new();
/// <summary>
/// Creates a new <see cref="FakeLoggerFactory"/> instance.
/// </summary>
public FakeLoggerFactory() { }
/// <inheritdoc />
/// <remarks>
/// This returns a <see cref="FakeLogger2"/> instance of the given category.
/// </remarks>
public ILogger CreateLogger(string categoryName)
{
string tmp = $"CreateLogger {categoryName}";
Console.WriteLine(tmp);
return new FakeLogger2(categoryName, _combinedLogging);
}
/// <inheritdoc />
public void AddProvider(ILoggerProvider provider)
{
}
/// <inheritdoc />
public void Dispose()
{
}
/// <summary>
/// Enumerate the combined log entries
/// </summary>
/// <returns></returns>
public IEnumerable<LogEntry> Logs()
{
return _combinedLogging.Logs();
}
}
[ExcludeFromCodeCoverage]
public class FakeLogger2 : ILogger
{
private CombinedLogging _combinedLogging;
private readonly string _category;
/// <summary>
/// Create a Fake Logger
/// </summary>
/// <param name="category">Specifying the category of the logging</param>
/// <param name="combinedLogging">Combines the logging from all FakeLogger2s</param>
public FakeLogger2(string category, CombinedLogging combinedLogging)
{
_category = category;
_combinedLogging = combinedLogging;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
{
return new NoOpDisposable();
}
private sealed class NoOpDisposable : IDisposable
{
public void Dispose()
{
}
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
_combinedLogging.Log(_category, logLevel, eventId, state, exception, formatter);
}
}
/// <summary>
/// To record a LogEntry
/// </summary>
/// <param name="Level">The LogLevel of the entry</param>
/// <param name="Category">The category of the entry, so an ILogger<T> will
/// log to a category of T where T is the type as a string</param>
/// <param name="Text">Logged text</param>
[ExcludeFromCodeCoverage]
public sealed record LogEntry(LogLevel Level, string Category, string Text)
{
/// <summary>
/// For Logger logging type T which has as a fully qualified type name of X.Y.Z,
/// ie. ILogger<T> we usually are only interested in the last part (class name), "Z"
/// </summary>
/// <returns>Class name part of the Type</returns>
public string MinimalCategory()
{
Debug.Assert(Category is not null, $"Field {nameof(Category)} is null");
var span = Category.AsSpan();
int ix = span.LastIndexOf('.'); // Look for last period/full stop character
if (ix > 0)
{
span = span[ix..];
}
return span.ToString();
}
public override string ToString()
{
return $"{DateTime.UtcNow.ToRoundTripFormatString()} - {Level.ToString()[0..3].ToUpperInvariant()} - {MinimalCategory()} - {Text}";
}
}
/// <summary>
/// Used to combine the logging of all FakeLogger2 loggers
/// </summary>
[ExcludeFromCodeCoverage]
public class CombinedLogging
{
/// <summary>
/// Store the logged lines by logging level here in logged order
/// </summary>
private readonly List<LogEntry> _allLogs = new();
public CombinedLogging()
{
}
public void Log<TState>(string category, LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
string message = "";
if (formatter != null)
{
message += formatter(state, exception);
}
var logLeveChar = logLevel.ToString()[0];
Console.WriteLine($"{logLeveChar} - {category} - {message}");
_allLogs.Add(new LogEntry(logLevel, category, $"{message}"));
}
/// <summary>
/// Retrieve All Logging
/// </summary>
/// <returns>All logging</returns>
public IEnumerable<LogEntry> Logs()
{
foreach (var line in _allLogs)
{
yield return line;
}
}
}
No comments:
Post a Comment