log4j 源码解析_Log4j源码解析--框架流程+核心解析
OK,現(xiàn)在我們來研究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抽象成了接口,然后主要的實現(xiàn)是WriterAppender,常用的ConsoleAppender,FileAppender都繼承了該類。
// 實際編碼中經(jīng)常會遇到DailyRollingFileAppender,RollingFileAppender都繼承于FileAppender。
}3,Layout是對日志行格式的抽象;
public abstract class Layout implements OptionHandler
{
// Layout抽象成一個模板,比較常用的PatternLayout,HTMLLayout都是該類子類
}4,Level對日志級別的抽象;
public class Level extends Priority implements Serializable
{
// 該類封裝一系列日志等級的名字和數(shù)字,然后內(nèi)容封裝多個等級的相關(guān)枚舉
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就是該接口實現(xiàn),里面封裝了框架一堆默認(rèn)配置,還有Logger工廠。
// 可以理解該類就是事件源,該類內(nèi)部封裝了以系列的事件
}7,ObjectRender是對日志實例的解析接口,它們主要提供了一種擴展支持。
public interface ObjectRenderer
{
/**
* @創(chuàng)建時間: 2016年2月25日
* @相關(guān)參數(shù): @param o
* @相關(guān)參數(shù): @return
* @功能描述: 解析日志對象,默認(rèn)實現(xiàn)返回toString()
*/
public String doRender(Object o);
}
OK,現(xiàn)在介紹完了Log4j核心類了,現(xiàn)在我們來研究下Log4j的實際運行情況。
暫時不涉及Logger核心類的初始化,簡單的一次記錄日志過程的序列圖如下:
關(guān)于上圖的解釋:
獲取Logger實例->判斷Logger實例對應(yīng)的日志記錄級別是否要比請求的級別低->若是調(diào)用forceLog記錄日志->創(chuàng)建LoggingEvent實例->將LoggingEvent實例傳遞給Appender->Appender調(diào)用Layout實例格式化日志消息->Appender將格式化后的日志信息寫入該Appender對應(yīng)的日志輸出中。
OK,現(xiàn)在我們在輸出日志到某個指定位置處打個斷點,看下eclipse中方法的調(diào)用棧。protected void subAppend(LoggingEvent event)
{
// layout格式化日志事件,然后appender輸出日志
this.qw.write(this.layout.format(event));
}
具體調(diào)用如下:
我們從我們自己寫的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,創(chuàng)建日志事件 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,對應(yīng)的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初始化日志框架的時候,第一行代碼就是獲取靜態(tài)常量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的初始化過程,詳細(xì)的一點的框架序列圖如下:
認(rèn)真的看懂上面的流程圖,建議在框架最后一步打一個斷點,然后從頭到尾調(diào)試一遍代碼。個人覺得這也是最合理最有效的閱讀框架源碼的方法。OK,下幾篇博客我轉(zhuǎn)載上善若水的幾篇源碼帖,他已經(jīng)整理的很詳細(xì)了。時間原因我自己就不整理了。
總結(jié)
以上是生活随笔為你收集整理的log4j 源码解析_Log4j源码解析--框架流程+核心解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js轮播图片小圆点变化_原生js实现轮播
- 下一篇: php yield 个人小解_php 技