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,下几篇博客我转载上善若水的几篇源码帖,他已经整理的很详细了。时间原因我自己就不整理了。
风流子弟曾少年,多少老死江湖前。。。