Log4j源碼解析--框架流程+核心解析


OK,如今咱們來研究Log4j的源碼:html

這篇博客有參照上善若水的博客,原文出處:http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html。感謝做者的無私分享。java


Log4J將寫日誌功能抽象成七個核心類或者接口:Logger、LoggerRepository、Level、LoggingEvent、Appender、Layout、ObjectRender。app

咱們一個一個來看:
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核心類的初始化,簡單的一次記錄日誌過程的序列圖以下:框架



關於上圖的解釋: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的初始化過程,詳細的一點的框架序列圖以下:

認真的看懂上面的流程圖,建議在框架最後一步打一個斷點,而後從頭至尾調試一遍代碼。我的以爲這也是最合理最有效的閱讀框架源碼的方法。OK,下幾篇博客我轉載上善若水的幾篇源碼帖,他已經整理的很詳細了。時間緣由我本身就不整理了。
相關文章
相關標籤/搜索