NLog是一个简单灵活的.NET日志记录类库。支持的平台也相当广泛,从.net环境到mono再到现在最新的Xamarin均支持。
它允许我们自定义从跟踪消息的来源(source)到记录跟踪信息的目标(target)的规则(rules)。target可以为文件、数据库、网络中的其它计算机(通过TCP或UDP)、基于MSMQ的消息队列、Windows系统日志等。
通过使用NLog,可以在输出带有上下文的调试诊断信息,根据配置可以把日志发送到一个或多个输出目标target中。
而且NLog的过滤信息功能执行效率很高,这样我们就可以一直保留程序中的日志写入代码,然后由NLog在运行时将其根据需要过滤掉,加上异步处理以及其他包装程序的支持,NLog将成为一个极为强大的、且极具伸缩性的日志记录工具。
从整体来看,Nlog主要分为、日志配置(config)、日志输出对象(target)、模版(layout),条件过滤(filter)几大部分。
其源码地址 https://github.com/NLog/NLog
1 创建Log信息
为了从你的程序中创建你所需要的日志消息,你需要使用Nlog日志记录API。Logger
和LogManager
这两个类将会经常被使用到。
- Logger代表了一些被命名的日志源,他们有一些写日志消息的成员方法。
- LogManager则是用于创建和管理Logger对象的实例。
其中Logger,根据日志配置、内部调用输出目标Target的Write方法,把日志写入实际输出目标中。
using NLog;
Logger logger = LogManager.GetLogger("loggerName");
logger.Info("info log message");
上面的日志配置,可以是使用文件的配置方式,也可以是编程配置的方式
2 日志配置(文件方式)
配置文件,支持两种配置方式:
- 配置信息嵌入在.NET应用程序标准的*.exe.config或者web.config文件里
- 保存在独立文件里,也叫单一格式
如果使用第一种方式,使用的是标准的configSections这种机制,那么你的配置文件看起来差不多是这个样子:
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<nlog>
<!-- TODO 这里填写Nlog配置元素-->
</nlog>
</configuration>
单一格式的配置文件就是一个以为根节点的纯XMl文件
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- TODO 这里填写Nlog配置元素-->
</nlog>
配置文件在程序启动时会被自动读取,然而在一些长时间运行的程序中,有时我们希望能够在不中断程序的前提下临时提高日志的级别。只需在配置文件中设置<nlog autoReload="true" />
。
2.1 配置元素
元素配置在的字节点内,需要配置的元素如下所示。列表中的前两个元素在所有的NLog配置文件中都必须提供,其余的则可以选择使用,通常用于一些复杂场景,暂时不进行说明。
- - 定义日志的目标/输出
- - 定义日志的路由规则
- - 从*.dll加载NLog扩展
- - 导入外部配置文件
- - 为配置变量赋值
-
定义日志的目标/输出
<targets> <target name="logfile" xsi:type="File" fileName="file.txt" /> </targets>
这将会定义一个目标,日志将会被输出到一个叫做file.txt的文件中。
-
定义日志的路由规则
<rules> <logger name="SomeNamespace.Component.*" minlevel="Trace" writeTo="logfile" /> </rules>
这条规则将会将以SomeNamespace.Component开头的Logger对象的Trace级别及以上级别的日志发送到file.txt
完整的文件配置如下:
<?xml version="1.0" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="logfile" xsi:type="File" fileName="file.txt" />
</targets>
<rules>
<logger name="SomeNamespace.Component.*" minlevel="Trace" writeTo="logfile" />
</rules>
</nlog>
2.2 配置文件存放目录
系统在启动时,会按照下面的顺序扫描配置信息,进行自我配置。
- 当你运行一个独立的*.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环境变量,则该变量所指向的文件
3 日志配置(编码方式)
某些情况下我们可能不愿用配置文件来配置NLog,而是选择使用NLog的配置API进行。若希望以编程的方式对NLog进行配置,我们需要:
- 创建一个LoggingConfiguration对象,用来保存配置信息
- 至少创建一个输出目标对象
- 设置该输出目标对象的属性
- 设置LoggingRule对象,并将其添加到LoggingConfiguration对象的LoggingRules集合中
- 启用该LoggingConfiguration对象(将LogManager.Configuration设定为该LoggingConfiguration对象即可)
下面的代码片段以编程方式创建了两个输出目标对象——控制台和文件,并将记录等级等于或高于Debug的日志信息发送至这两个输出目标:
class LogExample
{
static void Main(string[] args)
{
// 1. 创建一个LoggingConfiguration对象,用来保存配置信息
LoggingConfiguration config = new LoggingConfiguration();
// 2. 创建输出目标对象,并添加到配置信息中
ConsoleTarget consoleTarget = new ConsoleTarget();
config.AddTarget("console", consoleTarget);
FileTarget fileTarget = new FileTarget();
config.AddTarget("file", fileTarget);
// 3. 设置输出目标对象的属性
consoleTarget.Layout = "${longdate} ${logger} ${message}";
fileTarget.FileName = "${basedir}/file.log";
fileTarget.Layout = "${message}";
// 4. 定义记录规则
LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget);
config.LoggingRules.Add(rule1);
LoggingRule rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget);
config.LoggingRules.Add(rule2);
// 5. 启用该LoggingConfiguration对象
LogManager.Configuration = config;
// 使用示例
Logger logger = LogManager.GetLogger("Example");
logger.Trace("trace log message");
logger.Debug("debug log message");
logger.Info("info log message");
logger.Warn("warn log message");
logger.Error("error log message");
logger.Fatal("fatal log message");
}
}
4 上下文信息
NLog像其他日志类库一样有使用布局(layouts)的能力。布局由被一个美元符号 加 左 大 括 弧 “ 加左大括弧“ 加左大括弧“{”和一个右大括弧“}”为标记所包围的文本所组成。这个标记也就是所谓的“布局生成器(layout renderers),我们可以用它来把一些上下文相关的信息插入到日志信息中。布局可以应用在许多地方,比如可以被用在控制文件名或写入文件信息的格式,例如:
<target name="f" xsi:type="File" fileName="${logger}.txt" layout="${longdate} ${callsite} ${level} ${message}"/>
常用上下文信息有以下几种:
- ${longdate}:当前的日期和时间(多种格式)
- ${level}:记录等级
- ${logger}:来源名称
- ${callsite}: 输出跟踪消息的方法的堆栈信息
- ${machinename}: 计算机
- ${processName}: 进程
- ${threadId}: 线程名称
其他的形式可以参考 https://github.com/NLog/NLog/wiki/Layout-Renderers
5. 日志输出目标
NLog定义输出目标的基类Target,然后预定义了一些继承Target的子类,常见的有:
No | 对象 | 说明 |
---|---|---|
1 | FileTarget | 写入文件 |
2 | NetworkTarget | 通过UDP、TCP、HTTP等协议,把日志写入到网络 |
3 | MailTarget | 写入到邮件 |
4 | EventLogTarget | 写入Windows事件 |
5 | MemoryTarget | 写入内存 |
6 | MethodCallTarget | 把日志内容发送给函数 |
7 | ConsoleTarget | 发送到控制台 |
8 | TraceTarget | 调用System.Diagnostics.Trace对象,进行写日志 |
9 | DebugTarget | 主要为了测试用,只渲染日志内容,并不写入到任何对象 |
6. 输出目标封装
为了灵活性,Nlog还定义了一些继承了WrapperTargetBase
的包装程序、它对上面日志输出目标进行包装,从而改变日志输出行为,从而增加一些功能,常见的封装程序有:
No | 对象 | 说明 |
---|---|---|
1 | AsyncTargetWrapper | 异步写入,被封装的目标在另一个线程上运行 |
2 | AutoFlushTargetWrapper | 满足给定条件日志出现时候,自动提交写入操作 |
3 | BufferingTargetWrapper | 缓冲,先把日志写入到临时环形数组中,当调用Flush动作时,才进行写操作 |
4 | FilteringTargetWrapper | 过滤,只写入满足给定条件的日志,不满足的丢弃 |
5 | LimitingTargetWrapper | 设置给定时间内,最多能写入日志条数,超出的日志丢弃 |
6 | RepeatingTargetWrapper | 重复写日志 |
7 | RetryingTargetWrapper | 如果日志写入失败,自动重试 |
8 | RoundRobinGroupTarget | 负载均衡,顺序把日志写入到不同对象,如果有两个对象Target1,Target2,则把日志交替写入到这两个对象中 |
9 | SplitGroupTarget | 设置多个对象,把日志一次写入到多个对象 |
10 | FallbackGroupTarget | 灾难恢复,设置多个对象,如果日志写入失败,则尝试写入下一个对象 |
定义一个封装或者复合目标,你只需在一个目标节点里嵌套另一个目标节点即可。你甚至可以封装一个封装目标。嵌套的层数没有任何限制。比如,要给你的配置文件加上异步日志记录的功能,同时异步日志记录可以自动重试,你可以这样做:
<targets>
<target name="n" xsi:type="AsyncWrapper">
<target xsi:type="RetryingWrapper">
<target xsi:type="File" fileName="${file}.txt"/>
</target>
</target>
</targets>
7. 日志记录处理过程
每条日志信息的记录都需要经过下列步骤完成:
- 准备日志参数。
- 调用相应的日志方法(Trace(), Debug(), Info(), Warn(), Error(), Fatal() or Log())。
- 根据设置检查该级别的日志是否需要输出。
- 再检查是否有设置检查过滤器,若不满足过滤器条件,则不输出。
- 根据消息和传递给日志记录方法的参数对消息进行格式化。
- 最后一步是把日志发送给需要写入的输出目标。
Tip: 应当尽量避免自己进行字符串格式化来替代使用NLog中内建的格式化方法。因为格式化日志消息需要花费很长时间,所以NLog将格式化操作推迟到了当日志消息需要被输出的时候进行。如果消息的最后处理被跳过了,你将不需要花费时间在String.Format()上。
目前日志API中,利用FileTarget
、NetworkTarget
以及AsyncTargetWrapper
对象来实现了异步写文件日志和网络日志,那么接下就来逐一简要介绍这几个对象。
8. Target对象
Target
是所有输出对象的基类,它是一个抽象类,实现了ISupportsInitialize接口,
internal interface ISupportsInitialize
{
/// <summary>
/// 初始化实例
/// </summary>
/// <param name="configuration">配置信息</param>
void Initialize(LoggingConfiguration configuration);
/// <summary>
/// 关闭实例
/// </summary>
void Close();
}
void Initialize(LoggingConfiguration)
,主要是初始化Target,获取布局信息保存到List型属性 _allLayouts字段中。
void Close()
,调用了虚方法protected virtual void CloseTarget()
,具体实现由子类完成。
它还定义了一些以Write开头的一些重载方法,最终都是调用protected virtual void Write(LogEventInfo logEvent)
虚方法
protected virtual void Write(LogEventInfo logEvent)
protected virtual void Write(AsyncLogEventInfo logEvent)
protected virtual void WriteAsyncThreadSafe(AsyncLogEventInfo logEvent)
protected virtual void Write(AsyncLogEventInfo[] logEvents)
protected virtual void Write(IList<AsyncLogEventInfo> logEvents)
protected virtual void WriteAsyncThreadSafe(AsyncLogEventInfo[] logEvents)
protected virtual void WriteAsyncThreadSafe(IList<AsyncLogEventInfo> logEvents)
9. FileTarget对象
FileTarget日志输出对象主要功能是把日志写入到文件中。在内部根据设置不同,调用不同BaseFileAppender子类,来进行文件存储。
继承关系链是FileTarget : TargetWithLayoutHeaderAndFooter : TargetWithLayout : Target
9.1 常用属性配置
-
FileName
写入日志的文件名,支持Layout,即文件名字符串中可以包含布局呈现器中的实例变量,这样可以通过配置单个目标节点,而将日志写入多个文件中。例如:
<target xsi:type="File" fileName="${level}.log"/>
上面的配置,将Debug级别的日志写入到Debug.log文件中,Info级别的日志写入到Info.log文件中,其它以此类推。
-
BufferSize
日志文件缓存区大小(单位:字节)。默认值为32768(32KB)。
-
KeepFileOpen
是否保持日志文件处于打开状态,以代替其在每次日志是进行频繁打开和关闭文件。默认值为false。设置此属性为true,有助于提高性能。
-
OpenFileCacheTimeout
文件保持打开状态的最大时间秒数。如果设置为负数,则在一定不活动时间后,文件不会自动关闭。默认值为-1。(即默认状态下文件始终处于打开状态以备写入。)
-
OpenFileCacheSize
保持打开状态的文件数量。当通过设置单个文件类型目标,且结果写入多个不同文件(如根据日志等级或日志对象名称)时,则可将此属性设置为一个较 高的值以改善性能。其取值类型为Integer,默认值为5。这些文件是在最近最少使用算法基础管理的,此算法在缓存空间不足时将最长时间内没有使用的文件Flush。一般来说,不应当把此参数设置过大,最好不要超过10-15,这是因同时保持多个文件处于打开状态,对系统资源来说是一个很大的消耗。
-
NetworkWrites
是否通过多线程由不同的网络主机并行向文件中写入日志。默认值为false。通过此此种方式,可以有效阻止文件长期保存为打开状态。
-
ConcurrentWrites
是否允许使用通过多个进程的方式,将日志信息并行写入文件中。默认为true。这使得多进程记录日志成为可能。NLog使用一种特别的技术使用文件保持打开状态以备写入。使用了哪种技术,后续章节再进行说明
-
ConcurrentWriteAttempts
也就是说再写入文件前需要打开文件流,如果打开失败,进行尝试再打开的次数,默认值为10。
-
ConcurrentWriteAttemptDelay
和ConcurrentWriteAttemptDelay结合使用,在再次尝试将日志写入文件之前延迟的毫秒数。默认值为1。实际的延迟时间,是一个介于0到指定参数值之间的随机整数,且在每次尝试失败之后,都会将此参数加倍。假使此参数值为10,则第一次重试写入的延迟时间在 0-10之间的一个随机数,第二次重试时为0-20之间的一个随机数,以此类推。
-
AutoFlush
在每次日志信息后,是否自动刷新文件缓存区。其取值类型为Boolean,默认值为true。
-
DeleteOldFileOnStartUp
启动时,是否删除旧的日志文件。其取值类型为Boolean,默认为false,此选项,仅在“fileName”参数为单个文件时有效。
-
EnableFileDelete
是否允许删除日志文件。其取值类型为Boolean,默认为true。
-
CreateDirs
是否允许自动创建(不存在的)目录。其取值类型为Boolean,默认为true。如果设置为false,若目录不存在,则会在试图写入日志时报错。
-
文件存档相关属性(ArchiveAboveSize、ArchiveEvery、MaxArchiveFiles、…)
当设置了存档相关属性时、在多进程写文件的场合会显著的减慢文件写入的速度,如果只有一个进程写入该文件的话,设置属性ConcurrentWrites = false来最大化提高性能
9.2 写入动作逻辑简要说明
FileTarget重写了父类Target
的一些Write方法,来实现自己的写入逻辑,
在FileTarget中,主要做了以下几个操作
- 根据FilePathLayout,计算出文件的实际路径
- 根据日志的Layout,替换Layout模版中的宏对象,得到需要写入日志的实际内容
- 调用BaseFileAppender子类,把日志内容写入到实际文件中
BaseFileAppender
这个类主要定义了打开文件流的方法,针对不同平台使用了不同方法打开文件流
- Window API方式打开文件
- 标准的使用FileStream形式打开文件流
这些子类包含两类,一类是面对的是单进程写文件日志场景;而另外一类是考虑了多进程写文件内容的情况。
单进程文件写入类:
- SingleProcessFileAppender:单进程写文件
- CountingSingleProcessFileAppender:处理逻辑和上面类似,唯一的区别在于,定义了个记录文件当前长度的属性,每次写入文件内容时,就会累计,用于判断是否超过存档大小的设定值。
多进程文件写入类(前提是设置了并发写入属性ConcurrentWrites=true):
- MutexMultiProcessFileAppender:支持共享锁平台
- RetryingMultiProcessFileAppender:下面三种情况设置了使用此类
- KeepFileOpen=false
- NetworkWrites=true
- 不支持共享锁的平台
- WindowsMultiProcessFileAppender:Win平台使用
- UnixMultiProcessFileAppender:在Mono平台使用
10. NetworkTarget对象
NetworkTarget网络日志对象,它同样继承了Target对象,继承关系链为NetworkTarget : TargetWithLayout : Target。
10.1 常用属性配置
-
Address
网络地址,格式如tcp://host:port、udp://host:port
-
MaxMessageSize
每次发送网络数据的最大字节数,默认值为65000
-
OnOverflow
发送数据过大处理选项,即数据 > MaxMessageSize时处理,有三个选项:Error(出错),Split(分割发送),Discard(丢弃)
-
ConnectionCacheSize
最大保持活动链接数,默认值为5
-
MaxConnections
最大链接数
-
OnConnectionOverflow
链接数过大处理选项,即链接数 > MaxConnections时处理,有三个选项:AllowNewConnnection(开启新链接),DiscardMessage(丢弃),Block(等待有可用链接数)
10.2 写入动作逻辑简要说明
NetworkTarget重写了父类Target
的一些Write(AsyncLogEventInfo logEvent)方法,来实现自己的写入逻辑,
在NetworkTarget中,主要做了以下几个操作
- 根据Address,计算出文件的实际路径
- 根据日志的Layout,替换Layout模版中的宏对象,得到需要写入日志的实际内容
- 根据Address的前缀,调用不同NetworkTarget子类,把日志内容写入到实际的网络中
- Address以UDP开头:UdpNetworkSender
- Address以TCP开头:TcpNetworkSender
11. AsyncTargetWrapper对象
异步对象,定义在AsyncWrapper下的Target就具有了异步写的能力。AsyncTargetWrapper同样继承Target,继承链为
AsyncTargetWrapper : WrapperTargetBase : Target。
它定义了一个Target属性用来保存这个被封装的对象,同时在AsyncWrapper内部维护了一个队列,和一个定时器,来实现异步写入操作。
当写入的时候,首先把文件写入到这个队列,然后启动定时器,定时器的工作是从这个队列一次读取一条或者多条数据,把这个数据传递给被封装的Target。为了提高效率会在文件写入读取的时候,会根据队列中的数据动态调整定时器的执行周期。如,队列数据读取完,为空的时候,会禁用定时器。当有数据写入的时候有会开启定时器。