OK,如今咱們來研究Log4j的源碼:html
這篇博客有參照上善若水的博客,原文出處:http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html。感謝做者的無私分享。java
Log4J將寫日誌功能抽象成七個核心類或者接口:Logger、LoggerRepository、Level、LoggingEvent、Appender、Layout、ObjectRender。app
咱們一個一個來看: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); }
暫時不涉及Logger核心類的初始化,簡單的一次記錄日誌過程的序列圖以下:框架
關於上圖的解釋:eclipse
獲取Logger實例->判斷Logger實例對應的日誌記錄級別是否要比請求的級別低->如果調用forceLog記錄日誌->建立LoggingEvent實例->將LoggingEvent實例傳遞給Appender->Appender調用Layout實例格式化日誌消息->Appender將格式化後的日誌信息寫入該Appender對應的日誌輸出中。ide
OK,如今咱們在輸出日誌到某個指定位置處打個斷點,看下eclipse中方法的調用棧。
oop
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來封裝。this
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的初始化過程,詳細的一點的框架序列圖以下: