Skip to content

Instantly share code, notes, and snippets.

@markotny
Created March 29, 2022 17:53
Show Gist options
  • Save markotny/78c0a37e1b7c003c5c8e7dab6817550c to your computer and use it in GitHub Desktop.
Save markotny/78c0a37e1b7c003c5c8e7dab6817550c to your computer and use it in GitHub Desktop.
xUnit ITestOutputHelper integration with MEL
using Microsoft.Extensions.Logging;
namespace XunitLogging;
/// <summary>
/// Buffers all logs in a queue until an actual ILogger is registered
/// </summary>
internal class TestOutputBuffer : ILogger
{
private readonly Queue<Action<ILogger>> _bufferedLogs = new();
private ILogger? _output;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (_output is not null)
Try(() => _output.Log(logLevel, eventId, state, exception, formatter));
else
_bufferedLogs.Enqueue(output => output.Log(logLevel, eventId, state, exception, formatter));
}
public void RegisterOutput(ILogger output)
{
_output = output;
Try(() =>
{
while (_bufferedLogs.Count > 0)
_bufferedLogs.Dequeue()(output);
});
}
private static void Try(Action logAction)
{
try
{
logAction();
}
catch (Exception)
{
/* test might have already ended and output is no longer available (e.g. during async disposing) */
}
}
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
public IDisposable BeginScope<TState>(TState state) => null!;
}
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;
namespace XunitLogging;
/// <summary>
/// Provides buffers for each logger category until an ITestOutputHelper is available.
/// Allows capturing logs during TestHost startup and fixture initialization
/// </summary>
/// <example>
/// <code>
/// public TestOutputBufferProvider LoggingBufferProvider = new();
/// // ...
/// void SetLogging(IWebHostBuilder builder) =&gt; builder
/// .ConfigureLogging(logging =&gt; logging.AddProvider(LoggingBufferProvider));
/// // ...
/// public class SomeTests : IClassFixture&lt;SomeFixture&gt;
/// {
/// private readonly SomeFixture fixture;
///
/// public SomeTests(ITestOutputHelper output, SomeTestsFixture fixture)
/// {
/// fixture.LoggingBufferProvider.RegisterOutput(output);
/// this.fixture = fixture;
/// }
/// }
/// </code>
/// </example>
public class TestOutputBufferProvider : ILoggerProvider
{
private readonly ConcurrentDictionary<string, TestOutputBuffer> _buffers = new();
public ILogger CreateLogger(string categoryName)
=> _buffers.GetOrAdd(categoryName, name => new TestOutputBuffer());
public void RegisterOutput(ITestOutputHelper output)
{
foreach (var (category, buffer) in _buffers)
{
// requires Divergic.Logging.Xunit
var logger = output.BuildLogger(category);
buffer.RegisterOutput(logger);
}
}
public void Dispose() { }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment