1 Log4net简介
2 Log4net结构分析
2.1 结构划分
2.2 结构总图
2.3 日志的记录过程分析
3 Log4net重点对象介绍
3.1 ILogger日志实体:
3.2 日志结构ILoggerRespository:
3.3 LogManager
3.4 ILoggerWrapper日志包装类:
3.5 LoggerManager 工具类.
3.6 IRepositorySelector
3.7 输出方式IAppender:
3.8 IFilter
3.9 ILayout
3.10 PatternLayout
3.11 ParttenConverter
3.12 OptionConverter
3.13 IObjectRenderer
3.14 IPlugin.
3.15 TextWriterAdapter
3.16 ContextPropertiesBase
3.17 SecurityContext
3.18 IErrorHandler
3.19 Log4net目前支持的输出方式包括
3.20 日志等级:
4 Log4net关注点.
4.1 Log4net的习惯用法
5 总结... 16
Log4net是基于.net开发的一款非常著名的记录日志开源组件。最早是2001年7月由NeoWorks Limited启动的项目,基本的框架源于另外的一个非常著名的姐妹组件-log4j。现由Apache组织开发与维护。此日志架构是可灵活扩展,且通过配置文件来设置日志的属性及输出,不同修改代码即可实现程序的灵活跟踪。可以将日志分不同的等级,通过不同的过滤条件,以不同的样式,将日志输出到不同的媒介。可以从http://logging.apache.org/log4net/downloads.html网站下载最新版本的Log4net源码。
log4net 有五种主要的组件:Logger(记录器)、Repository(库)、Appender(附着器)、Layout(布局)以及 Filter(过滤器)。虽然它们的职责各有不同,但它们通过有机组合,最终构成Log4net的大体框架和功能。
图1、Log4net框架的结构图
如上图所示,Utils命名空间里的类为Log4net的基础工具类,它提供对一些基本功能和服务。它为整个Log4net框架提供基础功能和服务。这里的类不参与Log4net中类的架构层次,却是为了使Log4net架构更清晰明了,把一些零散的基础的功能和服务集合在一起,形成了此命名空间。
Log系列对象属于最外层命名控件log4net中的核心,它提供了用户使用的接口,公用户使用来记录日志,主要是便于用户理解和使用,它对外隐藏了Log4net内复杂的架构和实现原理。
Config命名控件内的类主要负责Log4net组件的环境配置。它主要通过两种方式来配置Log4net组件,一种是通过设置程序集特性配置,另一种是通过直接在代码中加载配置文件。
Core命名空间容纳框架的核心接口、连接各个组件的管理类以及在多个组件或层次中都要使用的关键类。这里的接口和类一般不会暴露给用户扩展和使用。
由于Respository系列类中定义了日志对象(ILogger)的组织结构,他负责组织ILogger对象以及附加功能,所以Respository命名空间包括Respository系列类和ILogger对象的默认实现。以及专门为了组织日志对象需要的类。
Plugin命名空间提供了附加功能扩展接口以及默认实现类。
ObjectRender命名空间提供了对象的打印方式接口以及默认实现类。
Logger组件不是命名空间,但是它在框架中却是一个很重要的角色:日志对象类。由于它的重要性以及在框架中的应用广泛性,它的顶层接口ILogger在 Core命名空间中定义,而默认实现却在Respository命名空间中,就是为了实现对日志对象结构的组织和管理。有架构图可以看出,每个Logger对象都有自己的输出方式Appender。
Appender命名空间中的类负责日志的输出,定义日志的输出方式,框架中定义了16种输出方式。在Appender对象中可以定义自己的日志布局和过滤方式。
Filter命名空间为定义要输出的日志的过滤器接口及默认实现类。过滤器是以职责链的形式组织的。
Layout命名空间定义了日志的输出格式类。
用户在使用Log4net框架来记录日志时,需要通过LogManager.GetLogger()获取ILog对象,然后用ILog对象的Error()等方法记录日志。然而,在框架中日志对象是ILogger的实现类,ILog对象只是对ILogger对象的包装,以便于用户的理解和使用。
ILogger对象在Log4net中是已一定的层次结构组织的。这就是Repository的作用。ILogger拥有继承机制,可以继承父节点的Appender。ILogger拥有自己的Appender集合,负责输出日志。Appender拥有自己的过滤器Filter和布局Layout。
2.3.1 在创建日志对象前必须配置日志环境,通常是通过XML来配置的。下图是配置的过程:
图2、解析配置文件的类图
图2展现了两种配置方式:
一是直接在代码中通过调用XmlConfigurator.Configure()来解析配置文件,配置日志环境;
二是通过定义程序集特性来加载并解析配置文件,配置日志环境。不过这种方式只能在创建第一个ILog对象时才能加载配置文件。
2.3.2 在加载并解析配置文件时就会搭建Log4net日志环境。图3就是搭建日志环境以及创建各种对象的流程。
图3、搭建日志环境以及创建各种对象的流程
2.3.3 既然用户是通过ILog对象来记录日志的,那么如何获取ILog对象以及它又如何包装日志对象(ILogger对象)呢?图4即是创建ILog对象的过程。
图4、创建ILog对象的过程
ILog对象是通过LogManager工具类来创建的。LogManager工具类通过LoggerManager创建ILogger对象,然后使用ILoggerWraper对其进行包装,并通过WrapperMap进行管理Logger和ILoggerWraper之间的映射。
ILogger对象的集合在ILoggerRepository中按一定的结构进行组合和管理所以需要从 ILoggerRepository中获取ILogger对象。欲获取ILogger对象,必须首先获取ILoggerRepository对象。IRepositorySelector就是负责缓存和管理ILoggerRepository对象的类,所以需要通过IRepositorySelector获取ILogger对象所在的ILoggerRepository对象,然后再从ILoggerRepository对象中获取ILogger对象。
2.3.4 既然ILog对象不是日志对象,那么ILog又是如何记录日志的呢?
图5、记录日志的过程
用户通过LogManager.GetLogger()获取ILog对象,然后通过ILog对象的Error()等方法记录日志。但是ILog对象的方法最终会映射为ILogger对象的Log()方法。在调用ILogger的Log()方法时,会创建一个LoggingEvent对象,此对象记录了日志的信息,之后就可以把LoggingEvent对象在ILogger对象和IAppender对象之间传递,直到把日志信息写入TextWriter中才可销毁。LoggingEvent对象的生命周期是从调用ILogger的Log()方法起,直到此条消息写入TextWriter中为止。ILogger对象需要调用自己的IAppender对象来把日志信息写到媒体上,IAppender对象需要调用IFilter对象来过滤需要写入媒体的日志,然后调用ILayout对象来设置日志的输出格式。
在Log4Net架构中,对日志的记录是以日志实体为单位的。它记录日志的最低级别(高于此基本的消息都可以记录),日志的名称,以及维护日志结构的库(Repository)。它包含记录日志的操作。但在扩展日志类实体时,一般不直接实现ILogger接口,而是继承Logger类,这是一个虚类。它除了实现接口ILogger外,也实现了IAppenderAttachable,以确保使自己可以被绑定到Appender上。一个日志实体可以绑定到一个或多个Appender上。由于框架支持分层级的组织结构,所以它有一个属性Parent,已建立与上层的关联。RootLogger继承自Logger,它表示根层节点。
日志的结构是层级结构,可选择子日志对象(通过属性Additivity)是否继承父对象的Appender,子对象和父对象在一个Hierarchy中。同时通过两种方式记录日志的层次关系,一种为子日志对象指定其父日志对象,另一种方式是通过日志的名称来记录:如果有两个logger,分别被定义为a.b.c和a.b,那么我们说a.b是a.b.c的祖先。
若某一个日志对象有个父节点,但是这个父节点却还没有被指定,这时就可以用一个临时节点来代替,ProvisionNode的作用就在于此。它是一个集合对象,继承自ArrayList,可以暂时在此保存它的所有自节点,以方便于以后指定一个ILogger对象来替换它。
ILoggerRespository类就像是Log4net的骨架,他支撑着整个Log4net的结构并管理着Log4net的各个部件。下面我们分析一下ILoggerRespository的功能以及工作原理:
ILoggerRespository类中维护着LevelMap,PluginMap分别是Level,Plugin对象以其名称为键的集合,,Rendermap是IObjectRender以其类型为键的集合。而Hierarchy中存储着已创建的ILogger的对象集合,此集合是以LoggerKey为键的映射。
ILoggerRespository中维护并存储着这些对象的集合,但它并不负责创建这些对象,而是把创建的工作委托给了XmlHierarchyConfigurator。它负责解析对Log4net的Xml(节点)配置。Hierarchy类不直接创建ILogger对象,而是通过ILoggerFactory对象来创建ILogger对象。在Hierarchy类中保存ILoggerFactory的一个默认实现:DefaultLoggerFactory。
LogManager作为面向用户的接口,它是在启用配置文件后被应用程序调用来创建ILog对象的。我们知道ILog是继承自ILoggerWrapper,所以ILog对象也是对ILogger对象的封装,ILog暴露出的接口比ILogger对象接口更好理解,更好使用。但是最终在Log4net框架中操作的是ILogger对象,而用户看到的却是对ILog对象的操作,这中间又是如何关联的呢,或者说这其中的机制是什么呢?
在LogManager中有一个类型为WrapperMap的静态成员变量s_wrapperMap。我们先看看WrapperMap类,它被用来创建ILog对象,并维护ILogger对象与ILoggerWrapper对象的对应关系。它的映射关系是:
这个类复杂对这个映射关系的维护,包括创建、获取及销毁。但是,ILoggerWrapper对象的创建工作却是通过代理委托出去了,在这里是委托给LogManager中的一个静态方法了:private static ILoggerWrapper WrapperCreationHandler(ILogger logger)。
日志包装类是对日志对象的包装,通过此接口可以获取它说包装的ILogger对象。ILog是ILoggerWrapper的子接口,它是Log4Net Framework的对外接口。框架使用者通过它可以对各种日志对象进行操作,写相应基本的日志等。
LoggerManager 工具类被用来获取ILoggerRepository和ILogger对象。但它并不能直接创建和维护这些对象。所以它把创建ILoggerRepository对象的工作委托IRepositorySelector,它只负责IRepositorySelector对象的创建和维护工作。但是IRepositorySelector也只是负责对ILoggerRepository对象的创建和维护工作。所以LoggerManager通过IRepositorySelector获取ILoggerRepository对象,然后再通过获取到的ILoggerRepository对象获取ILogger对象。LoggerManager通过两种方式创建IRepositorySelector对象:从配置文件种获取对象类型或者创建默认对象:DefaultRepositorySelector。另外在LoggerManager中添加了销毁IRepository对象进程监控,即当应用程序进程关闭时,自动销毁IRepository对象,这种监控是在创建LoggerManager的静态构造函数时建立的。
IRepositorySelector是用来维护ILoggerRepository的,在Log4net框架中,它有两个子类:DefaultRepositorySelector和CompactRepositorySelector。由于CompactRepositorySelector是在嵌入式系统中用到的,所以我们只讨论DefaultRepositorySelector。虽说这是一个默认实现,但也是暂时为止唯一的实现。在此类中,以数据字典的方式维护着对ILoggerRepository的缓存。若是第一次使用某个名字的ILoggerRepository是需要创建的。它有两种创建方式,一是通过程序集属性获取ILoggerRepository的名称和类型,二是通过参数传入ILoggerRepository的名称和类型,然后通过反射创建出对象。另外,若是不通过配置文件配置Log4net设置,而是通过程序集设置,也是通过此类中的方法获取Log4net配置的xml文件,并加载配置。默认情况下使用的是默认的ILoggerRepository名称“log4net-default-repository”,这时是从配置文件“log4net.Config”中取配置项的。
IAppender有一个抽象子类:AppenderSkeleton。在此抽象类中完成了IAppender的基本功能的实现,几乎所有的底层类都继承此类。AppenderCollection类是IAppender对象的集合类,方便对IAppender的集合进行维护。
每个ILogger对象需要维护着自己的IAppender集合,并向里面写入日志内容。而这些工作可以看作一个单一的职责,类AppenderAttachedImpl负责此职责。所以IAppender只是拥有AppenderAttachedImpl的一个实例作为成员变量(域)就可以了,然后通过此实例做写入日志工作。
在配置文件中必须有一个<log>节点或<root>节点,用以绑定Appender对象等信息,而若没有<root>节点,ILogger的根节点就无实际意思,而设置的<log>节点为Root的第一层子节点,此时,而其他节点就会作为<log>节点的子节点。这时<log>节点的IAppender就会覆盖Root的IAppender.而其子节点会继承<log>节点的IAppender。
IFilter为输出Logger的过去方案,此过滤方案是负责哪些LoggingEvent应该被处理。过滤方案是在AppenderSkeleton中被使用的,并以以职责链的模式对IFilter对象的维护和使用。
ILayout为消息输出格式的定义类,通过此类,可以格式化输出消息的格式。此类也是在AppenderSkeleton中使用,以格式化输出文字的格式。
PatternLayout类中对输出的各种信息以系统框架定义的格式化方式进行格式化。虽说此类中会对ParttenConvertor对象进行缓存,但是还是通过PatternParser创建了一系列PatternConverter对象,并以职责链模式把他们组合在一起。通过这些PatternConvertor对象格式化日志对象内部数据(包括机器名称,用户名,当前进程ID,信息类型等信息)。
对于ParttenConverter系列的类分两个分支:一个是Util/PatternStringConverters下的类,这里的类主要处理系统级别的数据。这些数据需要在加载配置Log4net时加载,PatternString负责对这些对象进行维护,虽说此类中会对ParttenConverter对象进行缓存,但是还是通过PatternParser创建了一系列PatternConverter对象,并以职责链模式把他们组合在一起。而第二个分支是Layout/Pattern目录下的类,这些类用来格式化日志事件LoggerEvent对象内的数据。
OptionConverter工具类负责类型转换并解析及获取相应的值。而目标类型存储在ConverterRegistry中,此类负责类型转换器的注册及缓存。OptionConverter从ConverterRegistry中获取相应的目标类。目标类主要是指Util/TypeConverters下的类。其中类PatternLayoutConverter用来转换为PatternLayout对象,PatternStringConverter用来转换为PatternString对象。而类OptionConverter主要在类XmlHierarchyConfigurator中解析配置文件时被调用。
IObjectRenderer对象及其子类用来以字符串的形式输出对象,在框架中它只有一个子类:DefaultRender,而RenderMap对象时以IObjectRenderer类型为键的映射集合。在ILoggerRepository中被调用,而IObjectRenderer对象则是在XmlHierarchyConfigurator中通过反射被创建,并保存在ILoggerRepository对象中。
IPlugin对象是用来定义附加的操作行为,IPluginFactory负责创建IPlugin对象,在架构中特性类PluginAttribute继承IPluginFactory。所以需要通过设置程序集特性来配置IPlugin类型,并且在DefaultRepositorySelector中通过反射创建IPlugin对象并加载到ILoggerRepository对象中。
TextWriterAdapter是通过适配器模式对TextWriter类进行了封装,以便于创建适用于特殊用户的TextWriter对象。在框架中主要由两类:ProtectCloseTextWriter用来隐藏其Close()函数,QuietTextWriter屏蔽其内部产生的异常,并把异常记录到框架自己的日志里。
ContextPropertiesBase抽象类是上下文属性类的基类,此系列类是用来存储某一类型上下文的字典类。
这些属性分别有GlobalContext,ThreadContext,LogicalThreadContext来创建,并在LoggingEvent,PropertyPatternConverter被调用来生成各种参数。其中LogicalThreadContextProperties是通过CallContext.GetData("log4net.Util.LogicalThreadContextProperties")取得数据,也就是说它支持通过Remoting在客户端与服务器端传输的数据。ThreadContextProperties是通过System.Threading.Thread.GetData(s_threadLocalSlot)来获取数据,即是存储在当前线程的数据插槽中,使之与其它线程隔绝。GlobalContextProperties是全局性上下文属性,存储在字典集合中,包含进程级数据。
SecurityContext类用来提供受保护的数据,比如模拟一个系统用户。这是一个抽象类,在框架中有两个个实现NullSecurityContext和WindowsSecurityContext. NullSecurityContext对象是一个空实现,不做任何操作,它由SecurityContextProvider类负责创建。WindowsSecurityContext是对Windows域用户的模拟。这些上下文对象在部分IAppender中被使用,以保证数据操作的安全性,例如:AdoNetAppender,FileAppender等。
IErrorHandler属于异常处理类,它被委托来处理异常。它在框架中只有一个实现类:OnlyOnceErrorHandler,用来在框架内部日志中记录异常信息。异常处理类在AppenderSkeleton中和QuietTextWriter中被使用。
1 AdoNetAppender
将日志记录到数据库中。可以采用SQL和存储过程两种方式。
2 AnsiColorTerminalAppender
在ANSI 窗口终端写下高亮度的日志事件。
3 AspNetTraceAppender
能用asp.net中Trace的方式查看记录的日志。
4 BufferingForwardingAppender
在输出到子Appenders之前先缓存日志事件。
5 ConsoleAppender
将日志输出到控制台。
6 EventLogAppender
将日志写到Windows Event Log.
7 FileAppender
将日志写到文件中。
8 LocalSyslogAppender
将日志写到local syslog service (仅用于UNIX环境下).
9 MemoryAppender
将日志存到内存缓冲区。
10 NetSendAppender
将日志输出到Windows Messenger service.这些日志信息将在用户终端的对话框中显示。
11 RemoteSyslogAppender
通过UDP网络协议将日志写到Remote syslog service。
12 RemotingAppender
通过.NET Remoting将日志写到远程接收端。
13 RollingFileAppender
将日志以回滚文件的形式写到文件中。
14 SmtpAppender
将日志写到邮件中。
15 TraceAppender
将日志写到.NET trace 系统。
16 UdpAppender
将日志connectionless UDP datagrams的形式送到远程宿主或以UdpClient的形式广播。
日志等级分为:(Unrecoverable errors,Recoverable errors,Information,Debug)不可恢复的错误一定要记录下来,用户不可轻易删除,以便于开发人员分析处理。可恢复的错误,也要记录,并提供处理措施,应有查看及分析工具支持。
消息日志用来记录一些关键操作,可作为用户监控,需要分析工具支持。调试日志,用来供开发人员和维护人员跟踪操作流程以及分析问题,平时需要屏蔽,仅在相关维护人员跟踪问题时再临时开放日志权限。
级别 |
允许的方法 |
Boolean属性 |
优先级别 |
分类 |
OFF |
|
|
Highest |
|
FATAL |
void Fatal(...); |
bool IsFatalEnabled; |
|
Unrecoverable |
RROR |
void Error(...); |
bool IsErrorEnabled; |
|
Recoverable |
WARN |
void Warn(...); |
bool IsWarnEnabled; |
|
|
INFO |
void Info(...); |
bool IsInfoEnabled; |
|
Information |
DEBUG |
void Debug(...); |
bool IsDebugEnabled; |
|
Debug |
ALL |
|
|
Lowest |
|
1) 使用xml文件来完成对Log4net环境的配置。
2) 在项目的main class中的静态初始化块里放Log4net环境的配置代码。注意:在一个项目中,Log4net环境只需要被配置一次,而不是在每个使用了logger的类里都需要调用一次 。
3) 让每个类都拥有一个private static的Logger对象,用来输出该类中的全部日志信息 .
4) 用MyClass.class作为参数创建该类的静态Logger对象 .
5) 封装一个ILog对象代理。
6) 做一个日志工具类,通过LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)获取ILog对象。
当前HR系统中的日志不够完善,日志分类不够详细,没有对日志文件进行系统管理的策略,使日志的功效没有完全发挥,所以需要对HR的日志功能进行改造。下面是改造的建议。
1) 需要指定记录日志规范:什么情况下记录什么日志。不可恢复错误日志是必须记且用户不可删除的,可恢复错误日志也必须记,但客户可以自己维护,消息日志记录一些关键操作,便于客户监控,用户可以选择是否记录,调试日志平时不必记录,只在跟踪问题时才打开。
2) 客户端操作中的不可恢复错误日志必须在服务端有记录。
3) 日志输出方式:基于不同等级的日志的功用不同,所需要的日志输出方式也不同。不可恢复错误的日志建议存放到数据库中;可恢复的错误,建议保存到XML中,便于分析,可删除;消息日志可存储到数据库、XML中;而调试日志,存放于txt中即可。
4) 需要有日志查看工具,便于查看分析日志。
5) 需要制订日志文件管理策略。