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