【log4j】【2】【log4j的原理】使用eclipse和jvm命令分析log4j的原理(1)

一、准备工作

安装好java,maven,eclipse.已有集成log4j的maven项目(使用maven可以便于加载和管理jar包,集成插件),log4j源码或者反编译软件(我使用的是反编译插件jad,集成到Eclipse)

二、查看类加载日志,方便寻找核心类

2.1 eclipse打印出日志

首先写好一个测试log4j的测试类

import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTest {
	
	@Test
	public  void maintest() {
		Logger log = Logger.getLogger(Log4jTest.class);
		log.debug("Hello world");
		log.info("info test threshold");
	}
}
使用junit4运行,运行之前加上jvm命令 -verbose,具体操作如图

右击>debug as>debug configuratio...


点击debug运行,可以在console上看到输出的日志

将输出的日志放到文本编辑器上(我是用的是Notepad++)

根据输出的日志查看分析大概了解到核心类是,Logger,LogManager(与logger同包,所以可以new Logger,Logger的构造器是protected),Level,Appender(设置输出位置),Layout(格式化输出)。

三、根据得到的核心类,画一个UML类图,时序图。

3.1 类图


3.2  时序图


四、核心代码逐个分析,代码逻辑

4.1 Logger分析

Logger继承Category,Category实现AppenderAttachable,Category和Logger的构造器都是protected,所以构造Logger的实例是用LogManage的方法实现的。

Logger与Appender的关联关系 是通过类AppenderAttachableImpl里面的成员变量Vector容器存放Appender实现的

public class AppenderAttachableImpl implements AppenderAttachable {
  
  /** Array of appenders. */
  protected Vector  appenderList;
......
public
  void addAppender(Appender newAppender) {
    // Null values for newAppender parameter are strictly forbidden.
    if(newAppender == null)
      return;
    
    if(appenderList == null) {
      appenderList = new Vector(1);
    }
    if(!appenderList.contains(newAppender))
      appenderList.addElement(newAppender);
  }


Logger的callAppenders方法

 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);
    }
  }

LoggingEvent携带了日志的所有信息,取出aai中的Appender,将event给到appender让其处理

如果当前类aai为空,就会去找其父节点的Category,直到找到RootLogger。如果没有处理event信息,就会记录没有appender的错误。

 

/**
     Log a localized and parameterized message. First, the user
     supplied <code>key</code> is searched in the resource
     bundle. Next, the resulting pattern is formatted using
     {@link java.text.MessageFormat#format(String,Object[])} method with the
     user supplied object array <code>params</code>.


     @since 0.8.4
  */
  public
  void l7dlog(Priority priority, String key,  Object[] params, Throwable t) {
    if(repository.isDisabled(priority.level)) {
      return;
    }
    if(priority.isGreaterOrEqual(this.getEffectiveLevel())) {
      String pattern = getResourceBundleString(key);
      String msg;
      if(pattern == null)
	msg = key;
      else
	msg = java.text.MessageFormat.format(pattern, params);
      forcedLog(FQCN, priority, msg, t);
    }
  }


l7dlog方法,记录国际化日志,getResourceBundleString(String key)是根据key获取国际化内容的一个方法。

Logger类里面包含了对于Logger的关联,RootLogger继承Logger,也是Logger的根节点。

public final class RootLogger extends Logger {

  public RootLogger(Level level) {
    super("root");
    setLevel(level);
  }
  
  public final Level getChainedLevel() {
    return level;
  }

  public final void setLevel(Level level) {
    if (level == null) {
      LogLog.error(
        "You have tried to set a null level to root.", new Throwable());
    } else {
      this.level = level;
    }
  }

}

RootLogger是final类不可被继承,并且Level一定要有值,避免再向上找父类,而且name和Level都有值。

LogManager在静态代码块将文件加载进来,并且将key=value,存放在Hierarchy中。经OptionConverter.selectAndConfigure将文件解析,并且将配置放到LogManager

