工作原理一

要知道Log4net究竟是咋干活的,咱们可以从下面这个脉络简图入手。你的程序中的语句log4net.LogManager.GetLogger().Info(“hello world!”); 就会引发log4net如下内部工作流程。不要管上面的对象(Appender/Filter等等)是什么东东,先看着这个流程,我们来摸摸log4net工作的脉络,然后我们再按关节一一打通。
log4net工作原理 - 图1

  1. 第一件事就是找调度(LogManager)要个干活的工人(Logger,写日志的对象),当然,方法是调用LogManager.GetLogger()。找个什么样工人,究竟是那个工人会被挑中,这里面有些曲折,会涉及到Repository(高级话题,下回分解)。咱先不管这么多,知道有个能干活的工人(Logger) 肯定是被找来了。
    另外有些麻烦事儿,这工人有经纪人ILoggerWrapper(Logger需要实现ILogger, 而ILoggerWrapper唯一的方法就是得到ILogger实例),经纪人又有代理ILog(ILog继承于ILoggerWrapper)。代理ILog存在的意义在于给你提供方便的接口函数(Info,Warn等等),而不是工人提供的void Log(string callerFullName, Level level, object message, Exception t)。 不管关系多复杂,虽然你让干什么活都得先对代理说,但最后还都是告诉了工人,一个字也没落。
  1. 你通过Info(“hello”)告诉工人干活了,工人Logger一定先看看这事能不能干。你的配置里说只写Info这个级别以上的信息,咱就不能写Debug和Warn。这种情况你需要付出性能代价(一个函数调用和一个整数形式的级别比较)。然后,工人Logger就创建一个任务包LoggingEvent,把你要做的事儿用任务包的形式包起来,以后的流程就都针对任务包LoggingEvent处理了。
    任务包LoggingEvent里信息丰富,包含:时间代码位置、工人的名字、信息、线程名、用户名、信息、异常、上下文等等。

  2. 接下来,Appender们登场了。原来工人自己不干具体的活,手里拽着一堆马仔,自己成了工头,告诉Appender去DoAppend(),让马仔们干活。注意,这里说得是“马仔们”,就是说同时会有多个马仔都在写东东。究竟那些马仔能被选中完成这光荣的任务,还要由客户您来决定,如:
    这些马仔及其特长:

马仔 特长
ConsoleAppender 在控制台上写日志
ColoredConsoleAppender ConsoleAppender的徒弟,青出于蓝,写出来的东东还可以带颜色,花花绿绿的,煞是好看
FileAppender 往文件里写日志
RollingFileAppender 往可滚动的文件里写日志,就是说它会按客户要求控制文件大小和数量,一个文件写满就帮你再开另一个接着写
ForwardingAppender 帮其他Appender传任务包的人,当然自己可以干一点雁过拔毛的事(做一些过滤的事情,比如说你要写的东东里包含不雅的词汇,它可以让你变得文明一些)
NetSendAppender 往Windows的Messager写日志
ASPNetTraceAppender 在Asp.Net的Trace里写日志
ADONetAppender 往数据库里写日志
EventLogAppender 往Windows事件里写日志
RemotingAppender 把日志转给另外的Remoting服务,如:一个专门的集中的日志服务器
SmtpAppender 通过邮件把日志发出去
SmtpPickupDirAppender 把日志包成邮件,发在指定目录,等待专门的Smtp代理去发送
TelnetAppender 把日志发到Telnet控制台
UdpAppender 通过UDP协议把日志发给另外的一个主机,或者组播给一些主机

当然,你可以自己搞个马仔,比如发个短信什么的,可以取名叫MobilePagerAppender(从AppenderSkeleton继承),通过配置告诉log4net就行。

  1. 说到这儿,检查员Filter登场。这活最终究竟干不干,马仔还得通过Decide()再问问检查员们。注意,这里说得是“检查员们”,就是说所有在册的检查员都点头,这话才能干。如何让检查员在册,看配置文件,如:
  1. <appender name="FF" type="log4net.Appender.ForwardingAppender" >
  2. <filter type="log4net.Filter.LevelRangeFilter">
  3. <param name="LevelMin" value="DEBUG"/>
  4. <param name="LevelMax" value="INFO"/>
  5. </filter>
  6. <appender-ref ref="ConsoleAppender" />
  7. </appender>

雁过拔毛的马仔ForwardingAppender和检查员LevelRangeFilter配合工作,把大于Debug和小于Info的东东通知给马仔ConsoleAppender,让它写到控制台上。

每个检查员都有自己的关注点,如下:

检查员 特长
LevelMatchFilter 日志级别等于指定的级别才放行
LevelRangeFilter 按日志级别范围做比较,可取区间内的放行。如必须大于Warn小于Info
StringMatchFilter 对你的言论进行检查,符合字符串比对条件的放行。如:必须包含“芝麻开门”的字符串才让写。
比对条件可以是简单的带通配符的字符串,也可以是正则表达式(帅!)
PropertyFilter StringMatchFilter的徒弟,对LoggingEvent的某个属性进行检查,符合字符串匹配条件才放行
LoggerMatchFilter 检查工头(Logger)的名字,如果是以指定的字符串开头的才放行。如:只要是姓“张”的工头发下来的任务包,都让过。
DenyAllFilter 这个检查员最黑,什么都不让过
  1. 检查员们点头后,这事就必须要干了。怎么干?客户要写的东东究竟用什么格式输出?这活由排版员Layout来干。下面是排版员的名单:
