原文出处:http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html。感谢上善若水的无私分享。html
在简单的介绍了Log4J各个模块类的做用后,如下将详细的介绍各个模块的具体做用以及代码实现。java
Logger是对记录日志动做的抽象,它提供了记录不一样级别日志的接口,日志信息能够包含异常信息也能够不包含:apache
Logger类包含Level信息 ,若是当前Logger未设置Level值,它也能够中父节点中继承下来,该值能够用来控制该Logger能够记录的日志级别:数组
Logger是一个命名的实体,其名字通常用”.”分割以体现不一样Logger的层次关系,其中Level和Appender信息能够从父节点中获取,于是Logger类中还具备name和parent属性。服务器
在某些状况下,咱们但愿某些Logger只将日志记录到特定的Appender中,而不想记录在父节点中的Appender中,Log4J为这种需求提供了additivity属性,即对当前Logger节点,若是其additivity属性设置为false,则该Logger不会继承父节点的Appender信息,可是其子节点依然会继承该Logger的Appender信息,除非子节点的additivity属性也设置成了false。app
最后,为了支持国际化,Log4J还提供了两个l7dlog()方法,经过指定的key,以从资源文件中获取消息内容。为了使用这两个方法,须要设置资源文件。一样,资源文件也是能够从父节点中继承的。ide
另外,在实际开发中常常会遇到要把日志信息同时写到不一样地方,如同时写入文件和控制台,于是一个Logger实例中能够包含多个Appender,为了管理多个Appender,Log4J抽象出了AppenderAttachable接口,它定义了几个用于管理多个Appender实例的方法,这些方法由AppenderAttachableImpl类实现,而Logger会实例化AppenderAttachableImpl,并将这些方法代理给该实例:oop
有时候,为了测试等其余需求,咱们但愿Logger自己不作什么事情,Log4J为这种需求提供了NOPLogger类,它继承自Logger,可是基本上的方法都为空。性能
Level是对日志级别的抽象,目前Log4J支持的级别有FATAL、ERROR、WARN、INFO、DEBUG、TRACE,从头至尾一次级别递减,另外Log4J还支持两种特殊的级别:ALL和OFF,它们分别表示打开和关闭日志功能。测试
每一个Level实例包含了该Level表明的int值(通常是从级别低到级别高一次增大)、该Level的String表达、该Level和系统Level的对应值。
Level类主要提供了判断哪一个Level级别更高的方法isGreaterOrEqual()以及将int值或String值转换成Level实例的toLevel()方法:
另外,因为对相同级别的Level实例来讲,它必须是单例的,于是Log4J对序列化和反序列化作了一些处理。即它的三个成员都是transient,真正序列化和反序列化的代码本身写,而且加入readResolve()方法的支持,以保证反序列化出来的相同级别的Level实例是相同的实例。
若是要实现本身的Level类,能够继承自Level,而且实现相应的静态toLevel()方法便可。关于如何实现本身的Level类将会在配置文件相关小节中详细讨论。
LoggerRepository从概念以及字面上来讲它就是一个Logger实例的容器:一方面相同名字的Logger实例只须要建立一次,在后面的使用中,只须要从这个容器中取便可;另外一方面,Logger容器能够存放从配置文件中解析出来的信息,从而使配置信息能够无缝的应用到Log4J内部系统中;最后Logger容器还为维护Logger的树状层次结构提供了方面,每一个Logger只维护父节点的信息,有了Logger容器的存在则能够很容易的找到一个新的Logger实例的父节点;关于Logger容器将在下一节中详细讲解。
LoggingEvent我的感受用LoggingContext更合适一些,它是对一第二天志记录时哪能获取到的数据的封装。它包含了如下信息以提供Layout在format()方法中使用:
1. fqnOfCategoryClass:日志记录接口(默认为Logger)的类全名,该信息主要用于计算日志记录点的源文件、调用方法以及行号等位置信息。
2. locationInfo:经过fqnOfCategoryClass计算位置信息,位置信息的计算由LocationInfo类实现,这些信息能够提供给Layout使用。
3. logger:目前来看主要是经过Logger实例取得LogRepository实例,并经过LogRepository取得注册的ObjectRender实例,若是有的话。
4. loggerName:当前日志记录的Logger名称,提供给Layout使用。
5. threadName:当前线程名,提供给Layout使用。
6. level:当前日志的级别,提供给Layout使用。
7. message:当前日志类,通常是String类型,可是也能够经过注册ObjectRender,而后传入相应的其余对象类型。
8. renderedMessage:通过ObjectRender处理后的日志信息,提供给Layout使用。
9. throwableInfo:异常信息,若是存在的话,提供给Layout使用。
10. timestamp:建立LoggingEvent实例的时间,提供给Layout使用。
11. 其余相对不经常使用的信息将会在后面小节中讲解。
LoggingEvent只是一个简单的数据对象(DO),于是其实现仍是比较简单的,即在建立实例时将数据提供给它,在其余类(Layout等)使用它时经过getXXX()方法取数据。不过仍是有几个方法能够简单的讲解一下。
LocationInfo所指的位置信息主要包括记录日志所在的源文件名、类名、方法名、所在源文件的行号。
咱们知道在异常栈中每一条记录都包含了方法调用对应的这些信息,Log4J的这些信息正是利用了这个原理,即经过构建一个Throwable实例,然后在该Throwable的栈信息中解析出来的:
以上Throwable通常会产生以下异常栈:
于是咱们就能够经过callers.fully.qualified.className信息来找到改行信息,这个className信息便是传入的fqnOfCategoryClass。
若是当前JDK版本是1.4以上,咱们就能够经过JDK提供的一些方法来查找:
不然,则须要咱们经过字符串查找的方式来查找:
对于经过字符串查找到的fullInfo值,在获取其余单个值时还须要作相应的字符串解析:
className:
Log4J中,对传入的message实例,若是是非String类型,会先使用注册的ObjectRender(在LogRepository中查找注册的ObjectRender信息)处理成String后返回,若没有找到相应的ObjectRender,则使用默认的ObjectRender,它只是调用该消息实例的toString()方法。
ThrowableInformation类用以处理异常栈信息,即经过Throwable实例获取异常栈字符串数组。同时还支持自定义的ThrowableRender(在LogRepository中设置),默认的ThrowableRender经过系统printStackTrace()方法来获取信息:
Layout负责将LoggingEvent中的信息格式化成一行日志信息。对不一样格式的日志可能还须要提供头和尾等信息。另外有些Layout不会处理异常信息,此时ignoresThrowable()方法返回false,而且异常信息须要Appender来处理,如PatternLayout。
Layout的实现比较简单,如SimpleLayout对一行日志信息只是打印日志级别信息以及日志信息。
关于Layout更详细的信息将会在之后小节中介绍。
Appender负责定义日志输出的目的地,它能够是控制台(ConsoleAppender)、文件(FileAppender)、JMS服务器(JmsLogAppender)、以Email的形式发送出去(SMTPAppender)等。Appender是一个命名的实体,另外它还包含了对Layout、ErrorHandler、Filter等引用:
简单的,在配置文件中,Appender会注册到Logger中,Logger在写日志时,经过继承机制遍历全部注册到它自己和其父节点的Appender(在additivity为true的状况下),调用doAppend()方法,实现日志的写入。在doAppend方法中,若当前Appender注册了Filter,则doAppend还会判断当前日志时候经过了Filter的过滤,经过了Filter的过滤后,若是当前Appender继承自SkeletonAppender,还会检查当前日志级别时候要比当前Appender自己的日志级别阀门要打,全部这些都经过后,才会将LoggingEvent实例传递给Layout实例以格式化成一行日志信息,最后写入相应的目的地,在这些操做中,任何出现的错误都由ErrorHandler字段来处理。
目前Log4J实现的Appender都继承自SkeletonAppender类,该类对Appender接口提供了最基本的实现,而且引入了Threshold的概念,即全部的比当前Appender定义的日志级别阀指要大的日志才会记录下来。
SkeletonAppender实现了doAppend()方法,它首先检查日志级别是否要比threshold要大;而后若是注册了Filter,则使用Filter对LoggingEvent实例进行过滤,若是Filter返回Filter.DENY则doAppend()退出,不然执行append()方法,该方法由子类实现。
在Log4J中,Filter组成一条链,它定了以decide()方法,由子类实现,若返回DENY则日志不会被记录、NEUTRAL则继续检查下一个Filter实例、ACCEPT则Filter经过,继续执行后面的写日志操做。使用Filter能够为Appender加入一些出了threshold之外的其余逻辑,因为它自己是链状的,并且它的执行是横跨在Appender的doAppend方法中,于是这也是一个典型的AOP的概念。Filter的实现将会在下一小节中讲解。
SkeletonAppender还重写了finalize()方法,这是由于Log4J自己做为一个组件,它可能仍是经过其余组件如commons-logging或slf4j组件间接的引入,于是使用它的程序不该该对它存在依赖的,然而在程序退出以前全部的Appender须要调用close()方法以释放它所占据的资源,为了避免在使用Log4J的程序手动的close()的方法,以减小Log4J代码的侵入性,于是Log4J将close()的方法调用加入到finalize()方法中,即在垃圾回收器回收Appender实例时就会调用它的close()方法。
WriterAppender将日志写入Java IO中,它继承自SkeletonAppender类。它引入了三个字段:immediateFlush,指定没写完一条日志后,即将日志内容刷新到设备中,虽然这么作会有一点性能上的损失,可是若是不怎么作,则会出如今程序异常终止的时候没法看到部分日志信息,而常常这些丢失的日志信息要用于分析为何会出现异常终止的状况,于是通常推荐将该值设置为true,即默认值;econding用于定义日志文本的编码方式;qw定义写日志的writer,它能够是文件或是控制台等Java IO支持的流。
在写日志文本前,WriterAppender还会作其余检查,如该Appender不能已经closed、qw和layout必须有值等,然后才能够将layout格式化后的日志行写入设备中。若layout自己不处理异常问题,则有Appender处理异常问题。最后若是每行日志须要刷新,则调用刷新操做。
ConsoleAppender继承自WriterAppender,它只是简单的将System.out或System.err实例传递给WriterAppender以构建相应的writer,最后实现将日志写入到控制台中。
在Log4J中,Filter组成一条链,它定了以decide()方法,由子类实现,若返回DENY则日志不会被记录、NEUTRAL则继续检查下一个Filter实例、ACCEPT则Filter经过,继续执行后面的写日志操做。使用Filter能够为Appender加入一些出了threshold之外的其余逻辑,因为它自己是链状的,并且它的执行是横跨在Appender的doAppend方法中,于是这也是一个典型的AOP的概念。
Log4J自己提供了四个Filter:DenyAllFilter、LevelMatchFilter、LevelRangeFilter、StringMatchFilter。
DenyAllFilter只是简单的在decide()中返回DENY值,能够将其应用在Filter链尾,实现若是以前的Filter都没有经过,则该LoggingEvent没有经过,相似或的操做:
StringMatchFilter经过日志消息中的字符串来判断Filter后的状态:
LevelMatchFilter判断日志级别是否和设置的级别匹配以决定Filter后的状态:
LevelRangeFilter判断日志级别是否在设置的级别范围内以决定Filter后的状态:
这一系列终因而结束了。本文主要介绍了Log4J核心类的实现和他们之间的交互关系。涉及到各个模块自己的其余详细信息将会在接下来的小节中详细介绍,如LogRepository与配置信息、Appender类结构的详细信息、Layout类结构的详细信息以及部分LoggingEvent提供的高级功能。而像Level、Logger自己,因为内容很少,已经在这一小节中所有介绍完了。