NLog自定义LayoutRenderer将日志事件呈现为空字符串 [英] NLog custom LayoutRenderer is rendering log events as empty string

查看:130
本文介绍了NLog自定义LayoutRenderer将日志事件呈现为空字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  • Visual Studio 2017
  • 项目的.NET运行时版本为4.6.2
  • XUnit版本2.3.1
  • NLog版本4.4.12
  • 流利断言4.19.4

从下面的可用来重现该问题的示例代码中,我们有2个自定义LayoutRenderer:RendererOneRendererTwo.然后分别将这2个用于测试TestATestB.当我一次运行一个测试时,我没有任何问题.但是,如果要通过XUnit的全部运行"按钮在一次示例中运行它们,我将得到失败的断言,如下图所示:

From the sample code below which can be run to reproduce the problem, we have 2 custom LayoutRenderer: RendererOne and RendererTwo. Those 2 are then used in tests TestA and TestB respectively. When i run the test one at a time, I do not get any problem. However, if were to run them in one go example via XUnit's "Run All" button, I get failed assertions as illustrated in the attached image below:

当连接到目标的渲染器在LayoutRenderer的Append方法中遇到LogEventInfo对象时,似乎正在生成空字符串(输出中的"").从示例代码中可以看出,我启用了InternalLogger,但是它没有向我显示可能出了什么问题.我认为我已经按照使用自定义渲染器的方式正确地完成了所有操作:

It would appear that the Renderer attached to the target is producing empty strings ("" in the output) when it encounters a LogEventInfo object in the Append method of the LayoutRenderer. As it can be seen in the sample code, I have enabled the InternalLogger but, it is not showing me what could be going wrong. I think that I have done everything correctly in terms of how to use a custom renderer:

  • 注册渲染器(参考:TestATestB的构造函数)
  • 将它们分配给相关的TestTarget(参考:Log的构造函数)
  • Register the Renderers (Ref: constructors of TestA and TestB)
  • Assign them to the relevant TestTarget (Ref: constructor of Log)

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

I do not know why the Layout which is not processing the log events correctly is not being registered. Here is another screenshot which illustrates this situation:

请注意,以上屏幕截图无关,但可以说明情况.对此问题的任何解释/修复/如何解决问题都将受到欢迎.

Note the above screenshots are not related but serves to illustrate the situation. Any kind of explanation/fix/how-to-troubleshoot the problem would be most welcome.

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

请注意,必须运行此问题的环境"部分中提到的依赖库.另外,可能如上所述,您必须运行几次代码才能使其失败.预先感谢.

Note you will have to install the dependency libraries mentioned in the "Environment" section of this question in order to run it. Also, it might be that you have to run the code couple of times in order for it to fail as shown above. Thanks in advance.

推荐答案

我在GitHub上的NLog的Issue页面上发布了该问题(参考:

I posted the question on the Issue page of NLog on GitHub (Ref: https://github.com/NLog/NLog/issues/2525). The suggestion by Rolf to explicitly disable parallel execution of Xunit via this line:

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

使问题消失了.请注意,即使我在XUnit的Visual Studio Runner中明确指示不要并行运行,在上述环境中也需要这样做.

in the AssemblyInfo.cs made the problem go away. Note, this is needed in the above environment even if i explicitly indicated in the Visual Studio Runner of XUnit not to do parallel runs.

这篇关于NLog自定义LayoutRenderer将日志事件呈现为空字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