log4j 源码解析_Log4j源码解析--框架流程+核心解析

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,下几篇博客我转载上善若水的几篇源码帖,他已经整理的很详细了。时间原因我自己就不整理了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值