Jeff Martin
Jeff Martin

Reputation: 11022

How to capture log4net output in xunit2

I've read http://xunit.github.io/docs/capturing-output.html and it seems to apply to making my test output specific message during the test running but I would really like to be able to capture the log4net output that is already integrated into the classes I am testing.

In the past i have set up log4net to use a TraceLogger and the test framework was able to associate the output with the test. (different testing framework). How can I somehow associate log4net output to the Xunit IOutputHelper?

Upvotes: 4

Views: 2245

Answers (2)

nolmit
nolmit

Reputation: 29

Based on solution described here https://github.com/damianh/CapturingLogOutputWithXunit2AndParallelTests#capturing-test-specific-log-output-when-using-xunit-2x-parallel-testing, i've rewrited it using log4net. May be it will helpful to somebody.

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);
            }
        }
    }

Upvotes: 2

Jeff Martin
Jeff Martin

Reputation: 11022

This was the answer i came up with

This is a class I can make my test class inherit from:

   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);
        }
    }

this is a custom Appender I made referenced in the above helper:

 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));
        }
    }

This could be customized more to take a custom layout or whatever...

Finally - i just make my test class inherit from this helper:

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

You could give your tests direct access to the output object too...

Upvotes: 4

Related Questions