65.9K
CodeProject 正在变化。 阅读更多。
Home

NLog:规则和过滤器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2019年5月6日

CPOL

5分钟阅读

viewsIcon

62144

在 Confirmit,我们使用 NLog 库在 .NET 应用程序中进行日志记录。尽管该库有文档,但我发现很难理解日志记录器是如何工作的。在本文中,我将尝试解释 NLog 如何使用规则和过滤器。

如何配置 NLog

我们将从一个小的回顾开始,了解可以使用 NLog 配置做什么。简单的配置通常是一个 XML 文件(例如 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">

  <targets>
    <target name="target1" xsi:type="ColoredConsole" 
     layout="Access Log|${level:uppercase=true}|${logger}|${message}">
      <highlight-row condition="true" foregroundColor="red"/>
    </target>
    <target name="target2" xsi:type="ColoredConsole" 
     layout="Common Log|${level:uppercase=true}|${logger}|${message}">
      <highlight-row condition="true" foregroundColor="green"/>
    </target>
    <target name="target3" xsi:type="ColoredConsole" 
     layout="Yellow Log|${level:uppercase=true}|${logger}|${message}">
      <highlight-row condition="true" foregroundColor="yellow"/>
    </target>
  </targets>

  <rules>
    <logger name="*" minlevel="Warn" writeTo="target1,target2,target3" />
  </rules>
</nlog>

您可以用一行代码加载此配置

LogManager.Configuration = new XmlLoggingConfiguration("NLog.config");

我们可以用 NLog 配置做什么?我们可以为每条规则设置多个目标(target)。

<rules>
  <logger name="*" minlevel="Warn" writeTo="target1,target2,target3" />
</rules>

我们可以定义要记录的日志级别。

<rules>
  <logger name="*" minlevel="Warn" writeTo="target1" />
  <logger name="*" levels="Debug,Warn,Info" writeTo="target2" />
</rules>

我们可以为每条规则设置过滤器。

<rules>
  <logger name="*" minlevel="Info" writeTo="target1">
    <filters defaultAction='Log'>
      <when condition="contains('${message}','Common')" action="Ignore" />
    </filters>
  </logger>
</rules>

最后,我们可以使用嵌套规则。

<rules>
  <logger name="*" minlevel="Info" writeTo="target1">
    <logger name="*" minlevel="Warn" writeTo="target2" />
  </logger>
</rules>

现在是时候看看这一切是如何工作的了。

构建日志记录器配置

当您请求日志记录器实例时,

var commonLogger = LogManager.GetLogger("Common");

NLog 会从其缓存中获取,或者创建一个新的(请参阅 此处)。在后一种情况下,它将为具有给定名称的日志记录器创建一个新配置。让我们更仔细地看看这个配置。

总的来说,日志记录器的配置包含一个独立的日志目标链,每个日志级别(TraceDebugInfoWarnErrorFatal)都有相应的过滤器(请参阅 此处)。现在我们将看到这些链是如何构建的。

负责构建这些链的主要方法是 LogFactory 类中的 GetTargetsByLevelForLogger。它的工作原理如下:它遍历 NLog 配置中的所有规则。首先,它会检查规则的名称是否与日志记录器的名称匹配。规则名称支持通配符,就像我们用于文件系统对象一样。

  • * - 任意字符序列
  • ? - 任意单个字符

因此,规则名称 * 匹配任何日志记录器名称,而 Common* 匹配所有以 Common 开头的日志记录器。

如果规则的名称与日志记录器的名称不匹配,则此规则及其所有子规则都不会被使用。否则,该方法将获取此规则启用的所有日志级别。对于每个此类级别,NLog 将规则的所有目标添加到相应带有规则过滤器的目标链中。

在构建目标链方面还有一个重要的步骤。如果当前规则被标记为 final 并且其名称与日志记录器的名称匹配,那么 NLog 将在此停止为规则启用的所有日志级别的所有目标链的构建。这意味着,后续的规则或嵌套规则都不会向这些目标链添加任何内容。它们已完全构建完成,并且不会被更改。这意味着,写成这样是没有意义的:

<rules>
  <logger name="*" minlevel="Info" writeTo="target1" final="true">
    <logger name="*" minlevel="Warn" writeTo="target2" />
  </logger>
</rules>

target2 永远不会收到任何消息。但写成这样的配置是有意义的:

<rules>
  <logger name="*" minlevel="Warn" writeTo="target1" final="true">
    <logger name="*" minlevel="Info" writeTo="target2" />
  </logger>
</rules>

由于外部规则没有为 Info 日志级别启用,因此此级别的目标链不会在外部规则处冻结。所以所有的 Info 消息都会发送到 target2

在将规则的目标添加到相应目标链后,当前规则的所有子规则将使用相同的算法进行递归处理。无论父规则启用了哪些日志级别,都会发生这种情况。

最后,日志记录器的配置就构建完成了。它包含每个可能日志级别的目标链及其过滤器。

现在是时候使用这个配置了。

使用日志记录器配置

我们从一个简单的事情开始。Logger 类有一个 IsEnabled 方法以及相应的 IsXXXEnabled 属性(IsDebugEnabledIsInfoEnabled 等)。它们是如何工作的?实际上,它只是检查相应日志级别的目标链是否不为空(请参阅 此处)。这意味着过滤器永远不会影响这些属性和方法的返回值。

接下来,我将解释当您想要记录一条消息时会发生什么。正如您所料,日志记录器会获取相应日志级别的目标链。它开始逐个处理链中的链接。对于每个链接,日志记录器会决定是否应该将消息写入相应目标,以及是否应该在此停止处理链。这是通过过滤器完成的。让我向您展示 NLog 过滤器是如何工作的。

过滤器在配置中的定义如下:

<rules>
  <logger name="*" minlevel="Info" writeTo="target1">
    <filters defaultAction='Log'>
      <when condition="contains('${message}','Common')" action="Ignore" />
    </filters>
  </logger>
</rules>

一个普通的过滤器有一个布尔条件。在这里,您可以认为过滤器为每条消息返回 truefalse。但事实并非如此。过滤器实际上返回的是 FilterResult 类型的值。如果过滤器的条件计算为 true,则过滤器返回由 action 属性定义的值(在我们的示例中是 Ignore)。如果条件计算为 false,则过滤器返回 Neutral。这意味着该过滤器不想决定如何处理该消息。

您可以在 此处 查看目标链是如何处理的。对于每个目标,过滤器结果都是使用 GetFilterResult 方法计算的。此结果等于第一个不是 Neutral 的过滤器的结果。这意味着,如果某个过滤器返回的值与 Neutral 不同,那么之后的所有过滤器都将不会被执行。

但是,如果所有过滤器都返回 Neutral 值,会发生什么?在这种情况下,将使用默认值。这个值由规则的 filters 元素的 defaultAction 属性设置。您认为 defaultAction 的默认值是什么?如果您认为它是 Neutral,那就对了。这意味着整个过滤器链可以返回 Neutral 作为结果。在这种情况下,NLog 会将其视为 Log。也就是说,消息仍将被写入目标(请参阅 此处)。

正如您可能猜到的,如果过滤器结果是 IgnoreIgnoreFinal,消息将不会被写入目标。如果结果是 LogLogFinal,消息将写入目标。但是 IgnoreIgnoreFinal 之间,以及 LogLogFinal 之间有什么区别?这很简单。在 IgnoreFinalLogFinal 的情况下,NLog 会在此停止目标链的处理,并且不会在当前目标之后的目标中写入任何内容。

结论

对 NLog 代码的分析帮助我很多地理解了它的规则和过滤器是如何工作的。我希望它也能帮助您。祝您好运!

历史

  • 2019 年 5 月 6 日:初始版本
© . All rights reserved.