OK,现在我们来研究Log4j的源码:

这篇博客有参照上善若水的博客,原文出处:http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html。感谢作者的无私分享。

Log4J将写日志功能抽象成七个核心类或者接口:Logger、LoggerRepository、Level、LoggingEvent、Appender、Layout、ObjectRender。

我们一个一个来看:

1,Logger用于对日志记录行为的抽象,提供记录不同级别日志的接口;

public class Logger extends Category
{
// Logger继承Category,Category也是一种日志类
}

2,Appender是对记录日志形式的抽象;

public interface Appender
{
// Appender抽象成了接口,然后主要的实现是WriterAppender,常用的ConsoleAppender,FileAppender都继承了该类。
// 实际编码中经常会遇到DailyRollingFileAppender,RollingFileAppender都继承于FileAppender。
}

3,Layout是对日志行格式的抽象;

public abstract class Layout implements OptionHandler
{
// Layout抽象成一个模板,比较常用的PatternLayout,HTMLLayout都是该类子类
}

4,Level对日志级别的抽象;

public class Level extends Priority implements Serializable
{
// 该类封装一系列日志等级的名字和数字,然后内容封装多个等级的相关枚举
public final static int INFO_INT = 20000; private static final String INFO_NAME = "INFO"; final static public Level INFO = new Level(INFO_INT, INFO_NAME, 6);
}

5,LoggingEvent是对一次日志记录过程中所能取到信息的抽象;

public class LoggingEvent implements java.io.Serializable
{
// 该类定义了一堆堆属性,封装了所有的日志信息。
}

6,LoggerRepository是Logger实例的容器

public interface LoggerRepository
{
// 常见的Hierarchy就是该接口实现,里面封装了框架一堆默认配置,还有Logger工厂。
// 可以理解该类就是事件源,该类内部封装了以系列的事件
}

7,ObjectRender是对日志实例的解析接口,它们主要提供了一种扩展支持。

public interface ObjectRenderer
{ /**
* @创建时间: 2016年2月25日
* @相关参数: @param o
* @相关参数: @return
* @功能描述: 解析日志对象,默认实现返回toString()
*/
public String doRender(Object o);
}
  • OK,现在介绍完了Log4j核心类了,现在我们来研究下Log4j的实际运行情况。

暂时不涉及Logger核心类的初始化,简单的一次记录日志过程的序列图如下:



关于上图的解释:

获取Logger实例->判断Logger实例对应的日志记录级别是否要比请求的级别低->若是调用forceLog记录日志->创建LoggingEvent实例->将LoggingEvent实例传递给Appender->Appender调用Layout实例格式化日志消息->Appender将格式化后的日志信息写入该Appender对应的日志输出中。

OK,现在我们在输出日志到某个指定位置处打个断点,看下eclipse中方法的调用栈。

protected void subAppend(LoggingEvent event)
{
// layout格式化日志事件,然后appender输出日志
this.qw.write(this.layout.format(event));
}

具体调用如下:



我们从我们自己写的bug()方法来开始一步一步走:

1,我们自己写的测试类中输出日志:

public void logTest()
{
log.debug("debug()。。。");
}

2,Category类中debug方法,输出之前先判断了下日志级别:

public void debug(Object message)
{
if (repository.isDisabled(Level.DEBUG_INT))
{
return;
}
if (Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel()))
{
forcedLog(FQCN, Level.DEBUG, message, null);
}
}

isDisabled()方法如下:

public boolean isDisabled(int level)
{
return thresholdInt > level;
}

3,创建日志事件 LoggingEvent,传递给AppenderAttachableImpl

protected void forcedLog(String fqcn, Priority level, Object message, Throwable t)
{
LoggingEvent loggingEvent = new LoggingEvent(fqcn, this, level, message, t);
callAppenders(loggingEvent);
}
public void 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);
}
}

4,AppenderAttachableImpl处理LoggingEvent事件。这里可能有多个appender,用appenderList来封装。

public int appendLoopOnAppenders(LoggingEvent event)
{
int size = 0;
Appender appender; if (appenderList != null)
{
size = appenderList.size();
for (int i = 0; i < size; i++)
{
appender = (Appender) appenderList.elementAt(i);
appender.doAppend(event);
}
}
return size;
}

5,对应的appender来处理日志。

public synchronized 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);
}

public void append(LoggingEvent event)
{
if (!checkEntryConditions())
{
return;
}
subAppend(event);
}
protected void subAppend(LoggingEvent event)
{
// layout格式化日志事件,然后appender输出日志
this.qw.write(this.layout.format(event)); if (layout.ignoresThrowable())
{
String[] s = event.getThrowableStrRep();
if (s != null)
{
int len = s.length;
for (int i = 0; i < len; i++)
{
this.qw.write(s[i]);
this.qw.write(Layout.LINE_SEP);
}
}
} if (shouldFlush(event))
{
this.qw.flush();
}
}

6,使用特定的日志格式化器layout格式化日志:

public String format(LoggingEvent event)
{
// Reset working stringbuffer
if (sbuf.capacity() > MAX_CAPACITY)
{
sbuf = new StringBuffer(BUF_SIZE);
}
else
{
sbuf.setLength(0);
} PatternConverter c = head; while (c != null)
{
c.format(sbuf, event);
c = c.next;
}
return sbuf.toString();
}

7,appender输出日志到特定的输出位置:

public void write(String string)
{
try
{
out.write(string);
count += string.length();
}
catch (IOException e)
{
errorHandler.error("Write failure.", e, ErrorCode.WRITE_FAILURE);
}
}



