一問:log4j.properties 字段如何加載?
二問:如何控制日誌輸出級別?
1、先看 demo 代碼:
package indi.sword.demo;
import org.apache.log4j.Logger;
/**
* @author jeb_lin
* 3:35 PM 28/02/2019
*/
public class Demo {
private static final Logger LOGGER = Logger.getLogger(Demo.class);
public static void main(String[] args) {
LOGGER.debug("abc");
}
}
2、再看log4j.properties 配置
log4j.rootLogger=info,stdout,rollingLog
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p (%c{1}#%M:%L) %t - %m%n
log4j.appender.stdout.Threshold=debug
## rolling log file
log4j.appender.rollingLog.File=/Users/Documents/temp/logs/rolling.log
log4j.appender.rollingLog.MaxFileSize=512MB
log4j.appender.rollingLog.MaxBackupIndex=12
log4j.appender.rollingLog.Threshold=debug
log4j.appender.rollingLog.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingLog.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p (%c{1}#%M:%L) %t - %m%n
log4j.appender.rollingLog=org.apache.log4j.RollingFileAppender
3、提問:爲什麼我以下設置 ,就可以控制整體的日誌級別,源碼怎麼看?
log4j.rootLogger=info,stdout,rollingLog
log4j.appender.stdout.Threshold=debug
log4j.appender.rollingLog.Threshold=debug
LOGGER.debug("abc");無法打印。只能把下面的info 改成 debug 才能打印。爲什麼?
log4j.rootLogger=info,stdout,rollingLog
一、學習方法很重要,調試技巧很重要。
1、直接在入口加斷點,進入方法
2、根據debug跳轉,直接看代碼
public class Category implements AppenderAttachable {
...
public void debug(Object message) {
if(repository.isDisabled(Level.DEBUG_INT))
return;
if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG, message, null);
}
}
...
}
3、直接看關鍵代碼
if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG, message, null);
}
這一段代碼表示如果當前的代碼 LOGGER.debug(“abc”); 中的 debug 級別大於等於 this.getEffectiveLevel(),那麼就打印。
4、好了,看到這,就可以知道this.getEffectiveLevel()就是log4j.properties中log4j.rootLogger=info 這個info值。
5、接下來肯定產生疑問,怎麼跑到 Category 這個類來的,還有就是 this.getEffectiveLevel() 這個是啥。
二、this.getEffectiveLevel() 哪來的?
1、 剖析 this.getEffectiveLevel() ,探索下這個Level 是怎麼設置進去的,有get自然有set,那麼自然可以找到當前類內部的 setLevel 方法
public class Category implements AppenderAttachable {
volatile protected Level level;
...
public void debug(Object message) {
if(repository.isDisabled(Level.DEBUG_INT))
return;
if(Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG, message, null);
}
}
...
public Level getEffectiveLevel() {
for(Category c = this; c != null; c=c.parent) {
if(c.level != null)
return c.level;
}
return null; // If reached will cause an NullPointerException.
}
...
public void setLevel(Level level) {
this.level = level;
}
2、 接下來就是 要看 setLevel 在哪個地方被使用到,通過idea的工具,往上層找引用,你會發現不止一個引用處,這樣不利於我們排查問題。
三、setLevel 到底是在那裏被引用的?
一般來說,就剛剛我那兩段主代碼,自然可以想到是 Logger 初始化的時候 setLevel 進去的,看我的上一篇文章=點我=,可以知道 LogManager 有個static 代碼塊,用於初始化log4j的配置信息。
1、調試技巧 :在 static 的第一段代碼打一個斷點,不斷追蹤下去。
2、跟上一篇文章一樣,看到關鍵代碼:selectAndConfigure
3、繼續點進去,看到關鍵代碼 doConfigure
4、就繼續點進去會來到 PropertyConfigurator類 的 doConfigure方法。爲什麼是PropertyConfigurator 看我上一篇文章=點我=
5、繼續點進去 doConfigure 方法(重點觀察紅色箭頭的兩方法,第一個方法是以log4j.threshold 方式設置全局日誌Level,第二種是我要講的log4j.rootLogger=info,stdout,rollingLog 這個info的引用)
6、 直接看configureRootCategory(properties, hierarchy);爲什麼是 configureRootCategory 看我上一篇文章=點我=
7、debug便於理解,直接盯住value這個值
8、點開 parseCategory 這個方法,看到調試結果沒,這就是我們一路找的 setLevel的方法所在地,所以圓滿對接上了。
四、log4j.properties 的其他屬性呢?在哪設置進去的?
我們知道 :
log4j.rootLogger = [ level ] , appenderName1, appenderName2, …(默認輸出目的地,當前端傳入類名)
下面的代碼剛好解釋了一點 appenderName1日誌輸出地
根據log4j.properties 配置文件,配置 stout 與 rollingLog 的 appender,然後logger.addAppender(appender); 完成配置。
=注意這裏的 aai,待會下面有用到=
於是:
info的值>debug,自然不會 ForceLog了。
五、正常如何打印?
1、修改下面的info爲debug。
log4j.rootLogger=info,stdout,rollingLog
改爲:
log4j.rootLogger=debug,stdout,rollingLog
這下子就進來了
2、點開 forceLog 方法
3、 點開callAppenders 方法(注意aai,是不是很熟悉)
4、進入方法 appendLoopOnAppenders
5、 debug進入 doAppend 方法,進入到
public abstract class AppenderSkeleton implements Appender, OptionHandler {
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);
}
}
6、debug 進入 append方法
public class WriterAppender extends AppenderSkeleton {
public
void append(LoggingEvent event) {
// Reminder: the nesting of calls is:
//
// doAppend()
// - check threshold
// - filter
// - append();
// - checkEntryConditions();
// - subAppend();
if(!checkEntryConditions()) {
return;
}
subAppend(event);
}
}
7、進入 subAppend方法,看到真正的寫操作了
8、一個是寫文件FileAppender、一個是寫控制檯 stout
OVER...