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中的事件監聽等操作 由於太瑣碎就沒有在文中描述

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