中的静态私有属性repositorySelector中。

 static {
    // By default we use a DefaultRepositorySelector which always returns 'h'.
    Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
    repositorySelector = new DefaultRepositorySelector(h);

    /** Search for the properties file log4j.properties in the CLASSPATH.  */
    String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
						       null);

    // if there is no default init override, then get the resource
    // specified by the user or the default config file.
    if(override == null || "false".equalsIgnoreCase(override)) {

      String configurationOptionStr = OptionConverter.getSystemProperty(
							  DEFAULT_CONFIGURATION_KEY, 
							  null);

      String configuratorClassName = OptionConverter.getSystemProperty(
                                                   CONFIGURATOR_CLASS_KEY, 
						   null);

      URL url = null;

      // if the user has not specified the log4j.configuration
      // property, we search first for the file "log4j.xml" and then
      // "log4j.properties"
      if(configurationOptionStr == null) {	
	url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
	if(url == null) {
	  url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
	}
      } else {
	try {
	  url = new URL(configurationOptionStr);
	} catch (MalformedURLException ex) {
	  // so, resource is not a URL:
	  // attempt to get the resource from the class path
	  url = Loader.getResource(configurationOptionStr); 
	}	
      }
      
      // If we have a non-null url, then delegate the rest of the
      // configuration to the OptionConverter.selectAndConfigure
      // method.
      if(url != null) {
	    LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
        try {
            OptionConverter.selectAndConfigure(url, configuratorClassName,
					   LogManager.getLoggerRepository());
        } catch (NoClassDefFoundError e) {
            LogLog.warn("Error during default initialization", e);
        }
      } else {
	    LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
      }
    } else {
        LogLog.debug("Default initialization of overridden by " + 
            DEFAULT_INIT_OVERRIDE_KEY + "property."); 
    }  
  }

Level分析

Level继承Priority,两者的构造器都是protected,Level这几个属性分别代表,级别,名字,对应的系统Level值(我并没有看出来,根据名字直译过来的)。另外,由于对相同级别的Level实例来说,它必须是单例的,因而Log4J对序列化和反序列化做了一些处理。即它的三个成员都是transient,真正序列化和反序列化的代码自己写,并且加入readResolve()方法的支持,以保证反序列化出来的相同级别的Level实例是相同的实例。【摘自 http://www.blogjava.net/DLevin/archive/2012/06/28/381667.html】

  transient int level;
  transient String levelStr;
  transient int syslogEquivalent;
  final static public Level OFF = new Level(OFF_INT, "OFF", 0);
  final static public Level FATAL = new Level(FATAL_INT, "FATAL", 0);
  final static public Level ERROR = new Level(ERROR_INT, "ERROR", 3);
  final static public Level WARN  = new Level(WARN_INT, "WARN",  4);
  final static public Level INFO  = new Level(INFO_INT, "INFO",  6);
  final static public Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7);
  public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7);
  final static public Level ALL = new Level(ALL_INT, "ALL", 7);

/**
     * Resolved deserialized level to one of the stock instances.
     * May be overriden in classes derived from Level.
     * @return resolved object.
     * @throws ObjectStreamException if exception during resolution.
     */
    private Object readResolve() throws ObjectStreamException {
        //
        //  if the deserizalized object is exactly an instance of Level
        //
        if (getClass() == Level.class) {
            return toLevel(level);
        }
        //
        //   extension of Level can't substitute stock item
        //
        return this;
    }


级别从高到低,OFF是不记录,ALL,表示都记录


Appender,可以添加过滤器,错误处理类,输出类,并且对其处理

public interface Appender {

  void addFilter(Filter newFilter);

  public
  Filter getFilter();

  public
  void clearFilters();

  public
  void close();
  
  public
  String getName();

  public
  void setErrorHandler(ErrorHandler errorHandler);

  public
  ErrorHandler getErrorHandler();

  public
  void setLayout(Layout layout);

  public
  Layout getLayout();
  
  public
  void setName(String name);

  public
  boolean requiresLayout();

对其子类AppenderSkeleton进行分析;

  /** The layout variable does not need to be set if the appender
      implementation has its own layout. */
  protected Layout layout;

  /** Appenders are named. */
  protected String name;

