log4j源码阅读(一)之Logger
概述
log4j是一款非常方便而且强大的开源日志项目,在经过简单的配置后,可以达到相当不错的效果。
头脑一热决定对log4j的源码进行一定的阅读,其初衷是希望通过源码的阅读,提高写代码的能力。
log4j其核心概念可分为:
logger 日志接收器,即程序员在自己的代码中使用如logger.error(...)的形式记录日志。
append 日志写出器,将logger接收到的日志信息写入到各种设备,如文件,控制台。
layout 日志格式化器,将输入的日志先进行格式化,再输出。
log4j将日志分为了几个级别,由低到高分别为:DEBUG < INFO < WARN < ERROR < FATAL。
若低级别的日志能输出,则比之级别高的日志也能输出。
UML
第一次画UML,多少有点紧张...
主要是希望通过UML图能够反映出logger的组织结构,及logger与其他组件的关系,因此很多因素被忽
略了。之后的说明都将对照着这个图来。
Logger
Logger继承自Category,而在Category有这样的说明
This class has been deprecated and replaced by the {@link Logger} <em>subclass</em></b></font>. It will be kept around to preserve backward compatibility until mid 2003.
name:作为自己的标识,在工厂方法中通过name来new出Logger实例。
level:每个Logger都有一个Level属性,在输出日志时,将通过方法getEffectiveLevel()获取到本身
有效的Level,以确定是否可以输出该条日志。稍后将详细说明级别判断流程。
parent:每个Logger都有个父结点,父子关系将在LoggerRepository中生成。正因为有了父子关系
所以在getEffectiveLevel方法中,实际上是向父结点方向遍历,找到第一个不为空的Level。也就是
说,若不明确指定当前结点的level,则使用父结点的level,在之后LoggerRepository的介绍时,会
知道,有一个公共的父结点RootLogger。
1 /** 2 Starting from this category, search the category hierarchy for a 3 non-null level and return it. Otherwise, return the level of the 4 root category. 5 6 <p>The Category class is designed so that this method executes as 7 quickly as possible. 8 */ 9 public 10 Level getEffectiveLevel() { 11 for(Category c = this; c != null; c=c.parent) { 12 if(c.level != null) { 13 return c.level; 14 } 15 } 16 return null; // If reached will cause an NullPointerException. 17 }
View Code
aai:每个Logger可关联多个Appender,接收的日志被依次输出到每一个Appender。Logger将对Appender
的管理代理到了AppenderAttachableImpl,例如addAppender操作实际上是交给aii处理的。
/**Add <code>newAppender</code> to the list of appenders of thisCategory instance.<p>If <code>newAppender</code> is already in the list ofappenders, then it won't be added again.*/synchronizedpublicvoid addAppender(Appender newAppender) {if(aai == null) {aai = new AppenderAttachableImpl();}aai.addAppender(newAppender);repository.fireAddAppenderEvent(this, newAppender);}
View Code
而在AppenderAttachableImpl中则仅将Appender添加到Vector
/**Attach an appender. If the appender is already in the list inwon't be added again.*/publicvoid addAppender(Appender newAppender) {// Null values for newAppender parameter are strictly forbidden.if(newAppender == null) {return;}if(appenderList == null) {appenderList = new Vector(1);}if(!appenderList.contains(newAppender)) {appenderList.addElement(newAppender);}}
View Code
debug:类似的还有info,error等,都是用户调用记录日志方法。在判断级别之后,将日志转递给Appender输出
将日志转化为LoggingEvent后,调用callAppenders向父结点方向遍历,每个一结点都调用AppenderAttachableImpl
的方法输出日志。实际上,在AppenderAttachableImpl中,将遍历当前结点所关联的所有Appender,依次输出。
publicvoid debug(Object message) {if(repository.isDisabled(Level.DEBUG_INT)) {return;}if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {forcedLog(FQCN, Level.DEBUG, message, null);}}
View Code
/**This method creates a new logging event and logs the eventwithout further checks. */protectedvoid forcedLog(String fqcn, Priority level, Object message, Throwable t) {callAppenders(new LoggingEvent(fqcn, this, level, message, t));}
View Code
publicvoid callAppenders(LoggingEvent event) {int writes = 0;for(Category c = this; c != null; c=c.parent) {// Protected against simultaneous call to addAppender, removeAppender,...synchronized(c) {if(c.aai != null) {writes += c.aai.appendLoopOnAppenders(event);}if(!c.additive) {break;}}}if(writes == 0) {repository.emitNoAppenderWarning(this);}}
View Code
LoggerRepository
LoggerRepository是对logger进行管理的仓库,提供工厂方法getLogger(name)来创建新的logger实例。并将所有创建
的实例,在逻辑上组织成树结构(logger的parent字段)。每个实例都有一个父结点存在。而LoggerRepository本身包
含一个RootLogger成员,是所有logger的共享祖先结点。也正是因为这个树结构的存在,在形如getEffectiveLevel等操
作中,都会向父结点方向遍历。所有子结点在没有特别配置过时,都使用父结点的属性(级别,输出器)。
LoggerRepository本身也带有level属性,在记录日志时,首先判断的是级别是否超过仓库的级别。该属性默认为ALL。
publicvoid debug(Object message) {if(repository.isDisabled(Level.DEBUG_INT)) {return;} ...
View Code
publicboolean isDisabled(int level) {return thresholdInt > level;}
View Code
Appender
logger将接收到的日志转化成LoggingEvent,通过callAppender方法,交由代理AppenderAttachableImpl完成输出。
AppenderAttachableImpl在遍历所有关联的Appender后,依次调用其doAppender方法进行输出。而每一个Appender
也是有优先级概念的,其他和logger的level相同。因此在Appender执行输出的时候,也是要优先判断级别是否符合当
前Appender的设置。另外,Appender提供一些过滤器,可对输出进行进一步的控制(应该是个简单的职责链模式)。
publicsynchronized void doAppend(LoggingEvent event) {if(closed) {LogLog.error("Attempted to append to closed appender named ["+name+"].");return;}if(!isAsSevereAsThreshold(event.getLevel())) {return;}Filter f = this.headFilter;FILTER_LOOP:while(f != null) {switch(f.decide(event)) {case Filter.DENY: return;case Filter.ACCEPT: break FILTER_LOOP;case Filter.NEUTRAL: f = f.getNext();}}this.append(event); }
View Code
当然输出之前,还需要通过Layout.format(...)格式化后再输出。
LogMagager
这是一个用户接口,是上面UML图中没有体现出来的内容。其主要是提供一些工厂,和一些默认的配置。如默认的仓库。
同时还提供一些工厂方法,如getLogger,内容只是将转交给其他模块实现。
static {// By default we use a DefaultRepositorySelector which always returns 'h'.Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));repositorySelector = new DefaultRepositorySelector(h); }
View Code
因此,用户可像通过LogManager使用logger,TestMain.class将最终转化成name。
public static Logger LOG = LogManager.getLogger(TestMain.class);
View Code
小结
阅读源码时,仅对大框架做了一些了解,如Appender,layout的实现细节,都简单略过了。log4j虽然强大,但终归只是记录
日志,源码不复杂,有兴趣的可以自己阅读一次。对于我来说,这也仅是一个源码阅读的开始吧。希望以后能学习到更多
优秀的设计。
转载于:https://www.cnblogs.com/fullstack/p/3911187.html
log4j源码阅读(一)之Logger相关推荐
- zookeeper 源码阅读(2)
接着zookeeper 源码阅读(1) Zookeeper服务器的启动,大致可以分为以下五个步骤 1. 配置文件解析. 2. 初始化数据管理器. 3. 初始化网络I/O管理器. 4. 数据恢复. 5. ...
- 应用监控CAT之cat-client源码阅读(一)
CAT 由大众点评开发的,基于 Java 的实时应用监控平台,包括实时应用监控,业务监控.对于及时发现线上问题非常有用.(不知道大家有没有在用) 应用自然是最初级的,用完之后,还想了解下其背后的原理, ...
- Yii源码阅读笔记 - 日志组件
2015-03-09 一 By youngsterxyf 使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category); Yii: ...
- zookeeper 源码阅读(3)
接着zookeeper 源码阅读(2) 这里详细看下工厂模式创建的zkServer,后面在介绍分析那三种选举算法 public class QuorumPeer extends ZooKeeperTh ...
- Flume-NG源码阅读之AvroSink
org.apache.flume.sink.AvroSink是用来通过网络来传输数据的,可以将event发送到RPC服务器(比如AvroSource),使用AvroSink和AvroSource可以组 ...
- DotText源码阅读(7) --Pingback/TrackBack
DotText源码阅读(7) --Pingback/TrackBack 博客这种服务的区别于论坛和所谓文集网站,很大程度上我认为是由于pingback/trackback的存在,使得博客这种自媒体有可 ...
- Rpc框架dubbo-client(v2.6.3) 源码阅读(二)
接上一篇 dubbo-server 之后,再来看一下 dubbo-client 是如何工作的. dubbo提供者服务示例, 其结构是这样的! dubbo://192.168.11.6:20880/co ...
- Soul网关源码阅读番外篇(一) HTTP参数请求错误
Soul网关源码阅读番外篇(一) HTTP参数请求错误 共同作者:石立 萧 * 简介 在Soul网关2.2.1版本源码阅读中,遇到了HTTP请求加上参数返回404的错误,此篇文章基于此进行探索 ...
- Soul 网关源码阅读(三)请求处理概览
Soul 源码阅读(三)请求处理概览 简介 基于上篇:Soul 源码阅读(二)代码初步运行的配置,这次debug下请求处理的大致路径,验证网关模型的路径 详细流程记录 查看运行日志,寻找切入点 ...
最新文章
- 机翻降重?掩饰抄袭?SCI期刊上的这些「奇言怪语」,不少来自中国作者
- Python全栈开发,Day1 - Python基础1
- VHDL中的左移函数
- 数据结构之字典序全排列
- 信息安全工程师笔记-案例分析(四)
- 评分组件(RatingBar)
- vue组件化开发实践
- QFile和C语言对文件操作的性能比较.--读取double型二进制数据文件
- 制作本地yum镜像站
- QQ sdk和Android sdk 28的兼容处理
- javascript高级编程笔记04(基本概念)
- 胖子哥的大数据之路(二)- 大数据结构化数据存储应用模式
- 批处理html转excel,批量Excel转TXT工具(Batch Excel to Text Converter)v2020.12.1118官方免费版...
- oppor829t如何刷机_科普OPPO R1 R829T的线刷教程及最简单的三星手机刷机教程
- hammerJs-v2.0.4详解
- Go异常处理——defer、panic、recover
- 如何使用Blender制作360度全景图和全景视频?
- 【TensorFlow】计算图graph的使用学习笔记(二)
- 2018 届互联网校招高薪清单曝光:25 万年薪只是白菜价?
- 地理坐标系与投影坐标系的区别以及投影变换与定义投影的区别