NLog开源框架说明

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。LoggerLogManager这两个类将会经常被使用到。

  • Logger代表了一些被命名的日志源,他们有一些写日志消息的成员方法。
  • LogManager则是用于创建和管理Logger对象的实例。

其中Logger,根据日志配置、内部调用输出目标Target的Write方法,把日志写入实际输出目标中。

using NLog;
Logger logger = LogManager.GetLogger("loggerName");
logger.Info("info log message");

上面的日志配置,可以是使用文件的配置方式,也可以是编程配置的方式

2 日志配置(文件方式)

配置文件,支持两种配置方式:

  1. 配置信息嵌入在.NET应用程序标准的*.exe.config或者web.config文件里
  2. 保存在独立文件里,也叫单一格式

如果使用第一种方式,使用的是标准的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配置文件中都必须提供,其余的则可以选择使用,通常用于一些复杂场景,暂时不进行说明。

  1. - 定义日志的目标/输出
  2. - 定义日志的路由规则
  3. - 从*.dll加载NLog扩展
  4. - 导入外部配置文件
  5. - 为配置变量赋值
  • 定义日志的目标/输出

      <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将在以下目录搜索配置信息:
    1. 标准的程序配置文件(通常为 程序名.exe.config)
    2. 程序目录下的程序名.exe.nlog文件
    3. 程序目录下的NLog.config文件
    4. NLog.dll所在目录下的NLog.dll.nlog文件
    5. 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件
  • 如果是一个ASP.NET程序,被搜索的目录包括:
    1. 标准的web程序配置文件web.config
    2. 和web.config在同一目录下的web.nlog文件
    3. 程序目录下的NLog.config文件
    4. NLog.dll所在目录下的NLog.dll.nlog文件
    5. 如果定义了NLOG_GLOBAL_CONFIG_FILE环境变量,则该变量所指向的文件

3 日志配置(编码方式)

某些情况下我们可能不愿用配置文件来配置NLog,而是选择使用NLog的配置API进行。若希望以编程的方式对NLog进行配置,我们需要:

  1. 创建一个LoggingConfiguration对象,用来保存配置信息
  2. 至少创建一个输出目标对象
  3. 设置该输出目标对象的属性
  4. 设置LoggingRule对象,并将其添加到LoggingConfiguration对象的LoggingRules集合中
  5. 启用该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对象说明
1FileTarget写入文件
2NetworkTarget通过UDP、TCP、HTTP等协议,把日志写入到网络
3MailTarget写入到邮件
4EventLogTarget写入Windows事件
5MemoryTarget写入内存
6MethodCallTarget把日志内容发送给函数
7ConsoleTarget发送到控制台
8TraceTarget调用System.Diagnostics.Trace对象,进行写日志
9DebugTarget主要为了测试用,只渲染日志内容,并不写入到任何对象

6. 输出目标封装

为了灵活性,Nlog还定义了一些继承了WrapperTargetBase的包装程序、它对上面日志输出目标进行包装,从而改变日志输出行为,从而增加一些功能,常见的封装程序有:

No对象说明
1AsyncTargetWrapper异步写入,被封装的目标在另一个线程上运行
2AutoFlushTargetWrapper满足给定条件日志出现时候,自动提交写入操作
3BufferingTargetWrapper缓冲,先把日志写入到临时环形数组中,当调用Flush动作时,才进行写操作
4FilteringTargetWrapper过滤,只写入满足给定条件的日志,不满足的丢弃
5LimitingTargetWrapper设置给定时间内,最多能写入日志条数,超出的日志丢弃
6RepeatingTargetWrapper重复写日志
7RetryingTargetWrapper如果日志写入失败,自动重试
8RoundRobinGroupTarget负载均衡,顺序把日志写入到不同对象,如果有两个对象Target1,Target2,则把日志交替写入到这两个对象中
9SplitGroupTarget设置多个对象,把日志一次写入到多个对象
10FallbackGroupTarget灾难恢复,设置多个对象,如果日志写入失败,则尝试写入下一个对象

定义一个封装或者复合目标,你只需在一个目标节点里嵌套另一个目标节点即可。你甚至可以封装一个封装目标。嵌套的层数没有任何限制。比如,要给你的配置文件加上异步日志记录的功能,同时异步日志记录可以自动重试,你可以这样做:

<targets> 
  <target name="n" xsi:type="AsyncWrapper"> 
    <target xsi:type="RetryingWrapper"> 
      <target xsi:type="File" fileName="${file}.txt"/> 
    </target> 
  </target> 
</targets> 

7. 日志记录处理过程

每条日志信息的记录都需要经过下列步骤完成:

  1. 准备日志参数。
  2. 调用相应的日志方法(Trace(), Debug(), Info(), Warn(), Error(), Fatal() or Log())。
  3. 根据设置检查该级别的日志是否需要输出。
  4. 再检查是否有设置检查过滤器,若不满足过滤器条件,则不输出。
  5. 根据消息和传递给日志记录方法的参数对消息进行格式化。
  6. 最后一步是把日志发送给需要写入的输出目标。

Tip: 应当尽量避免自己进行字符串格式化来替代使用NLog中内建的格式化方法。因为格式化日志消息需要花费很长时间,所以NLog将格式化操作推迟到了当日志消息需要被输出的时候进行。如果消息的最后处理被跳过了,你将不需要花费时间在String.Format()上。

目前日志API中,利用FileTargetNetworkTarget以及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中,主要做了以下几个操作

  1. 根据FilePathLayout,计算出文件的实际路径
  2. 根据日志的Layout,替换Layout模版中的宏对象,得到需要写入日志的实际内容
  3. 调用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中,主要做了以下几个操作

  1. 根据Address,计算出文件的实际路径
  2. 根据日志的Layout,替换Layout模版中的宏对象,得到需要写入日志的实际内容
  3. 根据Address的前缀,调用不同NetworkTarget子类,把日志内容写入到实际的网络中
    • Address以UDP开头:UdpNetworkSender
    • Address以TCP开头:TcpNetworkSender

11. AsyncTargetWrapper对象

异步对象,定义在AsyncWrapper下的Target就具有了异步写的能力。AsyncTargetWrapper同样继承Target,继承链为

AsyncTargetWrapper : WrapperTargetBase : Target。

它定义了一个Target属性用来保存这个被封装的对象,同时在AsyncWrapper内部维护了一个队列,和一个定时器,来实现异步写入操作。

当写入的时候,首先把文件写入到这个队列,然后启动定时器,定时器的工作是从这个队列一次读取一条或者多条数据,把这个数据传递给被封装的Target。为了提高效率会在文件写入读取的时候,会根据队列中的数据动态调整定时器的执行周期。如,队列数据读取完,为空的时候,会禁用定时器。当有数据写入的时候有会开启定时器。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值