Log4j源码分析

介绍

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用

依赖包:

<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>
import org.apache.log4j.Logger;

public class Application {

	public static void main(String[] args) {
		Logger logger = Logger.getLogger(Application.class);
		logger.trace("This is a trace message");
		logger.debug("This is a debug message");
		logger.info("This a info message");
		logger.error("This a error message");
	}
}

配置文件

log4j.properties

log4j.rootLogger=INFO,stdout 
 
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] %d{yyyy-MM-dd HH:mm:ss} %l: %m%n

源码跟踪

初始化

跟踪getLogger方法 发现实际调用是LogManager

public static Logger getLogger(final String name) {
     // Delegate the actual manufacturing of the logger to the logger repository.
    return getLoggerRepository().getLogger(name);
  }

查看LogManager发现 它有一个静态块在类加载时用来构建LoggerRepository

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

说明:创建RootLogger 日志级别为DEBUG, 把RootLogger作为参数用来初始化一个LoggerRepository对象h, 再创建一个DefaultRepositorySelector.

后边操作是查找配置文件并加载配置文件

OptionConverter.selectAndConfigure(url, configuratorClassName,
					   LogManager.getLoggerRepository());

点击查看执行代码如下

 public
  void doConfigure(Properties properties, LoggerRepository hierarchy) {
	repository = hierarchy;
    String value = properties.getProperty(LogLog.DEBUG_KEY);
    if(value == null) {
      value = properties.getProperty("log4j.configDebug");
      if(value != null)
	LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
    }

    if(value != null) {
      LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
    }

      //
      //   if log4j.reset=true then
      //        reset hierarchy
    String reset = properties.getProperty(RESET_KEY);
    if (reset != null && OptionConverter.toBoolean(reset, false)) {
          hierarchy.resetConfiguration();
    }

    String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
						       properties);
    if(thresholdStr != null) {
      hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
						     (Level) Level.ALL));
      LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
    }
    
    configureRootCategory(properties, hierarchy);
    configureLoggerFactory(properties);
    parseCatsAndRenderers(properties, hierarchy);

    LogLog.debug("Finished configuring.");
    // We don't want to hold references to appenders preventing their
    // garbage collection.
    registry.clear();
  }

说明:
1、 配置hierarchy中的rootLogger(读取配置文件内容对默认的rootLogger,进行修改:日志级别、添加Appender).
2、配置LoggerFactory(读取配置文件是否配置有新的Factory类,如果有,则通过反射机制来创建该对象,并为该对象设置相关属性的值,数据来源:配置文件).
3、解析配置文件组建Logger对象并存放到LoggerRepostory的HashTable中.
4、清空构建logger对象的中间过程中存储Appender对象的HashTable集合

至此LoggerRepostory初始化完成,且代表log4j整个配置完成.

梳理了一个简单的关系图 方便了解
关系结构:(

RepositorySelector has-a LoggerRepository

LoggerRepository has-a Logger root 根Logger

LoggerRepository has-a Hashtable ht 存放其他Logger的集合

LoggerRepository has-a LoggerFactory defaultFactory 默认的loggerFactory

Logger has-a LoggerRepository

Logger has-a AppenderAttachableImpl aai AppenderAttachableImpl 是封装了appender集合的对象

核心对象

Logger 输出日志对象 持有一个Appender的集合,当执行输出方法时,把消息封装为LoggingEvent 日志事件,并循环调用appender的subAppend方法。

LoggerFactory 用来构建Logger对象,

LoggerRepository 存放日志对象,提供相关获取日志操作的方法

LoggerManager 负责完成日志配置.

日志输出

logger.debug("This is a debug message");

方法定义在:Category类

public void debug(Object message) {
    if(repository.isDisabled(Level.DEBUG_INT))
      return;
    if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
      forcedLog(FQCN, Level.DEBUG, message, null);
    }
  }

protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
    callAppenders(new LoggingEvent(fqcn, this, level, message, t));
}

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

说明: 创建日志事件,并通知给Appender。 c.aai是appender的集合。

AppenderAttachableImpl类

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

说明:遍历appenderList并调用appender的doAppend方法.

AppenderSkeleton类

 abstract protected void append(LoggingEvent event);

说明:此处只是声明一个抽象方法,具体有子类实现.
WriterAppender类

 public WriterAppender(Layout layout, OutputStream os) {
    this(layout, new OutputStreamWriter(os));
  }

public WriterAppender(Layout layout, Writer writer) {
    this.layout = layout;
    this.setWriter(writer);
}

public synchronized void setWriter(Writer writer) {
    reset();
    this.qw = new QuietWriter(writer, errorHandler);
    //this.tp = new TracerPrintWriter(qw);
    writeHeader();
}
    
public void append(LoggingEvent event) {
    if(!checkEntryConditions()) {
      return;
    }
    subAppend(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();
    }
  }

说明:该类实现了append(LoggingEvent event) 方法 具体调用的是subAppend(LoggingEvent event)方法。通过调用qw.write方法输出日志。

查看子类中具体的设置qw的方法,此处以ConsoleAppender为例:
ConsoleAppender类

public ConsoleAppender(Layout layout, String target) {
    setLayout(layout);
    setTarget(target);
    activateOptions();
}

public void activateOptions() {
      if (follow) {
          if (target.equals(SYSTEM_ERR)) {
             setWriter(createWriter(new SystemErrStream()));
          } else {
             setWriter(createWriter(new SystemOutStream()));
          }
      } else {
          if (target.equals(SYSTEM_ERR)) {
             setWriter(createWriter(System.err));
          } else {
             setWriter(createWriter(System.out));
          }
      }

      super.activateOptions();
}

public synchronized void setWriter(Writer writer) {
    reset();
    this.qw = new QuietWriter(writer, errorHandler);
    //this.tp = new TracerPrintWriter(qw);
    writeHeader();
}

说明:最终logger.debug(“This is a debug message”)方法 实际是通过调用System.out进行Message的输出.

总结

相对于sel4j、logback, log4j相对结构简单,入门容易。 本文中有些细节描述不到位,比如:解析配置文件转换为Logger对象、Logger间的继承关系、LoggerRepostory中的事件监听等操作 由于太琐碎就没有在文中描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章