  /**
     There is no level threshold filtering by default.  */
  protected Priority threshold;

  /** 
      It is assumed and enforced that errorHandler is never null.
  */
  protected ErrorHandler errorHandler = new OnlyOnceErrorHandler();

  /** The first filter in the filter chain. Set to <code>null</code>
      initially. */
  protected Filter headFilter;
  /** The last filter in the filter chain. */
  protected Filter tailFilter;
属性有,输出类,最低级别,错误处理类,过滤职责链的开始和结束。

Appender的作用,将一个LoggingEvent处理一下,然后丢给Layout

AppenderSkeleton
AppenderSkeleton实现了Appender接口,写日志是通过doAppend(LoggingEvent e)实现的
 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);    
  }
先判断log是不是关闭状态,如果不是,判断日志界别是否大于阈值,然后再得到过滤器,判断过滤器是否为空,如果不为空,通过Filter的decide(event)方法判断event时候符合要求,如果decide方法返回值为DENY直接返回,如果为ACCEPT,直接跳出循环,记录日志,如果为NEUTRAL ,则得到下一个职责,进行再一次判断循环。append方法是抽象方法,调用子类。
WriterAppender
直接看输出日志的方法,首先检查当前Appender是否符合条件,然后再进行输出。
public
  void append(LoggingEvent event) {

    // Reminder: the nesting of calls is:
    //
    //    doAppend()
    //      - check threshold
    //      - filter
    //      - append();
    //        - checkEntryConditions();
    //        - subAppend();

    if(!checkEntryConditions()) {
      return;
    }
    subAppend(event);
   }
判断Appender是否为关闭,输出流是否为空,输出类是否为空。
protected
  boolean checkEntryConditions() {
    if(this.closed) {
      LogLog.warn("Not allowed to write to a closed appender.");
      return false;
    }

    if(this.qw == null) {
      errorHandler.error("No output stream or file set for the appender named ["+
			name+"].");
      return false;
    }

    if(this.layout == null) {
      errorHandler.error("No layout set for the appender named ["+ name+"].");
      return false;
    }
    return true;
  }
将event格式化,输出到日志,判断是否记录异常信息,如有异常信息就记录,最后刷新流,将流输出到日志文件

protected
  void subAppend(LoggingEvent event) {
    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();
    }
  }


Layout
layout是一个抽象类,主要方法有,格式化event,是否忽略异常
  
public abstract class Layout implements OptionHandler {

  // Note that the line.separator property can be looked up even by
  // applets.
  public final static String LINE_SEP = System.getProperty("line.separator");
  public final static int LINE_SEP_LEN  = LINE_SEP.length();


  /**
     Implement this method to create your own layout format.
  */
  abstract
  public
  String format(LoggingEvent event);

  /**
     Returns the content type output by this layout. The base class
     returns "text/plain". 
  */
  public
  String getContentType() {
    return "text/plain";
  }

  /**
     Returns the header for the layout format. The base class returns
     <code>null</code>.  */
  public
  String getHeader() {
    return null;
  }

  /**
     Returns the footer for the layout format. The base class returns
     <code>null</code>.  */
  public
  String getFooter() {
    return null;
  }



  /**
     If the layout handles the throwable object contained within
     {@link LoggingEvent}, then the layout should return
     <code>false</code>. Otherwise, if the layout ignores throwable
     object, then the layout should return <code>true</code>.
     If ignoresThrowable is true, the appender is responsible for
     rendering the throwable.

     <p>The {@link SimpleLayout}, {@link TTCCLayout}, {@link
     PatternLayout} all return <code>true</code>. The {@link
     org.apache.log4j.xml.XMLLayout} returns <code>false</code>.

     @since 0.8.4 */
  abstract
  public
  boolean ignoresThrowable();

}

子类PatternLayout
 /**
     Produces a formatted string as specified by the conversion pattern.
  */
  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();
  }

判断stringBuffer 容量是不是大于最大容量,如果是,新建一个StringBuffer,反之将清空StringBuffer。PatternConverter 是一个职责链,将日志信息格户到sbuf,返回格式化的字符串。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值