idea 添加注釋
/** 然后回車
選中代碼塊 Ctrl+Shift+/
重點推薦閱讀:
http://www.itdecent.cn/p/8551fe9c6354
目前我用的兩種方式。
文章來源于公司的大牛
1 Log的用途
不管是使用何種編程語言,日志輸出幾乎無處不再??偨Y(jié)起來,日志大致有以下幾種用途:
l 問題追蹤:通過日志不僅僅包括我們程序的一些bug,也可以在安裝配置時,通過日志可以發(fā)現(xiàn)問題。
l 狀態(tài)監(jiān)控:通過實時分析日志,可以監(jiān)控系統(tǒng)的運行狀態(tài),做到早發(fā)現(xiàn)問題、早處理問題。
l 安全審計:審計主要體現(xiàn)在安全上,通過對日志進(jìn)行分析,可以發(fā)現(xiàn)是否存在非授權(quán)的操作。
2 記錄Log的基本原則
2.1 日志的級別劃分
Java日志通常可以分為:error、warn、info、debug、trace五個級別。在J2SE中預(yù)定義的級別更多,分別為:SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST。兩者的對應(yīng)大致如下:
|
Log4j、slf4j
|
J2se
|
使用場景
|
|
error
|
SEVERE
|
問題已經(jīng)影響到軟件的正常運行,并且軟件不能自行恢復(fù)到正常的運行狀態(tài),此時需要輸出該級別的錯誤日志。
|
|
warn
|
WARNING
|
與業(yè)務(wù)處理相關(guān)的失敗,此次失敗不影響下次業(yè)務(wù)的執(zhí)行,通常的結(jié)果為外部的輸入不能獲得期望的結(jié)果。
|
|
info
|
INFO
|
系統(tǒng)運行期間的系統(tǒng)運行狀態(tài)變化,或關(guān)鍵業(yè)務(wù)處理記錄等用戶或管理員在系統(tǒng)運行期間關(guān)注的一些信息。
|
|
CONFIG
|
系統(tǒng)配置、系統(tǒng)運行環(huán)境信息,有助于安裝實施人員檢查配置是否正確。
|
|
debug
|
FINE
|
軟件調(diào)試信息,開發(fā)人員使用該級別的日志發(fā)現(xiàn)程序運行中的一些問題,排除故障。
|
|
FINER
|
基本同上,但顯示的信息更詳盡。
|
|
trace
|
FINEST
|
基本同上,但顯示的信息更詳盡。
|
2.2 日志對性能影響
不管是多么優(yōu)秀的日志工具,在日志輸出時總會對性能產(chǎn)生或多或少的影響,為了將影響降低到最低,有以下幾個準(zhǔn)則需要遵守:
l 如何創(chuàng)建Logger實例:創(chuàng)建Logger實例有是否static的區(qū)別,在log4j的早期版本,一般要求使用static,而在高版本以及后來的slf4j中,該問題已經(jīng)得到優(yōu)化,獲?。▌?chuàng)建)logger實例的成本已經(jīng)很低。所以我們要求:對于可以預(yù)見的多數(shù)情況下單例運行的class,可以不添加static前綴;對于可能是多例居多,尤其是需要頻繁創(chuàng)建的class,我們要求要添加static前綴。
l 判斷日志級別:
n對于可以預(yù)見的會頻繁產(chǎn)生的日志輸出,比如for、while循環(huán),定期執(zhí)行的job等,建議先使用if對日志級別進(jìn)行判斷后再輸出。
n對于日志輸出內(nèi)容需要復(fù)雜的序列化,或輸出的某些信息獲取成本較高時,需要對日志級別進(jìn)行判斷。比如日志中需要輸出用戶名,而用戶名需要在日志輸出時從數(shù)據(jù)庫獲取,此時就需要先判斷一下日志級別,看看是否有必要獲取這些信息。
l 優(yōu)先使用參數(shù),減少字符串拼接:使用參數(shù)的方式輸出日志信息,有助于在性能和代碼簡潔之間取得平衡。當(dāng)日志級別限制輸出該日志時,參數(shù)內(nèi)容將不會融合到最終輸出中,減少了字符串的拼接,從而提升執(zhí)行效率。
2.3 什么時候輸出日志
日志并不是越多越詳細(xì)就越好。在分析運行日志,查找問題時,我們經(jīng)常遇到該出現(xiàn)的日志沒有,無用的日志一大堆,或者有效的日志被大量無意義的日志信息淹沒,查找起來非常困難。那么什么時候輸出日志呢?以下列出了一些常見的需要輸出日志的情況,而且日志的級別基本都是>=INFO,至于Debug級別日志的使用場景,本節(jié)沒有專門列出,需要具體情況具體分析,但也是要追求“恰如其分”,不是越多越好。
2.3.1 系統(tǒng)啟動參數(shù)、環(huán)境變量
系統(tǒng)啟動的參數(shù)、配置、環(huán)境變量、System.Properties等信息對于軟件的正常運行至關(guān)重要,這些信息的輸出有助于安裝配置人員通過日志快速定位問題,所以程序有必要在啟動過程中把使用到的關(guān)鍵參數(shù)、變量在日志中輸出出來。在輸出時需要注意,不是一股腦的全部輸出,而是將軟件運行涉及到的配置信息輸出出來。比如,如果軟件對jvm的內(nèi)存參數(shù)比較敏感,對最低配置有要求,那么就需要在日志中將-Xms -Xmx -XX:PermSize這幾個參數(shù)的值輸出出來。
2.3.2 異常捕獲處
在捕獲異常處輸出日志,大家在基本都能做到,唯一需要注意的是怎么輸出一個簡單明了的日志信息。這在后面的問題問題中有進(jìn)一步說明。
2.3.3 函數(shù)獲得期望之外的結(jié)果時
一個函數(shù),尤其是供外部系統(tǒng)或遠(yuǎn)程調(diào)用的函數(shù),通常都會有一個期望的結(jié)果,但如果內(nèi)部系統(tǒng)或輸出參數(shù)發(fā)生錯誤時,函數(shù)將無法返回期望的正確結(jié)果,此時就需要記錄日志,日志的基本通常是warn。需要特別說明的是,這里的期望之外的結(jié)果不是說沒有返回就不需要記錄日志了,也不是說返回false就需要記錄日志。比如函數(shù):isXXXXX(),無論返回true、false記錄日志都不是必須的,但是如果系統(tǒng)內(nèi)部無法判斷應(yīng)該返回true還是false時,就需要記錄日志,并且日志的級別應(yīng)該至少是warn。
2.3.4 關(guān)鍵操作
關(guān)鍵操作的日志一般是INFO級別,如果數(shù)量、頻度很高,可以考慮使用DEBUG級別。以下是一些關(guān)鍵操作的舉例,實際的關(guān)鍵操作肯定不止這么多。
n 刪除:刪除一個文件、刪除一組重要數(shù)據(jù)庫記錄……
n 添加:和外系統(tǒng)交互時,收到了一個文件、收到了一個任務(wù)……
n 處理:開始、結(jié)束一條任務(wù)……
n ……
2.4 日志輸出的內(nèi)容
l ERROR:錯誤的簡短描述,和該錯誤相關(guān)的關(guān)鍵參數(shù),如果有異常,要有該異常的StackTrace。
l WARN:告警的簡短描述,和該錯誤相關(guān)的關(guān)鍵參數(shù),如果有異常,要有該異常的StackTrace。
l INFO:言簡意賅地信息描述,如果有相關(guān)動態(tài)關(guān)鍵數(shù)據(jù),要一并輸出,比如相關(guān)ID、名稱等。
l DEBUG:簡單描述,相關(guān)數(shù)據(jù),如果有異常,要有該異常的StackTrace。
在日志相關(guān)數(shù)據(jù)輸出的時要特別注意對敏感信息的保護(hù),比如修改密碼時,不能將密碼輸出到日志中。
2.5 什么時候使用J2SE自帶的日志
我們通常使用slf4j或log4j這兩個工具記錄日志,那么還需要使用J2SE的日志框架嗎?當(dāng)然需要,在我們編寫一些通用的工具類時,為了減少對第三方的jar包的依賴,首先要考慮使用java.util.logging。
考慮到slf4j等日志框架提供了日志bridge工具,為java.util.logging提供了Handler,所以普通應(yīng)用的開發(fā)過程中也可以考慮使用J2SE自有日志,這樣不但可以減少項目的編譯依賴,同時在應(yīng)用實施時可以更靈活的選擇日志的輸出工具包。
3 典型問題分析
3.1 該用日志的地方不用
上圖對異常的處理直接使用e.printStackTrace()顯然是有問題的,正確的做法是:要么通過日志方式輸出錯誤信息,要么直接拋出異常,要么創(chuàng)建新的自定義異常拋出。
另:對于靜態(tài)工具類函數(shù)中的異常處理,最簡單的方式就是不捕獲、不記錄日志,直接向上拋出,如果認(rèn)為異常類型太多,或者意義不明確,可以拋出自定義異常類的實例。
3.2 啰嗦重復(fù)、沒有重點
首先上面不應(yīng)該有error級別的日志。
其次在日志中直接輸出e.toString(),為定位問題提供的信息太少。
另外需要明確一點:日志系統(tǒng)是一個多線程公用的系統(tǒng),在兩行日志輸出之間有可能會被插入其他線程的日志記錄,不會按照我們的意愿順序輸出,后面有更典型的例子。
最后,上面的日志可以簡化為:
logger.debug(“從properties中...{}...{}...”,name, value, e);
logger.warn(“從properties中獲取{}發(fā)生錯誤:{}”,name, e.toString());
或者直接一句:
logger.warn(“從properties中...{}...{}...”,name, value, e);
或者更完美的:
if(logger.isDebugEnabled()){
logger.warn(“從properties中...{}...”, name, e);
}else{
logger.warn(“從properties中獲取{}發(fā)生錯誤:{}”, name, e.toString());
}
3.3 日志和異常處理的關(guān)系
首先上面的日志信息不夠充分,級別定義不夠恰當(dāng)。
另外,既然將異常捕獲并記錄的日志,就不應(yīng)該重新將一個一模一樣的異常再次拋出去了。如果將異常再次拋出,那在上層肯定還需要對該異常進(jìn)行處理,并記錄日志,這樣就重復(fù)了。如果沒有特別原因,此處不應(yīng)該捕獲異常。
3.4 System.out方式的日志
上面的日志形式十分隨意,只適合臨時的代碼調(diào)試,不允許提交到正式的代碼庫中。
對于臨時調(diào)試日志,建議在日志的輸出信息中添加一些特殊的連續(xù)字符,也可以用自己的名稱、代號,這樣可以在調(diào)試完畢后,提交代碼之前,方便地找到所有臨時代碼,一并刪除。
3.5 日志信息不明確
上面的“添加任務(wù)出錯。。。”既沒有記錄任務(wù)id,也沒有任務(wù)名稱,軟件部署后發(fā)現(xiàn)錯誤后,根據(jù)該日志記錄不能確認(rèn)哪一條任務(wù)錯誤,給進(jìn)一步的分析原因帶來困難。
另外第二個紅圈中的問題有:要使用參數(shù);一行日志就可以了。
還有一些其他共性的錯誤,就不多說了。
3.6 忘記日志輸出是多線程公用的
如果有另外一個線程正在輸出日志,上面的記錄就會被打斷,最終顯示輸出和預(yù)想的就會不一致。正確的做法應(yīng)是將這些信息放到一行,如果需要換行可以考慮使用“\r”,如果內(nèi)容較多,考慮增加if (logger.isDebugEnabled())進(jìn)行判斷。而第二個例子中的輸出有System.out的習(xí)慣,相關(guān)內(nèi)容應(yīng)該一行完成。
3.7 多個參數(shù)的處理
對于多參的日志輸出,可以考慮:
public void debug(String format, Object... arguments);
但是在使用多參時,會創(chuàng)建一個對象數(shù)組,也會有一定的消耗,為此,在對性能敏感的場景,可以增加對日志級別的判斷。
在開發(fā)B/S系統(tǒng)時,對于LOG,需要關(guān)注:
- 日志信息的集中采集、存儲、信息檢索:在WEB集群節(jié)點越來越多的情況下,讓開發(fā)及系統(tǒng)維護(hù)人員能很方便的查看日志信息
- 日志信息的輸出策略:日志信息輸出全而不亂,便于跟蹤和分析問題
- 關(guān)鍵業(yè)務(wù)的日志輸出:基于度量數(shù)據(jù)采集、數(shù)據(jù)核查、系統(tǒng)安全等方面的考慮,關(guān)鍵業(yè)務(wù)系統(tǒng)對輸出的日志信息有特殊的要求,需要做針對性的設(shè)計
本文主要從這3個方面進(jìn)行說明,重點說明日志輸出的使用
日志的采集和存儲
對于目前存儲日志,主要存在2種方式:
- 本地日志:直接存放在本地磁盤上
- 遠(yuǎn)程日志:發(fā)往?日志平臺,用作數(shù)據(jù)分析和日志處理并展現(xiàn)。如用戶訪問行為的記錄,異常日志,性能統(tǒng)計日志等。
日志工具的選擇
推薦使用SLF4J(Simple Logging Facade for Java)作為日志的api,SLF4J是一個用于日志系統(tǒng)的簡單Facade,允許最終用戶在部署其應(yīng)用時使用其所希望的日志系統(tǒng)。與使用apache commons-logging和直接使用log4j相比,SLF4J提供了一個名為參數(shù)化日志的高級特性,可以顯著提高在配置為關(guān)閉日志的情況下的日志語句性能
//slf4j
log.debug("Found {} records matching filter: '{}'", records, filter);
//log4j
log.debug("Found " + records + " records matching filter: '" + filter + "'");
可以看出SLF4J的方式一方面更簡略易讀,另一方面少了字符串拼接的開銷,并且在日志級別達(dá)不到時(這里例子即為設(shè)置級別為debug以上),不會調(diào)用對象的toString方法。
日志輸出級別(由高到低)
ERROR:系統(tǒng)中發(fā)生了非常嚴(yán)重的問題,必須馬上有人進(jìn)行處理。沒有系統(tǒng)可以忍受這個級別的問題的存在。比如:NPEs(空指針異常),數(shù)據(jù)庫不可用,關(guān)鍵業(yè)務(wù)流程中斷等等
WARN:發(fā)生這個級別問題時,處理過程可以繼續(xù),但必須對這個問題給予額外關(guān)注。這個問題又可以細(xì)分成兩種情況:一種是存在嚴(yán)重的問題但有應(yīng)急措施(比如數(shù)據(jù)庫不可用,使用Cache);第二種是潛在問題及建議(ATTENTION),比如生產(chǎn)環(huán)境的應(yīng)用運行在Development模式下、管理控制臺沒有密碼保護(hù)等。系統(tǒng)可以允許這種錯誤的存在,但必須及時做跟蹤檢查
INFO:重要的業(yè)務(wù)處理已經(jīng)結(jié)束。在實際環(huán)境中,系統(tǒng)管理員或者高級用戶要能理解INFO輸出的信息并能很快的了解應(yīng)用正在做什么。比如,一個和處理機(jī)票預(yù)訂的系統(tǒng),對每一張票要有且只有一條INFO信息描述 "[Who] booked ticket from [Where] to [Where]"。另外一種對INFO信息的定義是:記錄顯著改變應(yīng)用狀態(tài)的每一個action,比如:數(shù)據(jù)庫更新、外部系統(tǒng)請求
DEBUG:用于開發(fā)人員使用。將在TRACE章節(jié)中一起說明這個級別該輸出什么信息
TRACE:非常具體的信息,只能用于開發(fā)調(diào)試使用。部署到生產(chǎn)環(huán)境后,這個級別的信息只能保持很短的時間。這些信息只能臨時存在,并將最終被關(guān)閉。要區(qū)分DEBUG和TRACE會比較困難,對一個在開發(fā)及測試完成后將被刪除的LOG輸出,可能會比較適合定義為TRACE級別
推薦使用debug,info,warn,error級別即可,對于不同的級別可以設(shè)置不同的輸出路徑,如debug,info輸出到一個文件,warn,error輸出到一個帶error后綴的文件
日志API規(guī)范
- Log對象的聲明和初始化,僅以下代碼是符合規(guī)范的
// (推薦)
private static final Logger logger = LoggerFactory.getLogger(Xxx.class);
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final Logger logger = LoggerFactory.getLogger("loggerName");
private static Logger logger = LoggerFactory.getLogger(Xxx.class);
protected final Logger logger = LoggerFactory.getLogger(getClass());
private Logger logger = LoggerFactory.getLogger(getClass());
protected Logger logger = LoggerFactory.getLogger(getClass());
不得使用System.out, System.err進(jìn)行日志記錄,請改使用logger.debug、logger.error
debug/info級別的信息,信息本身需要計算或合并的,必須加 isXxxEnabled() 判斷在前,這樣可以大大提高高并發(fā)下的效率。如:
if (logger.isDebugEnabled()) {
logger.debug(test());
}
private String test(){
int i = 0;
while (i < 1000000) {
i++;
}
return "";
}
如果不加 isXxxEnabled() 判斷,test()在info級別下也會執(zhí)行。
注意error和warn級別的區(qū)別,導(dǎo)致業(yè)務(wù)不正常服務(wù)的,用error級別;錯誤是預(yù)期會發(fā)生的,并且已經(jīng)有了其他的處理流程,使用warn級別
正確的記錄異常信息
記錄異常信息是“記錄所有信息”中的一個重要組成,但很多開發(fā)人員只是把logging當(dāng)做處理異常的一種方式。他們通常返回缺省值,然后當(dāng)做什么都沒發(fā)生。 其他時候,他們先log異常信息,然后再拋出包裝過的異常。如:
log.error("IO exception", e);
throw new MyCustomException(e);
這種方法總是會打印兩次相同的 stack trace信息,因為有些地方會捕捉MyCustomException異常,然后輸出導(dǎo)致問題的日志信息。
但有時基于某些原因我們真的想log異常信息怎么辦?很過見多的log語句有一半以上都是錯的,
如:
try {
Integer x = null;
++x;
} catch (Exception e) {
//A
log.error(e);
//B
log.error(e, e);
//C
log.error(""+ e);
//D
log.error(e.toString());
//E
log.error(e.getMessage());
//F
log.error(null, e);
//G
log.error("", e);
//H
log.error("{}", e);
//I
log.error("{}", e.getMessage());
//J
log.error("Error reading configuration file: " + e);
//K
log.error("Error reading configuration file: "+ e.getMessage());
//L
log.error("Error reading configuration file", e);
}
上面只有G、L是對的,L的處理方式更好一些
注意:捕獲異常后不處理也不輸出log是一種非常不負(fù)責(zé)任的行為,這會造成問題很難被定位,極大地提高debug成本!
- 重要方法入口,業(yè)務(wù)流程前后及處理的結(jié)果等,推薦記錄log,并使用debug級別,如:
public String printDocument(Document doc, Mode mode) {
log.debug("Entering printDocument(doc={}, mode={})", doc, mode);
//Lengthy printing operation
String id = "Id";
log.debug("Leaving printDocument(): {}", id);
return id;
}
因為對于非開發(fā)人員掌控的環(huán)境(無法做DEBUG),記錄方法調(diào)用、入?yún)ⅰ⒎祷刂档姆绞綄τ谂挪閱栴}會有很大幫助。
- Log的內(nèi)容一定要確保不會因為Log語句的問題而拋出異常造成中斷,如:
log.debug("Processing request with id: {}", request.getId());
你能確保request對象不是NULL嗎?如果request為null,就會拋出NullPointerException。
- 避免拖慢應(yīng)用系統(tǒng)
輸出太多日志信息:通常每小時輸出到disk的數(shù)據(jù)量達(dá)到幾百MiB就已經(jīng)是上限了不正確使用toString() 或字符串拼接方法。
try {
log.trace("Id=" + request.getUser().getId() + " accesses" + manager.getPage().getUrl().toString())
} catch(NullPointerException e) {
}
- 日志信息中盡量包含數(shù)據(jù)和描述:
easy to read, easy to parse
一些反模式的例子
log.debug("Message processed");
log.debug(message.getJMSMessageID());
log.debug("Message with id '{}' processed", message.getJMSMessageID());
if (message instanceof TextMessage)
//...
else {
log.warn("Unknown message type");
}
為什么不包含 message type, message id, etc,包含個message content很難嗎?另一個anti-pattern是magic-log。
有些開發(fā)人員為了自己查找信息方便,輸出類似“&&&!#”的Log,而不是“Message with XYZ id received”。
最后,Log 不要涉及密碼及個人信息(身份證、銀行卡號etc)
- 關(guān)鍵業(yè)務(wù)系統(tǒng)日志的要求
- 用戶瀏覽日志
使用WEB服務(wù)器或應(yīng)用服務(wù)器實現(xiàn)日志輸出,關(guān)鍵信息包括:訪問時間、用戶IP、訪問的URL、用戶瀏覽器信息、HTTP狀態(tài)碼、請求處理時間
- 用戶登錄日志
用于記錄用戶的Login、Logout、CheckLogin請求情況,關(guān)鍵信息如下:
Login:請求時間、用戶IP、用戶名、渠道信息、用戶瀏覽器信息、登錄處理結(jié)果、請求花費時間、tokenId、sessionid
Logout:請求時間、用戶IP、用戶名、渠道信息、用戶瀏覽器信息、登出結(jié)果、請求花費時間、tokenid、sessionid
CheckLogin:請求時間、用戶IP、用戶名、渠道信息、用戶瀏覽器信息、檢查結(jié)果、檢查花費時間、tokenid、sessionId
- 服務(wù)接口調(diào)用日志
所有外部接口的調(diào)用需要記錄接口訪問信息,關(guān)鍵信息如下:
請求時間、接口URL、接口方法、調(diào)用結(jié)果、執(zhí)行時間
配置規(guī)范
統(tǒng)一使用log4j.xml、log4j2.xml、logback配置。
所有的jar包中不推薦包含log4j.xml、log4j.properties、logback.xml文件,避免干擾實際的業(yè)務(wù)系統(tǒng)。
注意Logger間的繼承關(guān)系,如:
- log4j的繼承是通過命名來實現(xiàn)的。
- 子logger會默認(rèn)繼承父logger的appender,將它們加入到自己的Appender中;除非加上了additivity="false",則不再繼承父logger的appender。
- 子logger只在自己未定義輸出級別的情況下,才會繼承父logger的輸出級別。
Log文件位置和命名,目前Log文件的位置統(tǒng)一放在相同目錄下面,Log名字通常以業(yè)務(wù)名開頭,如xxx.log.2015-11-19等。
日志格式:必選打印數(shù)據(jù)項: 發(fā)生時間、日志級別、日志內(nèi)容,可選文件和行號。
遠(yuǎn)程日志的輸出需要注意host和port,區(qū)分cagegory。
作者 @九都散人
2015 年 11月 22日
參考
- SLF4J :http://www.slf4j.org/docs.html
- Log4j:http://logging.apache.org/
- Log4j2:http://logging.apache.org/log4j/2.x/
作者:九都散人
鏈接:http://www.itdecent.cn/p/8551fe9c6354
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。
Overview
一個在生產(chǎn)環(huán)境里運行的程序如果沒有日志是很讓維護(hù)者提心吊膽的,有太多雜亂又無意義的日志也是令人傷神。程序出現(xiàn)問題時候,從日志里如果發(fā)現(xiàn)不了問題可能的原因是很令人受挫的。本文想討論的是如何在Java程序里寫好日志。
一般來說日志分為兩種:業(yè)務(wù)日志和異常日志,使用日志我們希望能達(dá)到以下目標(biāo):
- 對程序運行情況的記錄和監(jiān)控;
- 在必要時可詳細(xì)了解程序內(nèi)部的運行狀態(tài);
- 對系統(tǒng)性能的影響盡量??;
Java日志框架
Java的日志框架太多了。。。
- Log4j 或 Log4j 2 - Apache的開源項目,通過使用Log4j,我們可以控制日志信息輸送的目的地是控制臺、文件、GUI組件、甚至是套接口服務(wù)器、NT的事件記錄器、UNIX Syslog守護(hù)進(jìn)程等;用戶也可以控制每一條日志的輸出格式;通過定義每一條日志信息的級別,用戶能夠更加細(xì)致地控制日志的生成過程。這些可以通過一個配置文件(XML或Properties文件)來靈活地進(jìn)行配置,而不需要修改程序代碼。Log4j 2則是前任的一個升級,參考了Logback的許多特性;
- Logback - Logback是由log4j創(chuàng)始人設(shè)計的又一個開源日記組件。logback當(dāng)前分成三個模塊:logback-core,logback- classic和logback-access。logback-core是其它兩個模塊的基礎(chǔ)模塊。logback-classic是log4j的一個改良版本。此外logback-classic完整實現(xiàn)SLF4J API使你可以很方便地更換成其它日記系統(tǒng)如log4j或JDK14 Logging;
- java.util.logging - JDK內(nèi)置的日志接口和實現(xiàn),功能比較簡;
- Slf4j - SLF4J是為各種Logging API提供一個簡單統(tǒng)一的接口),從而使用戶能夠在部署的時候配置自己希望的Logging API實現(xiàn);
- Apache Commons Logging - Apache Commons Logging (JCL)希望解決的問題和Slf4j類似。
選項太多了的后果就是選擇困難癥,我的看法是沒有最好的,只有最合適的。在比較關(guān)注性能的地方,選擇Logback或自己實現(xiàn)高性能Logging API可能更合適;在已經(jīng)使用了Log4j的項目中,如果沒有發(fā)現(xiàn)問題,繼續(xù)使用可能是更合適的方式;我一般會在項目里選擇使用Slf4j, 如果不想有依賴則使用java.util.logging或框架容器已經(jīng)提供的日志接口。
Java日志最佳實踐
定義日志變量
日志變量往往不變,最好定義成final static,變量名用大寫。
日志分級
Java的日志框架一般會提供以下日志級別,缺省打開info級別,也就是debug,trace級別的日志在生產(chǎn)環(huán)境不會輸出,在開發(fā)和測試環(huán)境可以通過不同的日志配置文件打開debug級別。
- fatal - 嚴(yán)重的,造成服務(wù)中斷的錯誤;
- error - 其他錯誤運行期錯誤;
- warn - 警告信息,如程序調(diào)用了一個即將作廢的接口,接口的不當(dāng)使用,運行狀態(tài)不是期望的但仍可繼續(xù)處理等;
- info - 有意義的事件信息,如程序啟動,關(guān)閉事件,收到請求事件等;
- debug - 調(diào)試信息,可記錄詳細(xì)的業(yè)務(wù)處理到哪一步了,以及當(dāng)前的變量狀態(tài);
- trace - 更詳細(xì)的跟蹤信息;
在程序里要合理使用日志分級:

基本的Logger編碼規(guī)范
1.在一個對象中通常只使用一個Logger對象,Logger應(yīng)該是static final的,只有在少數(shù)需要在構(gòu)造函數(shù)中傳遞logger的情況下才使用private final。

2.輸出Exceptions的全部Throwable信息,因為logger.error(msg)和logger.error(msg,e.getMessage())這樣的日志輸出方法會丟失掉最重要的StackTrace信息。

3.不允許記錄日志后又拋出異常,因為這樣會多次記錄日志,只允許記錄一次日志。

4.不允許出現(xiàn)System print(包括System.out.println和System.error.println)語句。

5.不允許出現(xiàn)printStackTrace。

6.日志性能的考慮,如果代碼為核心代碼,執(zhí)行頻率非常高,則輸出日志建議增加判斷,尤其是低級別的輸出<debug、info、warn>。
debug日志太多后可能會影響性能,有一種改進(jìn)方法是:

但更好的方法是Slf4j提供的最佳實踐:

一方面可以減少參數(shù)構(gòu)造的開銷,另一方面也不用多寫兩行代碼。
7.有意義的日志
通常情況下在程序日志里記錄一些比較有意義的狀態(tài)數(shù)據(jù):程序啟動,退出的時間點;程序運行消耗時間;耗時程序的執(zhí)行進(jìn)度;重要變量的狀態(tài)變化。
初次之外,在公共的日志里規(guī)避打印程序的調(diào)試或者提示信息。
FAQ:參考
1.7 Good Rules to Log Exceptions
https://www.cnblogs.com/kofxxf/p/3713472.html
https://blog.csdn.net/xiangnideshen/article/details/45894631