OK,上面的过程不涉及Logger的初始化过程,我们是在使用Log4j初始化日志框架的时候,第一行代码就是获取静态常量log,代码如下:

public static Logger log = Logger.getLogger(Log4jTest.class);

也就是说项目在启动时就加载log到我们的项目中了,具体的加载过程源码如下,log4j这里使用了一个工厂,然后用Hashtable来装各个Logger,同时保持单例。

public static Logger getLogger(Class clazz)
{
return LogManager.getLogger(clazz.getName());
}
public static Logger getLogger(final String name)
{
// Delegate the actual manufacturing of the logger to the logger repository.
return getLoggerRepository().getLogger(name);
}
public Logger getLogger(String name)
{
return getLogger(name, defaultFactory);
}
public Logger getLogger(String name, LoggerFactory factory)
{
CategoryKey key = new CategoryKey(name);
Logger logger;
synchronized (ht)
{
Object o = ht.get(key);
if (o == null)
{
logger = factory.makeNewLoggerInstance(name);
logger.setHierarchy(this);
ht.put(key, logger);
updateParents(logger);
return logger;
}
else if (o instanceof Logger)
{
return (Logger) o;
}
else if (o instanceof ProvisionNode)
{
// System.out.println("("+name+") ht.get(this) returned ProvisionNode");
logger = factory.makeNewLoggerInstance(name);
logger.setHierarchy(this);
ht.put(key, logger);
updateChildren((ProvisionNode) o, logger);
updateParents(logger);
return logger;
}
else
{
// It should be impossible to arrive here
return null; // but let's keep the compiler happy.
}
}
}

涉及Logger的初始化过程,详细的一点的框架序列图如下:









认真的看懂上面的流程图,建议在框架最后一步打一个断点,然后从头到尾调试一遍代码。个人觉得这也是最合理最有效的阅读框架源码的方法。OK,下几篇博客我转载上善若水的几篇源码帖,他已经整理的很详细了。时间原因我自己就不整理了。



Log4j源码解析--框架流程+核心解析的更多相关文章

  1. log4j源码解析-文件解析

    承接前文log4j源码解析,前文主要介绍了log4j的文件加载方式以及Logger对象创建.本文将在此基础上具体看下log4j是如何解析文件并输出我们所常见的日志格式 附例 文件的加载方式,我们就选举 ...

  2. Spring源码分析之AOP从解析到调用

    正文: 在上一篇,我们对IOC核心部分流程已经分析完毕,相信小伙伴们有所收获,从这一篇开始,我们将会踏上新的旅程,即Spring的另一核心:AOP! 首先,为了让大家能更有效的理解AOP,先带大家过一 ...

  3. 曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  4. Spring源码-IOC部分-Xml Bean解析注册过程【3】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  5. Spring源码情操陶冶#task:scheduled-tasks解析器

    承接前文Spring源码情操陶冶#task:executor解析器,在前文基础上解析我们常用的spring中的定时任务的节点配置.备注:此文建立在spring的4.2.3.RELEASE版本 附例 S ...

  6. # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  7. springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)

    在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...

  8. 曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  9. 曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

随机推荐

  1. 15.5 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表

    点击返回:自学Zabbix之路 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表  1. Actions表 actions表记录了当触发器触发时,需要采用的动作. 2.Aler ...

  2. [原创]Nginx反向代理及负载均衡

    1.基本命令 # 启动nginx start nginx.exe # windowsnginx -c /usr/local/nginx/conf/nginx.conf # Linux# 优雅的停止ng ...

  3. squid日志分析

    sarg对squid的日志流量分析报表(按小时,天,周生成) 1.SARG介绍 SARG的全称是:Squid Analysis Report GeneratorSARG非常好用的Squid日志分析工具 ...

  4. TLD算法原理2--学习理解之(三)

    TLD(Tracking-Learning-Detection)是一种新的单目标长时间(long term tracking)跟踪算法.该算法与传统跟踪算法的显著区别在于将传统的跟踪算法和传统的检测算 ...

  5. RHM-M10汽车吊力矩限制器/载荷指示器

    一 产品特点 1.     采用7.0寸工业65K色TFT LCD真彩屏,亮度250nit,分辨率800×480: 2.     传感器采用进口机芯,过载能力强: 3.     采用油压取力和大臂弯曲 ...

  6. nautilus出现一闪而过现象

    linux相关问题: 1.这几天在使用乌班图时,出现文件夹打开一闪而过现象,于是我试着使用命令行来启动: sudo nautilus 出现下面这一堆错误(error_info): (nautilus: ...

  7. hadoop2.6.0集群搭建

    p.MsoNormal { margin: 0pt; margin-bottom: .0001pt; text-align: justify; font-family: Calibri; font-s ...

  8. ASP.NET没有魔法——ASP.NET MVC路由

    之前的文章中介绍了My Blog文章维护功能的开发,开发过程中使用Area的方法建立了用于维护文章的Controller.View和Model.但是无论代码怎么变对于浏览器来说都是通过一个url地址去 ...

  9. Spring的IOC分析(一)

    我们学习Spring之前需要对23种java的设计模式的9种有一定的理解,设计模式为了解耦,Spring也是在解耦的方向上设计的,所以设计模式要理解一下,它当中用到了很多. 单例模式(写法很多钟,7种 ...

  10. API接口签名验证2

    http://www.jianshu.com/p/d47da77b6419 系统从外部获取数据时,通常采用API接口调用的方式来实现.请求方和�接口提供方之间的通信过程,有这几个问题需要考虑: 1.请 ...