排版员 特长
ExceptionLayout 对LoggingEvent中的异常信息message进行排版
PatternLayout 最常用的排版员,通过一堆标识符来决定版式。
如:”%date %-5level- %message” 表示要以此输出日志日期、级别(5个字母的宽度)、信息
SimpleLayout 最简单的版式: [level] - [message]
XmlLayout 把日志写到XML文件中去,写成一个Element
XmlLayoutSchemaLog4j 把日志写到XML文件中去,写成一个Element,其格式需符合log4j对事件定义的DTD.

排版员需要排版LoggingEvent的信息的字符串内容RenderedMessage,例如文章开头的“hello world!”。除了“hello world!”这样的字符串,信息message还可以是任意的对象。因此需要针对对象进行专门的排版,由Render(对象打印机)来干。
你可以针对自己的信息对象搞Render。如打印订单信息的OrderRenader,一旦在订单处理中发生错误,把订单的主要信息打印出来,方便调试。别忘了:OrderRenader必须实现log4net.ObjectRenderer.IObjectRenderer。

  1. 一切就绪,各个马仔就做最后的输出,有打印屏幕的,有写文件的,有在网络上发数据的,八仙过海,各显神通。

整个流程走完,相信我们接触到的Logger、Appender、Filter、Layout、Render都已不再陌生。log4net良好的实现了事件过滤、格式排版的高度扩展性和可配置性。
log4net的这处理模式可以看作是一种扩展的Publish/Subscribe模式,完全可以应用到我们自己的应用程序中去,比如说订单处理,可以实现对不同订单的过滤,实现不同的订单的提交目的地(写数据库、发邮件、短信通知等等)。

最后,给出Repository、Appender、Filter、Layout、Render的关系简图:
log4net工作原理 - 图2

虽然,Repository在下回分解,但这里还需要简单说两句。Repository可以说成基于一个log4net配置节创建的log4net容器,它根据log4net配置节的指示创建以上其他的对象并保有他们的实例,随时为你所用。一般而言,你的应用程序不需要关心它,用缺省的容器即可。

工作原理二

Repository可以说成基于一个log4net配置节创建的log4net容器,它根据log4net配置节的指示创建其他所有对象(Logger/Appender/Filter/Layout等等)并保有他们的实例,随时为你所用。

每个Repository都有自己唯一的名字,如 root。

一般而言一个AppDomain(或者说一个进程)有一个Repository,该AppDomain下所有程序集Assembly都可以使用这个Repository。Repository需要实现ILoggerRepository,log4net中log4net.Repository.Hierarchy.Hierarchy就通过继承LoggerRepositorySkeleton实现了ILoggerRepository,它也是log4net中唯一实现ILoggerRepository的类。

Hierarchy

那么Hierarchy是什么呢?

Hierarchy里存放着通过配置文件创建的所有Logger。由于Logger们是有父子关系的,因此Hierarchy通过继承树来存放所有的Logger。根节点就是我们熟悉的Root,如例:

Logger 名 日志级别 从父Logger继承的级别
root INFO INFO
my none INFO
my.net DEBUG DEBUG
my.net.tcp none DEBUG

对应配置文件,应该是:

  1. <root>
  2. <level value="INFO" />
  3. <appender-ref ref="ConsoleAppender" />
  4. </root>
  5. <logger name=" my">
  6. <appender-ref ref="ConsoleAppender" />
  7. </logger>
  8. <logger name=" my.net ">
  9. <appender-ref ref="ConsoleAppender" />
  10. </logger>
  11. <logger name=" my.net.tcp">
  12. <filter type="log4net.Filter.LevelRangeFilter">
  13. <param name="LevelMin" value="DEBUG"/>
  14. <param name="LevelMax" value="INFO"/>
  15. </filter>
  16. <appender-ref ref=" ColoredConsoleAppender" />
  17. </logger>

上例中,定义了三个Logger,都将存放在Hierarchy中。三个Logger形成继承关系,子Logger中未定义的属性都将从父Logger中继承。

一旦你的应用程序通过log4net.LogManager.GetLogger()得到ILog(也就是logger的代理),那么将从Hierarchy的继承树中找出对应的Logger。

log4net.LogManager.GetLogger() 得到 root
log4net.LogManager.GetLogger(“my”) 得到 my logger

这样,你就可以为程序集中不同的命名空间甚至是某个类设置相应的log4net配置。如上例“my.net.tcp”就可以实现和其父Logger不同的日志行为。

使用不同的Repository

如果你的应用程序中不同程序集需要使用不同配置节,或者说需要使用不同的log4net配置文件,那就使用不同的Repository。

如在my.net.tcp程序集中,加入语句:[assembly: log4net.Config.DOMConfigurator(ConfigFile=”my.net.tcp.config”, Watch=true)]

这样,你的就可以单独使用一份配置文件,创建一个新的Repository。

你也可以为自己的Repository命名: [assembly: log4net.Config.AliasRepository(“myrepository”)]

如何共用Repository

不作上面所说的所有改动,一个AppDomain中所有程序集都共用缺省的Repository,但是当需要共用另一个Repository时,就需要做一些工作。产生这样的需求包括:

  1. 两个应用程序共用一份log4net配置,对日志做同样的处理
  2. 两个AppDomain需要共用一份log4net配置,对日志做同样的处理。特别时在运行时动态升级程序集时,这个需求显得尤其关键。

首先记载log4net的程序集需要为Repository命名:[assembly: log4net.Config.AliasRepository(“myrepository”)]

后续的程序集,只需要引用它即可:[assembly: log4net.Config.Repository(“myrepository”)]

这种方式下,两个AppDomain写同一份日志文件时,可能产生文件共享冲突的错误(文件已经被锁定,不能写),需要修改配置,在RollingLogFileAppender中加入lockingModel配置,如:

  1. <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
  2. <param name="File" value="log\\TaskScheduleServer.log" />
  3. <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
  4. </appender>