NLog 如何设置 LoggerName?

How is NLog setting the LoggerName?

我遇到了一个有趣的、显然是临时性的 NLog 问题,或者可能是我使用它的方式。

我正在尝试创建一个日志服务抽象,目的是为了避免硬依赖,并且我已经在 NLog.FluentBuilder class 上建立了我的抽象模型。所以我有一对接口:


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

我的想法是能够为不同的日志记录框架创建适配器,当然我制作的第一个适配器是针对 NLog 的,因为那是我打算使用的。该实现本质上是 NLog 流畅界面的简化克隆,因此这是我的版本的部分抽象:

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

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

我省略了一些不会给问题添加任何额外信息的方法,基本上它们 都遵循相同的模式。这与 NLog.Fluent.LogBuilder class.

中的内容非常相似,但不完全相同

所以不,它变得有趣。

测试程序

我在我的库中包含了一个 .NET Core 3.0 控制台应用程序作为示例程序。

我已经完整地复制了这篇文章,但是没有大量的评论,这只会让 在路上。

程序只数到 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();
            }
        }
    }

此程序产生以下日志输出。

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

所以这是牛肉。为什么源名称在第 14 次迭代中途从 LogBuilder 更改为 Program?为什么曾经 LogBuilder

我问自己的一个问题是:“它总是在同一个地方发生变化吗”,答案是否定的。它会随着正负一次迭代而变化。

我猜这可能与我正在使用的缓冲日志目标有关...这是我的 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>

如果您能提供任何见解,我将不胜感激。仅供参考 project is open-source 如果您想克隆它并在 IDE.

中检查它

记录器名称创建如下:

  • 当使用 Logger.GetLogger(string name) 时,名称不加更改地使用
  • 使用 LogManager.GetCurrentClassLogger() 时,会在堆栈跟踪中搜索 class 的名称。在某些情况下,这可能会很棘手,因为内联和异步技巧使得很难找到正确的名称。

在这种情况下,实际上不需要使用 LogManager.GetCurrentClassLogger(),我建议将其替换为 Logger.GetLogger(string name)

另请注意,您设计的 fluent API 有点不合常规 - 至少对于您示例中添加的属性而言。