简体   繁体   English

NLog如何设置LoggerName?

[英]How is NLog setting the LoggerName?

I'm having an interesting, apparently temporal, issue with NLog or perhaps the way I'm using it.我对 NLog 或者我使用它的方式有一个有趣的、显然是暂时的问题。

I'm attempting to create a logging service abstraction, for the purposes of avoiding hard dependencies, and I've modelled my abstraction on the NLog.FluentBuilder class.为了避免硬依赖,我正在尝试创建日志服务抽象,并且我在NLog.FluentBuilder class 上对我的抽象进行了建模。 So I have a pair of interfaces:所以我有一对接口:


public interface ILog
    {
    IFluentLogBuilder Trace([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Debug([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Info([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Warn([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Error([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Fatal([CallerFilePath] string callerFilePath = null);
    void Shutdown();
    }

public interface IFluentLogBuilder
    {
    IFluentLogBuilder Exception(Exception exception);
    IFluentLogBuilder LoggerName(string loggerName);
    IFluentLogBuilder Message(string message);
    IFluentLogBuilder Message(string format, params object[] args);
    IFluentLogBuilder Message(IFormatProvider provider, string format, params object[] args);
    IFluentLogBuilder Property(string name, object value);
    IFluentLogBuilder Properties(IDictionary<string,object> properties);
    IFluentLogBuilder TimeStamp(DateTime timeStamp);
    IFluentLogBuilder StackTrace(StackTrace stackTrace, int userStackFrame);
    void Write([CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null,
        [CallerLineNumber] int callerLineNumber = default);
    void WriteIf(Func<bool> condition, [CallerMemberName] string callerMemberName = null,
        [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = default);
    void WriteIf(bool condition, [CallerMemberName] string callerMemberName = null,
        [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = default);
    }

The idea is to be able to create adapters for different logging frameworks, and of course the first one I've made is for NLog, since that's what I intend to use.这个想法是能够为不同的日志框架创建适配器,当然我所做的第一个是用于 NLog,因为这就是我打算使用的。 The implementation is essentially a pared-down clone of the fluent interface from NLog, so here's a partial abstraction of my version:该实现本质上是 NLog 流畅接口的精简克隆,所以这里是我的版本的部分抽象:

The ILog implementation ILog实现

    public sealed class LoggingService : ILog
        {
        private static readonly ILogger DefaultLogger = LogManager.GetCurrentClassLogger();

        static LoggingService()
            {
            LogManager.AutoShutdown = true;
            }

        public IFluentLogBuilder Trace([CallerFilePath] string callerFilePath = null)
            {
            return CreateLogBuilder(LogLevel.Trace, callerFilePath);
            }

        // Methods for other log levels elided for clarity...

        private IFluentLogBuilder CreateLogBuilder(LogLevel logLevel, string callerFilePath)
            {
            string name = !string.IsNullOrWhiteSpace(callerFilePath)
                ? Path.GetFileNameWithoutExtension(callerFilePath)
                : null;
            var logger = string.IsNullOrWhiteSpace(name) ? DefaultLogger : LogManager.GetLogger(name);
            var builder = new LogBuilder(logger, logLevel);
            return builder;
            }

        /// <inheritdoc />
        public void Shutdown() => LogManager.Shutdown();
        }

The IFluentLogBuilder implementation IFluentLogBuilder实现

    internal sealed class LogBuilder : IFluentLogBuilder
    {
        private readonly LogEventInfo logEvent;
        private readonly ILogger logger;

        public LogBuilder(ILogger logger, LogLevel level)
        {
            if (logger == null)
                throw new ArgumentNullException(nameof(logger));
            if (level == null)
                throw new ArgumentNullException(nameof(level));
            this.logger = logger;
            logEvent = new LogEventInfo { LoggerName = logger.Name, Level = level };
        }

        /// <inheritdoc />
        public IFluentLogBuilder Exception(Exception exception)
        {
            logEvent.Exception = exception;
            return this;
        }

        /// <inheritdoc />
        public IFluentLogBuilder LoggerName(string loggerName)
        {
            logEvent.LoggerName = loggerName;
            return this;
        }

        /// <inheritdoc />
        public IFluentLogBuilder Message(string message)
        {
            logEvent.Message = message;
            return this;
        }

        // Some other builder methods elided for clarity... (they all follow the pattern).

        /// <inheritdoc />
        public void Write([CallerMemberName] string callerMemberName = null,
            [CallerFilePath] string callerFilePath = null,
            [CallerLineNumber] int callerLineNumber = default)
        {
            if (!logger.IsEnabled(logEvent.Level)) return;
            SetCallerInfo(callerMemberName, callerFilePath, callerLineNumber);
            logger.Log(logEvent);
        }

        private void SetCallerInfo(string callerMethodName, string callerFilePath, int callerLineNumber)
        {
            if (callerMethodName != null || callerFilePath != null || callerLineNumber != 0)
                logEvent.SetCallerInfo(null, callerMethodName, callerFilePath, callerLineNumber);
        }

        /// <summary>
        /// Builds and returns the <see cref="LogEventInfo"/> without writing it to the log.
        /// </summary>
        internal LogEventInfo Build() => logEvent;
    }

I've left out some of the methods that don't add any extra information to the issue, basically they all follow the same pattern.我省略了一些不会为问题添加任何额外信息的方法,基本上它们都遵循相同的模式。 This is all very similar, but not identical, to what is in the NLog.Fluent.LogBuilder class.这与NLog.Fluent.LogBuilder class 中的内容非常相似,但并不完全相同。

So noe it gets interesting.所以不,它变得有趣。

The Test Program测试程序

I included a .NET Core 3.0 console app as a sample program in my library.我在我的库中包含了一个 .NET Core 3.0 控制台应用程序作为示例程序。

I've reproduced this in its entirety, but without the copious comments, which would just get in the way here.我已经完整地复制了这个,但没有大量的评论,这只会妨碍这里。

The program just counts up to 1000, printing out each number as it goes, and producing some exceptions to make things more interesting and demonstrate semantic logging.该程序只数到 1000,打印出每个数字,并产生一些异常以使事情更有趣并演示语义日志记录。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TA.Utils.Core;

namespace TA.Utils.Logging.NLog.SampleConsoleApp
    {
    class Program
        {
        static readonly List<int> SuperstitiousNumbers = new List<int> {13, 7, 666, 3, 8, 88, 888};

        async static Task Main(string[] args)
            {
            var log = new LoggingService();
            log.Info()
                .Message("Application stating - version {Version}", GitVersion.GitInformationalVersion)
                .Property("SemVer", GitVersion.GitFullSemVer)
                .Property("GitCommit", GitVersion.GitCommitSha)
                .Property("CommitDate", GitVersion.GitCommitDate)
                .Write();
            var seed = DateTime.Now.Millisecond;
            var gameOfChance = new Random(seed);
            log.Debug().Property("seed",seed).Write();

            for (int i = 0; i < 1000; i++)
                {
                try
                    {
                    log.Debug().Message("Starting iteration {iteration}", i).Write();
                    if (SuperstitiousNumbers.Contains(i))
                        {
                        throw new SuperstitiousNumberException($"Skipping {i} because it is a superstitious number");
                        }

                    // There's a small chance of a random "failure"
                    if (gameOfChance.Next(100) < 3)
                        throw new ApplicationException("Random failure");
                    }
                catch (SuperstitiousNumberException ex)
                    {
                    log.Warn()
                        .Message("Superstitious looking number: {number}", i)
                        .Exception(ex)
                        .Property("SuperstitiousNumbers", SuperstitiousNumbers)
                        .Write();
                    }
                catch (ApplicationException ae)
                    {
                    log.Error().Exception(ae).Message("Failed iteration {iteration}", i).Write();
                    }
                await Task.Delay(TimeSpan.FromMilliseconds(1000));
                log.Debug().Message("Finished iteration {iteration}", i).Write();
                }
            log.Info().Message("Program terminated").Write();
            log.Shutdown();
            }
        }
    }

This program produces the following log output.该程序生成以下日志 output。

00:01:09.4823 | INFO  | LogBuilder                      | Application stating - version "1.1.1-beta.1+18.Branch.hotfix-1.1.1.Sha.8b8fa5a008c35d4fc21c99d0bd9a01f6d32c9a53"
00:01:09.5339 | DEBUG | LogBuilder                      |
00:01:09.5339 | DEBUG | LogBuilder                      | Starting iteration 0
00:01:10.5636 | DEBUG | LogBuilder                      | Finished iteration 0
00:01:10.5636 | DEBUG | LogBuilder                      | Starting iteration 1
00:01:11.5762 | DEBUG | LogBuilder                      | Finished iteration 1
00:01:11.5762 | DEBUG | LogBuilder                      | Starting iteration 2
00:01:12.5893 | DEBUG | LogBuilder                      | Finished iteration 2
00:01:12.5893 | DEBUG | LogBuilder                      | Starting iteration 3
00:01:12.5893 | WARN  | LogBuilder                      | Superstitious looking number: 3
00:01:13.6325 | DEBUG | LogBuilder                      | Finished iteration 3
00:01:13.6325 | DEBUG | LogBuilder                      | Starting iteration 4
00:01:14.6484 | DEBUG | LogBuilder                      | Finished iteration 4
00:01:14.6484 | DEBUG | LogBuilder                      | Starting iteration 5
00:01:15.6534 | DEBUG | LogBuilder                      | Finished iteration 5
00:01:15.6534 | DEBUG | LogBuilder                      | Starting iteration 6
00:01:16.6653 | DEBUG | LogBuilder                      | Finished iteration 6
00:01:16.6653 | DEBUG | LogBuilder                      | Starting iteration 7
00:01:16.6653 | WARN  | LogBuilder                      | Superstitious looking number: 7
00:01:17.6787 | DEBUG | LogBuilder                      | Finished iteration 7
00:01:17.6787 | DEBUG | LogBuilder                      | Starting iteration 8
00:01:17.6787 | WARN  | LogBuilder                      | Superstitious looking number: 8
00:01:18.6890 | DEBUG | LogBuilder                      | Finished iteration 8
00:01:18.6890 | DEBUG | LogBuilder                      | Starting iteration 9
00:01:19.6926 | DEBUG | LogBuilder                      | Finished iteration 9
00:01:19.6935 | DEBUG | LogBuilder                      | Starting iteration 10
00:01:20.7071 | DEBUG | LogBuilder                      | Finished iteration 10
00:01:20.7071 | DEBUG | LogBuilder                      | Starting iteration 11
00:01:21.7110 | DEBUG | LogBuilder                      | Finished iteration 11
00:01:21.7110 | DEBUG | LogBuilder                      | Starting iteration 12
00:01:22.7367 | DEBUG | LogBuilder                      | Finished iteration 12
00:01:22.7404 | DEBUG | LogBuilder                      | Starting iteration 13
00:01:22.7404 | WARN  | LogBuilder                      | Superstitious looking number: 13
00:01:23.7621 | DEBUG | LogBuilder                      | Finished iteration 13
00:01:23.7621 | DEBUG | LogBuilder                      | Starting iteration 14
00:01:24.7756 | DEBUG | Program                         | Finished iteration 14
00:01:24.7756 | DEBUG | Program                         | Starting iteration 15
00:01:25.7876 | DEBUG | Program                         | Finished iteration 15
00:01:25.7876 | DEBUG | Program                         | Starting iteration 16
00:01:26.8040 | DEBUG | Program                         | Finished iteration 16
00:01:26.8040 | DEBUG | Program                         | Starting iteration 17
00:01:27.8176 | DEBUG | Program                         | Finished iteration 17
00:01:27.8176 | DEBUG | Program                         | Starting iteration 18
00:01:28.8277 | DEBUG | Program                         | Finished iteration 18
00:01:28.8277 | DEBUG | Program                         | Starting iteration 19
00:01:29.8372 | DEBUG | Program                         | Finished iteration 19
00:01:29.8372 | DEBUG | Program                         | Starting iteration 20

So here's the beef.所以这里的牛肉。 Why does the source name change from LogBuilder to Program half way through iteration 14?为什么在第 14 次迭代中,源名称从LogBuilder更改为Program Why was it ever LogBuilder ?为什么它曾经是LogBuilder

One question I asked myself is: "does it always change at the same place" and the answer is no.我问自己的一个问题是:“它总是在同一个地方改变吗?”答案是否定的。 It varies by plus or minus one iteration.它以正负一次迭代变化。

My guess is that it could be something to do with the buffering log target that I'm using... here's my NLog.config file:我的猜测是这可能与我正在使用的缓冲日志目标有关......这是我的NLog.config文件:

<?xml version="1.0" encoding="utf-8"?>

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       autoReload="true">
  <extensions>
    <add assembly="NLog.Targets.Seq"/>
  </extensions>

  <targets async="true" >
    <target xsi:type="ColoredConsole" name="console"
            layout="${time} | ${pad:padding=-5:inner=${uppercase:${level}}} | ${pad:padding=-31:inner=${callsite:className=true:fileName=false:includeSourcePath=false:methodName=false:includeNamespace=false}} | ${message}" >
      <highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGreen" />
      <highlight-row condition="level == LogLevel.Info" foregroundColor="White" />
      <highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" />
      <highlight-row condition="level == LogLevel.Error" foregroundColor="Red" />
      <highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" />
    </target>

    <target name="seq" xsi:type="BufferingWrapper" bufferSize="1000"
            flushTimeout="500" slidingTimeout="false">
      <target xsi:type="Seq" name="seq" serverUrl="http://seq.nowhere.com:5341" apiKey="imnotfallingforthatone">
        <!-- Augment the log data with some extra properties -->
        <property name="ProcessId" value="${processid}" />
        <property name="ProcessName" value="${processname}" />
        <property name="ThreadId" value="${threadid}" as="number" />
        <property name="Machine" value="${machinename}" />
        <property name="Host" value="${hostname}" />
        <property name="User" value="${environment-user}" />
      </target>
    </target>

    <target xsi:type="Trace" name="debug" rawWrite="true">
      <layout>${pad:padding=-5:inner=${uppercase:${level}}}|${pad:padding=-16:fixedLength=true:alignmentOnTruncation=right:inner=${callsite:className=true:fileName=false:includeSourcePath=false:methodName=false:includeNamespace=false}}| ${message}</layout>
    </target>
  </targets>

  <rules>
    <logger name="*" minlevel="Trace" writeTo="console" />
    <logger name="*" minlevel="Trace" writeTo="debug" />
    <logger name="*" minlevel="Trace" writeTo="seq" />
  </rules>
</nlog>

I would appreciate any insights you are able to give.我将不胜感激您能够提供的任何见解。 FYI the project is open-source if you'd like to clone it and examine it in your IDE.仅供参考,如果您想克隆它并在 IDE 中检查它,该项目是开源的

The logger name is created a follows:记录器名称创建如下:

  • When using Logger.GetLogger(string name) the name is used without changes使用Logger.GetLogger(string name)时,使用的名称无需更改
  • When using LogManager.GetCurrentClassLogger() the name of the class is searched on the stacktrace.使用LogManager.GetCurrentClassLogger()时,会在堆栈跟踪中搜索 class 的名称。 In some cases this could be tricky, as inlining and async tricks makes it hard to find the correct name.在某些情况下,这可能很棘手,因为内联和异步技巧很难找到正确的名称。

In this case the usage of LogManager.GetCurrentClassLogger() isn't really needed and I recommend to replace that one with Logger.GetLogger(string name)在这种情况下,实际上不需要使用LogManager.GetCurrentClassLogger() ,我建议将其替换为Logger.GetLogger(string name)

Please also note that your design of the fluent API is a bit unconventional - at least for the added properties in your example.另请注意,您对流利的 API 的设计有点不合常规 - 至少对于您示例中添加的属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM