測試:
@Test
publicvoid testDelegateHandleRequestFour2() {
Filefile = new File("E:\\study\\text.txt");
FileReaderfr = null;
try{
fr= new FileReader(file);
}catch (FileNotFoundException e) {
//log.error("測試 : ",e.getMessage());
//log.error("測試 :"+e.getMessage());
//log.err(“測試:”+e);
//log.error("測試 : ",e);
//log.error("測試:%s",e.getMessage(), e);
// e.printStackTrace();
}
}
1、log.error("測試:" ,e.getMessage()); // e.getMessage()爲空,不會打印異常信息
2、log.error("測試:" +e.getMessage()); // e.getMessage()只是獲取了異常的詳細消息字符串,沒有堆棧信息。
3、log.error("測試:" +e); //只會打印出異常名稱,不會打印堆棧信息
4、log.error("測試:%s",e.getMessage(), e); //在slf4j中 %s不是字符串轉換符,不起作用 %s會原樣輸出到日誌。 需要刪除。
5、可以使用e.printStackTrace() 打印異常的堆棧信息,但是後面不要在使用log.error("測試:" ,e); //這樣異常堆棧信息會打印重複
可以使用log.error("查詢賬戶資產時:" +e.getMessage()); 這種只會打印異常的字符串。
6、只認緊挨着的{}符號
log.error("測試 ,姓名:{{}} 年齡:{}"+e,name,age);
也可以使用代碼遷移工具 http://www.slf4j.org/migrator.html,但是有很多侷限性。也需要自己手動備份代碼,不建議使用。
網上看到有人說:試了下其他的幾個runtime異常,發現getMessage都是爲空的,之後又去試了下SQLException和IOException,發現者兩種異常的在catch的時候getMessage是不爲null的。由此覺得runtime異常發生的時候JVM調用的是父類無參的構造器。
public Exception() { super(); }
而SQLException和IOException異常發生的時候JVM調用的是父類有參的構造器
publicException(String message) { super(message); }
所以SQLException和IOException的getMessage不爲null,而runtime異常卻爲空。
但是實際測試結果:
@Test publicvoid testDelegateHandleRequestOfSQLException() { Modeluser = null; try{ StringBuffersql = new StringBuffer(); List<v_user_users>v_user_users_list = null; sql.append(SQLTempletes.SELECT); sql.append(SQLTempletes.V_USER_USERS); sql.append("where andt_users.id = ?"); //讓sql語法錯誤 EntityManagerem = JPA.em(); Query query = em.createNativeQuery(sql.toString(),v_user_users.class); query.setParameter(1, 1); query.setMaxResults(1); v_user_users_list = query.getResultList(); if(v_user_users_list.size() > 0){ user = v_user_users_list.get(0); } }catch(Exception e) { //e.printStackTrace(); log.info("用戶setId填充時(lazy=true):",e.getMessage()); } }
使用log.info("用戶setId填充時(lazy=true):",e.getMessage());
使用log.info("用戶setId填充時(lazy=true):",e);
說明在報sql異常或IO異常的時候getMessage在第二個參數時也是null。
這種寫法log.error("測試 : ",e.getMessage()); 爲null的原因最終原因其實是:Log的方法檢測最後一個參數是不是一個Throwable ,如果是則打印異常的堆棧信息,如果不是就當成前面format的參數,如果沒有{}佔位符,則忽略。
error方法的api如下:
public void error(String msg);
public void error(String format, Object arg);
public void error(String format, Object arg1,Object arg2);
public void error(String format, Object...arguments);
public void error(String msg, Throwable t);
public void error(Marker marker, String msg);
public void error(Marker marker, String format,Object arg);
public void error(Marker marker, String format,Object arg1, Object arg2);
public void error(Marker marker, String format,Object... arguments);
public void error(Marker marker, String msg,Throwable t);
參考資料:
Slf4j官方網站:
https://www.slf4j.org/
Slf4j源代碼請參考:
https://logback.qos.ch/xref/index.html
下面就一起學習和總結下slf4jslf4j的使用與綁定原理
slf4J的使用
前文說到了,單獨的slf4j是不能工作的,必須帶上其他具體的日誌實現方案。就以apache的log4j作爲具體日誌實現方案爲例,如果在工程中要使用slf4j作爲接口,並且要用log4j作爲具體實現方案,那麼我們需要做的事情如下:(下面的xxx表示具體版本號)
l 將slf4j-api-xxx.jar加入工程classpath中;
l 將slf4j-log4jxx-xxx.jar加入工程classpath中;
l 將log4j-xxx.jar加入工程classpath中;
l 將log4j.properties(log4j.xml)文件加入工程classpath中。
前兩個包在 http://www.slf4j.org/download.html 處下載,後一個包在http://logging.apache.org/log4j/1.2/download.html 下載,可能包文件名中的版本號有些差,不要緊。log4j.properties以前該是怎麼寫,現在還是怎麼寫,比如一個最簡單的內容,只向控制檯輸出日誌信息,如下:
log4j.rootLogger=DEBUG,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-ddHH:mm:ss,SSS} [%c]-[%p] %m%n
使用 SLF4J 的代碼:
public class TestSlf4j {
private static finalLogger logger = LoggerFactory.getLogger(TestSlf4j.class);
public static voidmain(String[] args) {
logger.info("Hello{}","SLF4J");
}
}
執行它,控制檯輸出:
2017-05-23 09:14:51,390[com.unmi.TestSlf4j]-[INFO] Hello SLF4J
slf4j的工作原理:
首先,slf4j-api作爲slf4j的接口類,使用在程序代碼中,這個包提供了一個Logger類和LoggerFactory類,Logger類用來打日誌,LoggerFactory類用來獲取Logger;slf4j-log4j是連接slf4j和log4j的橋樑,怎麼連接的呢?我們看看slf4j的LoggerFactory類的getLogger函數的源碼:
/**
* Return alogger named corresponding to the class passed as parameter, using
* the staticallybound {@link ILoggerFactory} instance.
*
* @paramclazz the returned logger will be named after clazz
* @returnlogger
*/
public static Logger getLogger(Class clazz) {
returngetLogger(clazz.getName());
}
/**
* Return alogger named according to the name parameter using the statically
* bound{@link ILoggerFactory} instance.
*
* @paramname The name of the logger.
* @returnlogger
*/
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
returniLoggerFactory.getLogger(name);
}
publicstatic ILoggerFactory getILoggerFactory() {
if(INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
switch(INITIALIZATION_STATE) {
caseSUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
caseNOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
caseFAILED_INITIALIZATION:
thrownew IllegalStateException(UNSUCCESSFUL_INIT_MSG);
caseONGOING_INITIALIZATION:
//support re-entrant behavior.
//See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
return TEMP_FACTORY;
}
throw newIllegalStateException("Unreachable code");
}
追蹤到最後,發現LoggerFactory.getLogger()首先獲取一個ILoggerFactory接口,然後使用該接口獲取具體的Logger。獲取ILoggerFactory的時候用到了一個StaticLoggerBinder類,仔細研究我們會發現StaticLoggerBinder這個類並不是slf4j-api這個包中的類,而是slf4j-log4j包中的類,這個類就是一箇中間類,它用來將抽象的slf4j變成具體的log4j,也就是說具體要使用什麼樣的日誌實現方案,就得靠這個StaticLoggerBinder類。再看看slf4j-log4j包中的這個StaticLoggerBinder類創建ILoggerFactory長什麼樣子:
private final ILoggerFactory loggerFactory;
private StaticLoggerBinder() {
loggerFactory = new Log4jLoggerFactory();
try {
Levellevel = Level.TRACE;
} catch(NoSuchFieldError nsfe) {
Util
.report("This version of SLF4J requires log4j version 1.2.12 orlater. See also http://www.slf4j.org/codes.html#log4j_version");
}
}
public ILoggerFactory getLoggerFactory() {
returnloggerFactory;
}
可以看到slf4j-log4j中的StaticLoggerBinder類創建的ILoggerFactory其實是一個 org.slf4j.impl.Log4jLoggerFactory ,這個類的getLogger函數是這樣的:
public Logger getLogger(String name) {
Loggerslf4jLogger = loggerMap.get(name);
if(slf4jLogger != null) {
returnslf4jLogger;
} else {
org.apache.log4j.Logger log4jLogger;
if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
log4jLogger = LogManager.getRootLogger();
else
log4jLogger = LogManager.getLogger(name);
LoggernewInstance = new Log4jLoggerAdapter(log4jLogger);
LoggeroldInstance = loggerMap.putIfAbsent(name, newInstance);
returnoldInstance == null ? newInstance : oldInstance;
}
}
就在其中創建了真正的 org.apache.log4j.Logger ,也就是我們需要的具體的日誌實現方案的Logger類。就這樣,整個綁定過程就完成了。
Log4j和Slf4j的比較
SLF4J是爲各種loging APIs提供一個簡單統一的接口,從而使得最終用戶能夠在部署的時候配置自己希望的loging APIs實現。準確的說,slf4j並不是一種具體的日誌系統,而是一個用戶日誌系統的facade,允許用戶在部署最終應用時方便的變更其日誌系統。
在系統開發中,統一按照slf4j的API進行開發,在部署時,選擇不同的日誌系統包,即可自動轉換到不同的日誌系統上。比如:選擇JDK自帶的日誌系統,則只需要將slf4j-api-1.5.10.jar和slf4j-jdk14-1.5.10.jar放置到classpath中即可,如果中途無法忍受JDK自帶的日誌系統了,想換成log4j的日誌系統,僅需要用slf4j-log4j12-1.5.10.jar替換slf4j-jdk14-1.5.10.jar即可(當然也需要log4j的jar及配置文件)
SLF4J獲得logger對象:
private staticfinal Logger logger = LoggerFactory.getLogger(Test.class);
LOG4J獲得logger對象:
private staticLogger logger = Logger.getLogger(Test.class);
總結:
1. 大部分人在程序裏面會去寫logger.error(exception),其實這個時候log4j會去把這個exception tostring。真正的寫法應該是logger(message.exception);而slf4j就不會使得程序員犯這個錯誤。
2. log4j間接的在鼓勵程序員使用string相加的寫法,而slf4j就不會有這個問題。
3. 你可以使用logger.error("{}is+serviceid",serviceid);
4. 使用slf4j可以方便的使用其提供的各種具體的實現的jar。(類似commons-logger)
5. 從commons--logger和log4j merge非常方便,slf4j也提供了一個swing的tools來幫助大家完成這個merge。
slf4j的用法:
1. 從org.slf4j包導入Logger和LoggerFactory
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
2. 聲明日誌類private final Logger logger = LoggerFactory.getLogger(LoggingSample.class);
3. 簡單用法
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public classHelloWorld{
public staticvoid main(String[] args){
Logger logger
= LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
4.輸出帶參數的日誌信息,使用{}佔位符,方法中傳入參數
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Wombat { final Logger logger = LoggerFactory.getLogger(Wombat.class); Integer t; Integer oldT; public void setTemperature(Integer temperature) { oldT = t; t = temperature; logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT); if(temperature.intValue() > 50) { logger.info("Temperature has risen above 50 degrees."); } } }