NLog自定义LayoutRenderer将日志事件呈现为空字符串

时间:2018-01-19 12:34:46

标签: c# .net visual-studio continuous-integration nlog

这是我的环境:

  • Visual Studio 2017
  • Project的.NET运行时版本为4.6.2
  • XUnit版本2.3.1
  • NLog版本4.4.12
  • Fluent Assertions 4.19.4

问题

从下面的示例代码可以运行以重现问题,我们有2个自定义LayoutRendererRendererOneRendererTwo。然后分别在测试TestATestB中使用这两个。当我一次运行一个测试时,我没有遇到任何问题。但是,如果要通过XUnit的“全部运行”按钮在一个示例中运行它们,我会得到失败的断言,如下图所示:

enter image description here

""的Append方法遇到LogEventInfo对象时,附加到目标的渲染器似乎在输出中产生空字符串(LayoutRenderer)。正如在示例代码中可以看到的那样,我启用了InternalLogger但是,它没有告诉我可能出错的地方。我认为我已经在如何使用自定义渲染器方面做了一切正确的事情:

  • 注册呈现器(参考:TestATestB的构造函数)
  • 将它们分配给相关的TestTarget(参考:Log的构造函数)

我不知道为什么没有正确处理日志事件的布局没有注册。这是另一个屏幕截图,说明了这种情况:

enter image description here

请注意,上面的屏幕截图没有关联,但用于说明情况。任何解释/修复/如何解决问题都是最受欢迎的。

“准备好”运行代码

using FluentAssertions;
using NLog;
using NLog.Common;
using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Layouts;
using NLog.Targets;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using Xunit;

namespace LoggingTests
{
    [Target("test-target")]
    class TestTarget : TargetWithLayout
    {
        public ConcurrentBag<string> Messages = new ConcurrentBag<string>();

        public TestTarget(string name)
        {
            Name = name;
        }

        protected override void Write(LogEventInfo logEvent)
        {
            Messages.Add(Layout.Render(logEvent));
        }
    }

    class Log
    {
        private string _target_name;

        public Log(Layout layout,
                   string target_name)
        {
            if (LogManager.Configuration == null)
            {
                LogManager.Configuration = new LoggingConfiguration();
                InternalLogger.LogFile = Path.Combine(Environment.CurrentDirectory,
                                                      "nlog.debug.txt");
                InternalLogger.LogLevel = LogLevel.Trace;
            }

            _target_name = target_name;
            if (LogManager.Configuration.FindTargetByName<TestTarget>(_target_name) == null)
            {
                // Create the target:
                TestTarget target = new TestTarget(_target_name);

                // Register the target:
                Target.Register<TestTarget>(_target_name);

                // Assign layout to target:
                target.Layout = layout;

                // Add the target to the configuration:
                LogManager.Configuration.AddTarget(_target_name,
                                                   target);

                // Add a logging rule pertaining to the above target:
                LogManager.Configuration.AddRule(LogLevel.Trace,
                                                 LogLevel.Fatal,
                                                 target);

                // Because configuration has been modified programatically, we have to reconfigure all loggers:
                LogManager.ReconfigExistingLoggers();
            }
        }

        public void AssertLogContains(string message)
        {
            TestTarget target = LogManager.Configuration
                                          .FindTargetByName<TestTarget>(_target_name);
            target.Messages.Should().Contain(message);
        }
    }

    class Loggable
    {
        private Logger _logger;

        public Loggable()
        {
            _logger = LogManager.GetCurrentClassLogger();
        }

        public void Error(string message)
        {
            _logger.Error(message);
        }

        public void Info(string message)
        {
            _logger.Info(message);
        }
    }

    [LayoutRenderer("renderer-one")]
    class RendererOne : LayoutRenderer
    {
        protected override void Append(StringBuilder builder,
                                       LogEventInfo logEvent)
        {
            builder.AppendFormat($"{GetType().Name} - {logEvent.Level.Name}: {logEvent.Message}");
        }
    }

    [LayoutRenderer("renderer-two")]
    class RendererTwo : LayoutRenderer
    {
        protected override void Append(StringBuilder builder,
                                       LogEventInfo logEvent)
        {
            builder.AppendFormat($"{GetType().Name} - {logEvent.Level.Name} -> {logEvent.Message}");
        }
    }

    public class TestA
    {
        private Log _log;

        public TestA()
        {
            LayoutRenderer.Register<RendererOne>("renderer-one");
            _log = new Log("${renderer-one}", GetType().Name);
        }

        [Fact]
        public void SomeTest()
        {
            Loggable l = new Loggable();
            l.Info("Test A - SomeTest");
            l.Error("Test A - SomeTest");
            _log.AssertLogContains("RendererOne - Info: Test A - SomeTest");
            _log.AssertLogContains("RendererOne - Error: Test A - SomeTest");
        }

        [Fact]
        public void AnotherTest()
        {
            Loggable l = new Loggable();
            l.Info("Test A - AnotherTest");
            l.Error("Test A - AnotherTest");
            _log.AssertLogContains("RendererOne - Info: Test A - AnotherTest");
            _log.AssertLogContains("RendererOne - Error: Test A - AnotherTest");
        }
    }
    public class TestB
    {
        private Log _log;

        public TestB()
        {
            LayoutRenderer.Register<RendererTwo>("renderer-two");
            _log = new Log("${renderer-two}", GetType().Name);
        }

        [Fact]
        public void SomeTest()
        {
            Loggable l = new Loggable();
            l.Info("Test B - SomeTest");
            l.Error("Test B - SomeTest");
            _log.AssertLogContains("RendererTwo - Info -> Test B - SomeTest");
            _log.AssertLogContains("RendererTwo - Error -> Test B - SomeTest");
        }
    }
}

请注意,您必须安装此问题的“环境”部分中提到的依赖项库才能运行它。此外,您可能需要多次运行代码才能使代码失败,如上所示。提前谢谢。

1 个答案:

答案 0 :(得分:1)

我在GitHub上的NLog问题页面上发布了这个问题(参考:https://github.com/NLog/NLog/issues/2525)。 Rolf建议通过这一行显式禁用Xunit的并行执行:

[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)]

AssemblyInfo.cs中的

让问题消失了。注意,即使我在XUnit的Visual Studio Runner中明确指出不进行并行运行,也需要在上述环境中使用。