NLog文章系列——如何配置NLog
NLog支持以多种不同方式配置,目前同时支持直接编程和使用配置文件两种方法。本文将对目前支持的各种配置方式作详细描述。
日志配置
通过在启动的时候对一些常用目录的扫描,NLog会尝试使用找到的配置信息进行自动的自我配置。当你运行一个独立的*.exe客户端可执行程序时,NLog将在以下目录搜索配置信息:
- 标准的程序配置文件(通常为 程序名.exe.config)
- 程序目录下的程序名.exe.nlog文件
- 程序目录下的NLog.config文件
- NLog.dll所在目录下的NLog.dll.nlog文件
- 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件
如果是一个ASP.NET程序,被搜索的目录包括:
- 标准的web程序配置文件web.config
- 和web.config在同一目录下的web.nlog文件
- 程序目录下的NLog.config文件
- NLog.dll所在目录下的NLog.dll.nlog文件
- 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件
由于.NET Compact Framework不支持程序配置文件(*.exe.config)和环境变量,因此NLog将只会扫描这些地方:
- 程序目录下的NLog.config文件
- NLog.dll所在目录下的NLog.dll.nlog文件
- 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件
配置文件格式
NLog支持两种配置文件格式
- 配置信息嵌入在.NET应用程序标准的*.exe.config或者web.config文件里
- 保存在独立文件里,也叫单一格式
如果你选择了第一种方式,使用的是标准的configSections这种机制,那么你的配置文件看起来差不多是这个样子:
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<nlog>
</nlog>
</configuration>
单一格式的配置文件就是一个以<nlog />为根节点的纯XMl文件。命名空间并不强制使用,如果使用的话我们就可以利用Visual Studio的智能感应。
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
</nlog>
需要注意的是NLog的配置文件总是大小写敏感的,不管是在使用的时候,或者即使你没有使用名字空间。当然只有你的大小写符合要求,智能感应才能正常工作。
配置元素
下面这些元素可以作为<nlog />的字节点。列表中的前两个元素在所有的NLog配置文件中都必须提供,其余的则可以选择使用,通常用于一些复杂场景。
- <targets /> - 定义日志的目标/输出
- <rules /> - 定义日志的路由规则
- <extensions /> - 从*.dll加载NLog扩展
- <include /> - 导入外部配置文件
- <variable /> - 为配置变量赋值
输出目标
<target />区域定义了日志的目标或者说输出。每一个<target />元素代表一个目标。我们需要为每一个目标设置两个属性:
- name - 目标的名称
- type - 目标的类型 - 比如“File”,“Database”,“Mail”。如果你使用了名字空间,这个属性会被命名为 xsi:type.
除了这两个属性,通常来说给目标添加一些其它参数,这些属性将会影响你在程序中如何使用诊断跟踪语句(diagnostic traces)。每一个目标都可以有一组不同的参数集合,并且参数都是上下文相关的,你可以在本项目的主页上找到更多关于参数的详细说明。Visual Studio的智能感应对这些参数同样有用。
举个例子 - “File”目标可以使用“fileName”作为参数来定义输出文件名,而“Console”目标可以借助“error”参数的值来判断是否应该把当前进程的diagnostic traces结果输出到标准错误(stderr)还是标准输出(stdout)控制台。
下面这个例子演示了在<targets />区域同时定义多个目标:两个files目标,一个network目标和一个OutputDebugString目标:
<targets>
<target name="f1" xsi:type="File" fileName="file1.txt"/>
<target name="f2" xsi:type="File" fileName="file2.txt"/>
<target name="n1" xsi:type="Network" address="tcp://localhost:4001"/>
<target name="ds" xsi:type="OutputDebugString"/>
</targets>
NLog提供了许多已经预先定义好的目标。关于这些目标的详细说明请参考本项目的主页。实际上,你也可以很容易的为自己创建目标 - 全部只需大约15-20行代码即可,更多信息请参考项目文档。
路由规则
<rules />区域定义了日志的路由规则。实际上它是一个简单的路由表,对每一个日志源/记录者的名称和记录等级的组合,定义了一个日志写入目标列表。 表中的规则是被顺序处理的。每当遇到匹配的规则时,日志信息就会被送到规则中定义的一个或多个目标去。如果一个规则被标识为最后一个,那么其后的规则都不会被执行。
每一个路由表项就是一个<logger />元素,它的可以接受的属性有:
- name - 日志源/记录者的名字 (允许使用通配符*)
- minlevel - 该规则所匹配日志范围的最低级别
- maxlevel - 该规则所匹配日志范围的最高级别
- level - 该规则所匹配的单一日志级别
- levels - 该规则所匹配的一系列日志级别,由逗号分隔。
- writeTo - 规则匹配时日志应该被写入的一系列目标,由逗号分隔。
- final - 标记当前规则为最后一个规则。其后的规则即时匹配也不会被运行。
一些例子:
- <logger name="Name.Space.Class1" minlevel="Debug" writeTo="f1" /> - 名字空间Name.Space下的Class1这个类的所有级别等于或者高于Debug的日志信息都写入到“f1”这个目标里。
- <logger name="Name.Space.Class1" levels="Debug,Error" writeTo="f1" /> -名字空间Name.Space下的Class1这个类的所有级别等于Debug或Error的日志信息都写入到“f1”这个目标里。
- <logger name="Name.Space.*" writeTo="f3,f4" /> -名字空间Name.Space下所有类的所有级别的日志信息都写入到“f3”和“f4”这两个目标里。
- <logger name="Name.Space.*" minlevel="Debug" maxlevel="Error" final="true" /> - 名字空间Name.Space下所有类的、级别在Debug和Error之间的(包括Debug,Info,Warn,Error) 日志信息都不会被记录(因为这条规则没有定义writeTo),同时其它后续规则也都会被忽略(因为这里设置了final="true")。
最简单的情况下,整个日志的配置信息可以只由一个<target />元素和一个<logger />规则构成,就可以吧一定级别的日志信息路由到期望的目标去。随着程序不断的变大,增加新的目标和规则也很简单。
上下文信息
NLog最棒的功能之一就是使用布局(layouts)的能力。布局由被一个美元符号$加左大括弧“${”和一个右大括弧“}”为标记所包围的文本所组成。这个标记也就是所谓的“布局生成器(layout renderers),我们可以用它来把一些上下文相关的信息插入到日志信息中。布局可以应用在许多地方,比如可以被用在控制输出到屏幕或写入文件信息的格式,也可以用在控制文件名。接下来我们会更多的了解布局的强大。
假设我们希望每个输出到控制台的信息都包含一些这些信息:
- 当前的日期和时间
- 产生日志信息的类和方法的名字
- 日志等级
- 日志内容
利用Layout来实现很简单:
<target name="c" xsi:type="Console"
layout="${longdate} ${callsite} ${level} ${message}"/>
或者我们可以把每一个日志记录者生成的日志信息输出到一个单独的文件里:
<target name="f" xsi:type="File" fileName="${logger}.txt"/>
这里我们看到fileName属性的值被设置为布局生成器${logger},从而使每一条日志信息被写到一个以日志生成者名字命名的一个文件里。上面这个例子将生成如下一系列文件:
- Name.Space.Class1.txt
- Name.Space.Class2.txt
- Name.Space.Class3.txt
- Other.Name.Space.Class1.txt
- Other.Name.Space.Class2.txt
- Other.Name.Space.Class3.txt
- ...
有一个常见需求是能够用日期信息来区分日志文件。如果使用${shortdate}布局生成器,这简直太容易了:
<target name="f" xsi:type="File" fileName="${shortdate}.txt"/>
那么可以给每一个职员生成一个日志文件吗?答案就是${windows-identity}布局生成器:
<target name="f" xsi:type="File" fileName="${windows-identity:domain=false}.txt"/>
这样我们就能够给每一个职员生成一个日志文件了:
- Administrator.txt
- MaryManager.txt
- EdwardEmployee.txt
- ...
更复杂的场景也能做到。下面这个例子说明了如何为每个人每天生成一个日志文件。每天的日志文件存放在不同的文件夹里:
<target name="f" xsi:type="File"
fileName="${shortdate}/${windows-identity:domain=false}.txt"/>
这将创建如下文件:
- 2006-01-01/Administrator.txt
- 2006-01-01/MaryManager.txt
- 2006-01-01/EdwardEmployee.txt
- 2006-01-02/Administrator.txt
- 2006-01-02/MaryManager.txt
- 2006-01-02/EdwardEmployee.txt
- ...
NLog提供了许多预先定义好的布局生成器。关于它们的说明都在这个页面:http://www.nlog-project.org/layoutrenderers.html。建立你自己的布局生成器也很容易,大概只需要15-20行的代码而已。详细做法请参考项目文档。
包含文件
有时我们希望把配置文件分割为一些比较小的文件。NLog提供了包含文件这一机制来支持这种需求。要包含一个外部文件,你需要做的只是设置fileName这个属性。同时,和其它大多数NLog配置文件的属性一样,fileName也支持用大家都很熟悉的${var}标记引入动态值,这使得我们可以根据环境属性的不同包含不同的文件。在下面这个例子里,我们总是导入基于当前机器名的配置文件。
<nlog>
...
<include file="${basedir}/${machinename}.config"/>
...
</nlog>
变量的使用可以使我们以比较简洁的形式书写复杂或者是重复表达式(如文件名)。定义一个变量的语法是:<variable name="var" value="xxx" />。变量定义好之后,就可以像使用布局生成器一样 – 通过语法${var}来使用了。下面我们来看一个使用变量的例子:
<nlog>
<variable name="logDirectory" value="${basedir}/logs/${shortdate}"/>
<targets>
<target name="file1" xsi:type="File" fileName="${logDirectory}/file1.txt"/>
<target name="file2" xsi:type="File" fileName="${logDirectory}/file2.txt"/>
</targets>
</nlog>
自动再配置
配置文件在程序启动时会被自动读取。然而在一些长时间运行的程序中(比如Windows服务或者ASP.NET程序),有时我们希望能够在不中断程序的前提下临时提高日志的级别。NLog可以一直监视日志配置文件的状态,并在它们被修改后重新读取。要激活这一机制,你只需在你的配置文件中设置<nlog autoReload="true" />。注意自动再配置支持引入文件,所以每次如果一个引入文件被修改了,会引起整个配置信息被重新载入。
日志排错
有时候即使你觉得你已经把日志配置的没有任何问题了,你的程序就是不输出任何日志信息。原因可能有很多,最常见的是权限问题,尤其在ASP.NET程序里,aspnet_wp.exe或者w3wp.exe进程可能没有足够的权限访问存放日志文件的目录。NLog被设计为吃掉任何由于记录日志而带来的运行时异常。而下面这些设置可以改变这种行为并/或者重定向这些信息。
- <nlog throwExceptions="true" />- 设置throwExceptions属性为“true”可以让NLog不再阻挡这类异常,而是把它们抛给调用者。在部署是这样做可以帮我们快速定位问题。一旦应用程序已经正确配置了,我们建议把throwExceptions的值设为“false”,这样由于日志引发的问题不至于导致应用程序的崩溃。
- <nlog internalLogFile="file.txt" />- 设置internalLogFile属性可以让NLog把内部的调试和异常信息都写入指定文件里。
- <nlog internalLogLevel="Trace|Debug|Info|Warn|Error|Fatal" /> - 决定内部日志的级别,级别越高,输出的日志信息越简洁。
- <nlog internalLogToConsole="false|true" /> - 是否把内部日志输出到标准控制台。
- <nlog internalLogToConsoleError="false|true" /> - 是否把内部日志输出到标准错误控制台 (stderr)。
异步处理,封装和复合目标
NLog提供的封装和复合目标可以修改其它目标的行为,这可以增加一些功能如:
- 异步处理 (被封装的目标在另一个线程上运行)
- 自动重试 (retry-on-error)
- 负载平衡 (round-robin targets)
- 缓冲 (buffering)
- 过滤 (filtering)
- 备份目标 (灾难恢复failover)
- 更多请参考http://www.nlog-project.org/targets.html
定义一个封装或者复合目标,你只需在一个目标节点里嵌套另一个目标节点即可。你甚至可以封装一个封装目标。嵌套的层数没有任何限制。比如,要给你的配置文件加上异步日志记录的功能,同时异步日志记录可以自动重试,你可以这样做:
<targets>
<target name="n" xsi:type="AsyncWrapper">
<target xsi:type="RetryingWrapper">
<target xsi:type="File" fileName="${file}.txt"/>
</target>
</target>
</targets>
因为异步处理使用的非常普遍,NLog专门为异步处理设计了一个简化符号,这样所有需要异步处理的目标都不需要显式的定义封装了。你只要设置<targets async="true" />然后你所有的目标就都具备了异步处理的能力了。
缺省封装
有时我们希望用同一种封装来处理所有的目标,比如说增加缓冲和/或自动重试功能。NLog为此提供了专门的语法:<default-wrapper />。你只要把这个元素放在<targets />区域里,然后所有的目标都会自动加载同一个封装目标。需要注意的是<default-wrapper />只对当前这个<targets />区域有效,而你可以使用多个<targets />区域,这样你就可以把目标分组并用不同的封装目标处理。
<nlog>
<targets>
<default-wrapper xsi:type="BufferingWrapper" bufferSize="100"/>
<target name="f1" xsi:type="File" fileName="f1.txt"/>
<target name="f2" xsi:type="File" fileName="f2.txt"/>
</targets>
<targets>
<default-wrapper xsi:type="AsyncWrapper">
<wrapper xsi:type="RetryingWrapper"/>
</default-wrapper>
<target name="n1" xsi:type="Network" address="tcp://localhost:4001"/>
<target name="n2" xsi:type="Network" address="tcp://localhost:4002"/>
<target name="n3" xsi:type="Network" address="tcp://localhost:4003"/>
</targets>
</nlog>
上面的例子里我们定义了两个缓冲文件目标和三个异步以及自动重试网络目标。
缺省目标参数
和缺省封装目标类似,NLog也提供了<default-target-parameters />来让你为目标参数设置缺省值。比如,如果你不希望日志文件总是被打开,你既可以通过给每一个目标增加 keepFileOpen=”false”属性来达到这个目的:
<nlog>
<targets>
<target name="f1" xsi:type="File" fileName="f1.txt" keepFileOpen="false"/>
<target name="f2" xsi:type="File" fileName="f2.txt" keepFileOpen="false"/>
<target name="f3" xsi:type="File" fileName="f3.txt" keepFileOpen="false"/>
</targets>
</nlog>
或者,你可以定义一个<default-target-parameters />并把它的值赋给当前<targets />区域里所有的目标。缺省参数时依据不同的类型定义的,并且在XML文件的实际属性没有被定义好之前就生效了。
<nlog>
<targets>
<default-target-parameters xsi:type="File" keepFileOpen="false"/>
<target name="f1" xsi:type="File" fileName="f1.txt"/>
<target name="f2" xsi:type="File" fileName="f2.txt"/>
<target name="f3" xsi:type="File" fileName="f3.txt"/>
</targets>
</nlog>