Created
March 29, 2022 17:53
-
-
Save markotny/78c0a37e1b7c003c5c8e7dab6817550c to your computer and use it in GitHub Desktop.
xUnit ITestOutputHelper integration with MEL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) => builder | |
/// .ConfigureLogging(logging => logging.AddProvider(LoggingBufferProvider)); | |
/// // ... | |
/// public class SomeTests : IClassFixture<SomeFixture> | |
/// { | |
/// 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