Skip to content

Instantly share code, notes, and snippets.

@dennisroche
Last active March 11, 2018 06:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dennisroche/530234f16a94c8c1ee1a41bd94c1ba88 to your computer and use it in GitHub Desktop.
Save dennisroche/530234f16a94c8c1ee1a41bd94c1ba88 to your computer and use it in GitHub Desktop.
xUnit2 and BDDfy logging with Serilog
internal static class LogHelper
{
private static readonly AsyncLocal<Guid> TestCorrelationId = new AsyncLocal<Guid>();
private static readonly ConcurrentDictionary<Guid, XUnitBddfyTextReporter> BddfyTextReporterLookup = new ConcurrentDictionary<Guid, XUnitBddfyTextReporter>();
private static readonly ConcurrentDictionary<Guid, ITestOutputHelper> LoggerLookup = new ConcurrentDictionary<Guid, ITestOutputHelper>();
private static readonly ConcurrentDictionary<Guid, ILogger> SerilogLookup = new ConcurrentDictionary<Guid, ILogger>();
public static IDisposable Capture(ITestOutputHelper outputHelper)
{
if (outputHelper == null)
throw new ArgumentNullException(nameof(outputHelper));
var correlationId = Guid.NewGuid();
LoggerLookup.TryAdd(correlationId, outputHelper);
BddfyTextReporterLookup.TryAdd(correlationId, new XUnitBddfyTextReporter());
SerilogLookup.TryAdd(correlationId, CreateLogger());
TestCorrelationId.Value = correlationId;
return new DelegateDisposable(() =>
{
LoggerLookup.TryRemove(correlationId, out ITestOutputHelper removedHelper);
BddfyTextReporterLookup.TryRemove(correlationId, out XUnitBddfyTextReporter removedBddfyTextReporter);
SerilogLookup.TryRemove(correlationId, out ILogger removedLogger);
});
}
public static bool TryGetTestOutputHelper(out ITestOutputHelper testOutputHelper)
{
return LoggerLookup.TryGetValue(TestCorrelationId.Value, out testOutputHelper);
}
public static bool TryBddfyReporter(out XUnitBddfyTextReporter reporter)
{
return BddfyTextReporterLookup.TryGetValue(TestCorrelationId.Value, out reporter);
}
public static bool TryGetLogger(out ILogger logger)
{
return SerilogLookup.TryGetValue(TestCorrelationId.Value, out logger);
}
static LogHelper()
{
Configurator.Processors.Add(() =>
{
TryBddfyReporter(out XUnitBddfyTextReporter reporter);
return reporter;
});
}
private static ILogger CreateLogger()
{
return new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("IdentityServer", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft", LogEventLevel.Fatal)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.Enrich.WithDemystifiedStackTraces()
.WriteTo.Sink<XUnitLogContextSink>()
.WriteTo.LiterateConsole()
.CreateLogger();
}
private class DelegateDisposable : IDisposable
{
public DelegateDisposable(Action action)
{
_action = action;
}
public void Dispose()
{
_action();
}
private readonly Action _action;
}
}
public class SerilogTestBase : IDisposable
{
protected SerilogTestBase(ITestOutputHelper outputHelper)
{
_logger = LogHelper.Capture(outputHelper);
}
public static ILogger Log
{
get
{
LogHelper.TryGetLogger(out ILogger log);
return log;
}
}
public virtual void Dispose()
{
_logger.Dispose();
}
private readonly IDisposable _logger;
}
internal class XUnitBddfyTextReporter : IProcessor
{
public void Process(Story story)
{
if (!LogHelper.TryGetTestOutputHelper(out ITestOutputHelper logger))
return;
var reporter = new TextReporter();
reporter.Process(story);
logger.WriteLine(reporter.ToString());
}
public ProcessType ProcessType => ProcessType.Report;
}
public class XUnitLogContextSink : ILogEventSink
{
public void Emit(LogEvent logEvent)
{
if (!LogHelper.TryGetTestOutputHelper(out ITestOutputHelper logger))
return;
var buffer = new StringBuilder();
using (var writer = new StringWriter(buffer))
logEvent.RenderMessage(writer, null);
logger.WriteLine(buffer.ToString());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment