本文转自:
http://www.blogjava.net/BucketLi/archive/2012/05/15/335618.html
最近需要用到log4j动态定制Logger的场景,然后加上以前对于这个日志工具拿来就用而不知其原理的原因,所以决定花点时间看下它的源码,如果你还对log4j如何使用感到困惑,那么请首先简要浏览下它的官网http://logging.apache.org/log4j/
Log4j总体来说是一个可定制,支持同时多种形式输出日志,并且高度结构化的日志库。可定制,也就是既可以通过log4j.properties或者log4j.xml定义日志输出的级别(Level),形式(Appender)以及文本格式(Layout),也可以通过Logger类或者LogManager类取得Logger实例,并且设置日志输出级别(Level),形式(Appender)以及文本格式(Layout),可以说是相当的简便与灵活。下面一段代码简单地说明了后者的实现。
Logger MY_LOG = Logger.getLogger("MY_LOG");
//文件形式的输出方式实例化
DailyRollingFileAppender appender = new DailyRollingFileAppender();
appender.setName(name);
appender.setAppend(true);
appender.setEncoding("GBK");
//文本的输出格式采用PatternLayout
appender.setLayout(new PatternLayout(pattern));
appender.setFile(new File(getLogPath(), fileName).getAbsolutePath());
appender.activateOptions();
//将appender加入到MY_LOG的appender集合
MY_LOG.addAppender(appender);
//设定日志输出级别为INFO
MY_LOG.setLevel(Level.INFO);
Log4j初始化的代码是在LogManager的静态块里面,这个静态块无论如何都会实例化一个日志级别为DEBUG的RootLogger,并且初始化一个以这个RootLogger为根节点的级联结构,然后检查有没有用户指定重写这个日志系统的初始化工作,如果没有,那么先去找log4j.xml,如果没有找到,那么再去找log4j.properties(也就是log4j.xml的优先级高于log4j.properties), 只有找到这两个配置文件的其中一个,再初始化文件里面内容,主要是一些配置文件中指定的Logger初始化,以及各个Logger的Appender列表设定,各个Appender的具体实例,Layout等。其实,没找到任何log4j配置文件也没什么关系,因为已经有RootLogger,日志系统骨架已经完成了,所以也可以通过如前面的代码添加Logger。
//初始化以RootLogger为根的级联结构,rootLogger默认DEBUG级别
Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
//有没指定log4j.configuration(内部使用,不知出于什么目的,未探究)
if(configurationOptionStr == null) {
//加载classpath下log4j.xml
url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
if(url == null) {
//如果log4j.xml没有,加载log4j.properties
url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
}
} else {
try {
url = new URL(configurationOptionStr);
} catch (MalformedURLException ex) {
url = Loader.getResource(configurationOptionStr);
}
}
if(url != null) {
LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
//开始真正解析配置文件
OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository());
} else {
LogLog.debug("Could not find resource:["+configurationOptionStr+"].");
}
简要介绍完Log4J的初始化后,我们有必要来看下从Logger myLogger =LogManager. getLogger(“MY_ LOG”),到 myLogger.debug(“some message”)结束之后,Log4j到底为我们做了些什么。
上面这幅序列图主要描述的过程就是从LoggerManager取得一个Logger的过程,其中最重要的操作在Hierarchy类中,这个类说白了就是存储Logger的仓库,其内部使用一个HashMap ht来存储Logger和ProvisionNode。
这里需要解释下ProvisionNode,这个类是一个Vector的实现,之前我们谈到过初始化的一开始,以RootLogger为根节点的级联结构(就是Hierarchy实例),那么这个ProvisionNode想当于这个级联结构中的树节点,比如我在定义了一个名字为a.b.c的Logger,那么总共会生成”a”,”a.b”两个ProvisionNode,以及一个名字为”a.b.c”的Logger。Hierarchy并没有一个链表来维护他们之间的顺序,ProvisionNode会通过其本身就是向量的特性将属于它的Logger进行有序的排列,而Logger本身则通过parent属性记住他们的日志属性可以从哪里继承。
这样做的好处有两点,第一点就是只要名字相同,从LogManager中取出来的Logger就是同一个实例,第二点好处就是级联结构,可以定义出差异化的Logger,特别是其以a.b.c.d类似包结构的拆分日志节点,使得包级别的日志差异化输出更加的容易,并且这种特性提供了日志节点属性继承的功能。
举个例子,我们一般取得一个Logger实例是这样的, Logger log=LogManager.getLogger(Class class), Log4j处理方式为getLogger(class.getName()),这也就是说,这个Logger的名字是带包名的完全限定名字,所以如果我们通过名为a.b.c.aclass,a.b.c.d.bclass这么两个类取得Logger实例,然后定义名为a.b.c和a.b.c.d 2个Logger,那么总共会生成如下的节点
ProvisionNode: a,a.b
Logger: a.b.c(加入a,a.b 2个ProvisionNode中并且parent为RootLogger)
a.b.c.d(加入a,a.b 2个ProvisionNode中,并且parent为a.b.c)
a.b.c.aclass (加入到a,a.b 2个ProvisionNode中,并且parent为a.b.c),
a.b.c.d.bclass(加入到a,a.b 2个ProvisionNode中,并且parent为a.b.c.d)
其中ProvisionNode可能升级为Logger,当同名的Logger加入时,该ProvisionNode中所有以该ProvisionNode为父节点的子节点修改parent指向新的Logger.
final private void updateChildren(ProvisionNode pn, Logger logger) {
final int last = pn.size();
for(int i = 0; i < last; i++) {
Logger l = (Logger) pn.elementAt(i);
//有可能子节点的父节点指向更低一级的节点,比如孙节点。这个应该不难理解。
if(!l.parent.name.startsWith(logger.name)) {
logger.parent = l.parent;
l.parent = logger;
}
}
}
这样Logger a.b.c除了自身的日志输出设置之外,还享受rootLogger的输出(输出级别,Appenders),Logger a.b.c.d同时享受rootLogger和a.b.c的设置,a.b.c.d.bclass享受rootLogger,a.b.c,a.b.c.d的日志输出设定,也就是可以分别定义一批Logger的输出级别和输出形式以及其他属性,当然也有控制开关控制这种继承。下图说明了一些问题。
取得Logger之后,随后就是需要按指定形式输出日志内容,首先需要在LoggerRepository中判定当前Level是否可以做日志输出,包括与全局的thresholdInt进行判定(相当于总开关,这个级别通不过直接返回,不做任何事情),通过后,再与其自身Logger日志级别比较,如果没有设定,查其父节点的日志级别,仍然没有设定,那么查父节点的父节点,直到查到有日志级别设定或者最终到RootLogger(其默认为Debug级别),比较通过后,就可以调用callDependers进行日志输出了。
日志级别为 OFF>FATAL>ERROR>WARN>INFO>DEBUG>ALL,只有输出级别大于等于Logger自身级别才能进行输出,比如 logger.debug,那么只有该Logger的级别(也可能是其先辈节点的日志级别)在DEBUG或者ALL才能允许被输出。全局的thresholdInt如果不设定是保持在ALL级别。
一般Logger会通过AppenderAttachableImpl的实例来维护多个Appender,并且可以共享父节点的Appender List(包括RootLogger里面定义的appenders),所以我们一般在log4j.xml或者log4j.properties里面定义一个某个包下的Logger,然后挂有几个Appender,而程序中通过完全限定的类名(这个类属于前面指定的包)取得Logger,那么当这些Logger输出日志的时候,其本身并没有任何Appender,但是却通过先辈节点定义的Appenders得以输出。这里需要注意的是,如果在直系节点间定义相同的appender,似乎会多次重复输出,因为其会遍历自身以及所有先辈节点的Appender list并且逐一进行doAppend调用,而不会去排重,并且addAppender的时候也没有遍历先辈节点排重。
Appender持有一个Filter链表,doAppend的时候首先走一遍过滤器,结果有3种,Filter.DENY(直接拒绝返回),Filter.ACCEPT(接收输出请求,并且不再走之后的Filter),Filter.NEUTRAL(继续执行下一个Filter),顺利通过Filter链之后,进入真正的输出日志过程,这边以WriteAppender为例,首先将message通过持有的Layout进行格式化(format),然后调用输出流输出日志到目标文件(FileAppender)或者屏幕(ConsoleAppender)。
至此,整个日志输出过程结束。
下面两幅图分别是Log4j的整体类图,不是非常完整,但是大概能够了解到整个结构。
总结下,log4J出来已经很多年了,以前只是使用下,并没有去探究里面机制,但其某些机制还是相当不错的。文中可能出现一些错误,请各位能够指出。
分享到:
相关推荐
【原创整理,严禁转载,转载必究】 参考 文献 [1]李维安;王鹏程;徐业坤.慈善捐赠、政治关联与债务融资——民营企业与Z F的资源交换行为[J].南开管理评论,2015,18(01):4-14. [2] 许年行;李哲.高管贫困经历与企业慈善...
本人学习struts的小例子,包含多模块处理、中文化处理、数据验证、Log4J使用、重复提交解决等问题,还包含部分ejb,转载了myeclipse配置weblogic和jboss的文章。 包含源代码。
这些jar包可能包括Struts2、Spring、Hibernate的库,以及其他如log4j、servlet API等运行所需的类库。开发者可以通过分析这些库文件来理解项目的构建环境和依赖结构。 综合来看,这个项目是一个关于如何利用JSR 168...
8. **异常处理与日志记录**:SpringBoot支持统一的异常处理机制,并集成了Logback或Log4j等日志框架,便于调试和监控系统状态。 9. **单元测试与集成测试**:为了保证代码质量,项目中可能会包含JUnit或Spock等单元...
第19章 使用log4j进行日志操作 564 19.1 log4j介绍 564 19.1.1 logger组件 564 19.1.2 appender组件 566 19.1.3 layout组件 567 19.2 使用log4j 568 19.3 log4j使用实例 572 19.4 ndc和mdc 585 19.5 小结 ...
7. **日志记录**:为了追踪爬虫运行状态和调试问题,项目可能使用了日志记录工具,如Log4j,记录爬虫运行过程中的重要信息。 8. **URL管理**:为了避免重复爬取同一个URL,通常会使用URL队列或集合来存储已访问过的...
第19章 使用log4j进行日志操作 564 19.1 log4j介绍 564 19.1.1 logger组件 564 19.1.2 appender组件 566 19.1.3 layout组件 567 19.2 使用log4j 568 19.3 log4j使用实例 572 19.4 ndc和mdc 585 19.5 小结 ...
第19章 使用log4j进行日志操作 564 19.1 log4j介绍 564 19.1.1 logger组件 564 19.1.2 appender组件 566 19.1.3 layout组件 567 19.2 使用log4j 568 19.3 log4j使用实例 572 19.4 ndc和mdc 585 19.5 小结 ...
第19章 使用log4j进行日志操作 564 19.1 log4j介绍 564 19.1.1 logger组件 564 19.1.2 appender组件 566 19.1.3 layout组件 567 19.2 使用log4j 568 19.3 log4j使用实例 572 19.4 ndc和mdc 585 19.5 小结 ...