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


  • 相关阅读:
    软件工程第1次阅读作业
    centOS 7设置静态IP,使用Xshell远程连接
    jmeter响应结果乱码问题
    Jmeter环境搭建详细介绍
    redis常用操作命令集合
    cocoapod 快速更新,加载
    获取验证码倒计时
    支持向量机SVM(Support Vector Machine)
    Ensemble Learning: Bootstrap aggregating (Bagging) & Boosting & Stacked generalization (Stacking)
    Eureka安全下线服务
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232843.html
Copyright © 2011-2022 走看看