如何在 xunit2 中捕获 log4net 输出

How to capture log4net output in xunit2

我读过 http://xunit.github.io/docs/capturing-output.html 它似乎适用于在测试期间使我的测试输出特定消息 运行 但我真的希望能够捕获已经存在的 log4net 输出集成到我正在测试的类

过去我将 log4net 设置为使用 TraceLogger,并且测试框架能够将输出与测试相关联。 (不同的测试框架)。我怎样才能以某种方式将 log4net 输出关联到 Xunit IOutputHelper?

这是我想出的答案

这是一个 class 我可以让我的测试 class 继承自:

   public class LogOutputTester:IDisposable
    {
        private readonly IAppenderAttachable _attachable;
        private TestOutputAppender _appender;

        protected LogOutputTester(ITestOutputHelper output)
        {
            log4net.Config.XmlConfigurator.Configure(); 
            var root = ((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
            _attachable = root;

            _appender = new TestOutputAppender(output);
            if (_attachable != null)
                _attachable.AddAppender(_appender);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            _attachable.RemoveAppender(_appender);
        }
    }

这是我在上面的帮助程序中引用的自定义 Appender:

 public class TestOutputAppender : AppenderSkeleton
    {
        private readonly ITestOutputHelper _xunitTestOutputHelper;

        public TestOutputAppender(ITestOutputHelper xunitTestOutputHelper)
        {
            _xunitTestOutputHelper = xunitTestOutputHelper;
            Name = "TestOutputAppender";
            Layout = new PatternLayout("%-5p %d %5rms %-22.22c{1} %-18.18M - %m%n");
        }

        protected override void Append(LoggingEvent loggingEvent)
        {
            _xunitTestOutputHelper.WriteLine(RenderLoggingEvent(loggingEvent));
        }
    }

这可以进一步自定义以采用自定义布局或其他...

最后 - 我只是让我的测试 class 继承自这个助手:

   public class MyTestClass:LogOutputTester
    {
        public EdgeClientTests(ITestOutputHelper output):base(output)
        {
        }
    ...

您也可以让您的测试直接访问输出对象...

基于此处描述的解决方案 https://github.com/damianh/CapturingLogOutputWithXunit2AndParallelTests#capturing-test-specific-log-output-when-using-xunit-2x-parallel-testing,我使用 log4net 重写了它。 可能对某人有帮助。

public static class LogHelper
    {
        private static readonly Subject<LoggingEvent> LogEventSubject = new Subject<LoggingEvent>();
        private const string CaptureCorrelationIdKey = "EventId";
        private static readonly ILayout layout;

        static LogHelper()
        {
            XmlConfigurator.Configure();
            var root = ((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
            IAppenderAttachable attachable = root;
            var appender = new XunitAppender(logEvent => LogEventSubject.OnNext(logEvent));
            attachable?.AddAppender(appender);
            layout = appender.Layout;
            appender.ActivateOptions();
        }

        public static IDisposable Capture(ITestOutputHelper testOutputHelper)
        {

            var captureId = Guid.NewGuid();
            Func<LoggingEvent, bool> filter = logEvent =>
                logEvent.GetProperties().Contains(CaptureCorrelationIdKey) &&
                logEvent.LookupProperty(CaptureCorrelationIdKey).ToString() == captureId.ToString();

            var subscription = LogEventSubject.Where(filter).Subscribe(logEvent =>
            {
                using (var writer = new StringWriter())
                {
                    layout.Format(writer, logEvent);
                    testOutputHelper.WriteLine(writer.ToString());
                }
            });

            ThreadContext.Properties[CaptureCorrelationIdKey] = captureId.ToString();

            return new DisposableAction(() =>
            {
                subscription.Dispose();
                ThreadContext.Properties.Clear();
            });
        }

        private class DisposableAction : IDisposable
        {
            private readonly Action _action;

            public DisposableAction(Action action)
            {
                _action = action;
            }

            public void Dispose()
            {
                _action();
            }
        }

        public sealed class XunitAppender : AppenderSkeleton
        {
            private readonly Action<LoggingEvent> _action;

            public XunitAppender(Action<LoggingEvent> action)
            {
                _action = action;
                Name = "XunitAppender";
                Layout = new PatternLayout("%date %-5level %logger - %message");
            }

            protected override void Append(LoggingEvent loggingEvent)
            {
                _action(loggingEvent);
            }
        }
    }