歡迎關(guān)注作者簡(jiǎn)書(shū)
csdn傳送門(mén)
LOG日志詳解
Log的用途
問(wèn)題追蹤:通過(guò)日志不僅僅包括我們程序的一些bug绸贡,也可以在安裝配置時(shí)排监,通過(guò)日志可以發(fā)現(xiàn)問(wèn)題。
狀態(tài)監(jiān)控:通過(guò)實(shí)時(shí)分析日志践樱,可以監(jiān)控系統(tǒng)的運(yùn)行狀態(tài)翰灾,做到早發(fā)現(xiàn)問(wèn)題缕粹、早處理問(wèn)題。
安全審計(jì):審計(jì)主要體現(xiàn)在安全上纸淮,通過(guò)對(duì)日志進(jìn)行分析平斩,可以發(fā)現(xiàn)是否存在非授權(quán)的操作。
記錄Log的基本原則
日志的級(jí)別劃分
Java日志通逞士椋可以分為:error绘面、warn、info侈沪、debug揭璃、trace五個(gè)級(jí)別。在J2SE中預(yù)定義的級(jí)別更多亭罪,分別為:SEVERE瘦馍、WARNING、INFO应役、CONFIG扣墩、FINE哲银、FINER、FINEST呻惕。
日志對(duì)性能的影響
不管是多么優(yōu)秀的日志工具荆责,在日志輸出時(shí)總會(huì)對(duì)性能產(chǎn)生或多或少的影響,為了將影響降低到最低亚脆,有以下幾個(gè)準(zhǔn)則需要遵守:
如何創(chuàng)建Logger實(shí)例:創(chuàng)建Logger實(shí)例有是否static的區(qū)別做院,在log4j的早期版本,一般要求使用static濒持,而在高版本以及后來(lái)的slf4j中键耕,該問(wèn)題已經(jīng)得到優(yōu)化,獲雀逃(創(chuàng)建)logger實(shí)例的成本已經(jīng)很低屈雄。所以我們要求:對(duì)于可以預(yù)見(jiàn)的多數(shù)情況下單例運(yùn)行的class,可以不添加static前綴官套;對(duì)于可能是多例居多酒奶,尤其是需要頻繁創(chuàng)建的class,我們要求要添加static前綴奶赔。
判斷日志級(jí)別:
n對(duì)于可以預(yù)見(jiàn)的會(huì)頻繁產(chǎn)生的日志輸出惋嚎,比如for、while循環(huán)站刑,定期執(zhí)行的job等另伍,建議先使用if對(duì)日志級(jí)別進(jìn)行判斷后再輸出。
n對(duì)于日志輸出內(nèi)容需要復(fù)雜的序列化绞旅,或輸出的某些信息獲取成本較高時(shí)摆尝,需要對(duì)日志級(jí)別進(jìn)行判斷。比如日志中需要輸出用戶名因悲,而用戶名需要在日志輸出時(shí)從數(shù)據(jù)庫(kù)獲取结榄,此時(shí)就需要先判斷一下日志級(jí)別,看看是否有必要獲取這些信息囤捻。
優(yōu)先使用參數(shù),減少字符串拼接:使用參數(shù)的方式輸出日志信息邻寿,有助于在性能和代碼簡(jiǎn)潔之間取得平衡蝎土。當(dāng)日志級(jí)別限制輸出該日志時(shí),參數(shù)內(nèi)容將不會(huì)融合到最終輸出中绣否,減少了字符串的拼接誊涯,從而提升執(zhí)行效率。
什么時(shí)候輸出日志
日志并不是越多越詳細(xì)就越好蒜撮。在分析運(yùn)行日志暴构,查找問(wèn)題時(shí)跪呈,我們經(jīng)常遇到該出現(xiàn)的日志沒(méi)有,無(wú)用的日志一大堆取逾,或者有效的日志被大量無(wú)意義的日志信息淹沒(méi)耗绿,查找起來(lái)非常困難。那么什么時(shí)候輸出日志呢砾隅?以下列出了一些常見(jiàn)的需要輸出日志的情況误阻,而且日志的級(jí)別基本都是>=INFO,至于Debug級(jí)別日志的使用場(chǎng)景晴埂,本節(jié)沒(méi)有專門(mén)列出究反,需要具體情況具體分析,但也是要追求“恰如其分”儒洛,不是越多越好精耐。
系統(tǒng)啟動(dòng)參數(shù)、環(huán)境變量
系統(tǒng)啟動(dòng)的參數(shù)琅锻、配置卦停、環(huán)境變量、System.Properties等信息對(duì)于軟件的正常運(yùn)行至關(guān)重要浅浮,這些信息的輸出有助于安裝配置人員通過(guò)日志快速定位問(wèn)題沫浆,所以程序有必要在啟動(dòng)過(guò)程中把使用到的關(guān)鍵參數(shù)、變量在日志中輸出出來(lái)滚秩。在輸出時(shí)需要注意专执,不是一股腦的全部輸出,而是將軟件運(yùn)行涉及到的配置信息輸出出來(lái)郁油。比如本股,如果軟件對(duì)jvm的內(nèi)存參數(shù)比較敏感,對(duì)最低配置有要求桐腌,那么就需要在日志中將-Xms -Xmx -XX:PermSize這幾個(gè)參數(shù)的值輸出出來(lái)拄显。
異常捕獲處
在捕獲異常處輸出日志,大家在基本都能做到案站,唯一需要注意的是怎么輸出一個(gè)簡(jiǎn)單明了的日志信息躬审。這在后面的問(wèn)題問(wèn)題中有進(jìn)一步說(shuō)明。
函數(shù)獲得期望之外的結(jié)果時(shí)
一個(gè)函數(shù)蟆盐,尤其是供外部系統(tǒng)或遠(yuǎn)程調(diào)用的函數(shù)承边,通常都會(huì)有一個(gè)期望的結(jié)果,但如果內(nèi)部系統(tǒng)或輸出參數(shù)發(fā)生錯(cuò)誤時(shí)石挂,函數(shù)將無(wú)法返回期望的正確結(jié)果博助,此時(shí)就需要記錄日志,日志的基本通常是warn痹愚。需要特別說(shuō)明的是富岳,這里的期望之外的結(jié)果不是說(shuō)沒(méi)有返回就不需要記錄日志了蛔糯,也不是說(shuō)返回false就需要記錄日志。比如函數(shù):isXXXXX()窖式,無(wú)論返回true蚁飒、false記錄日志都不是必須的,但是如果系統(tǒng)內(nèi)部無(wú)法判斷應(yīng)該返回true還是false時(shí)脖镀,就需要記錄日志飒箭,并且日志的級(jí)別應(yīng)該至少是warn。
關(guān)鍵操作
關(guān)鍵操作的日志一般是INFO級(jí)別蜒灰,如果數(shù)量弦蹂、頻度很高,可以考慮使用DEBUG級(jí)別强窖。以下是一些關(guān)鍵操作的舉例凸椿,實(shí)際的關(guān)鍵操作肯定不止這么多。
刪除:刪除一個(gè)文件翅溺、刪除一組重要數(shù)據(jù)庫(kù)記錄……
添加:和外系統(tǒng)交互時(shí)脑漫,收到了一個(gè)文件、收到了一個(gè)任務(wù)……
處理:開(kāi)始咙崎、結(jié)束一條任務(wù)……
……
日志輸出的內(nèi)容
ERROR:錯(cuò)誤的簡(jiǎn)短描述优幸,和該錯(cuò)誤相關(guān)的關(guān)鍵參數(shù),如果有異常褪猛,要有該異常的StackTrace网杆。
WARN:告警的簡(jiǎn)短描述,和該錯(cuò)誤相關(guān)的關(guān)鍵參數(shù)伊滋,如果有異常碳却,要有該異常的StackTrace。
INFO:言簡(jiǎn)意賅地信息描述笑旺,如果有相關(guān)動(dòng)態(tài)關(guān)鍵數(shù)據(jù)昼浦,要一并輸出,比如相關(guān)ID筒主、名稱等关噪。
DEBUG:簡(jiǎn)單描述,相關(guān)數(shù)據(jù)乌妙,如果有異常使兔,要有該異常的StackTrace。
在日志相關(guān)數(shù)據(jù)輸出的時(shí)要特別注意對(duì)敏感信息的保護(hù)冠胯,比如修改密碼時(shí),不能將密碼輸出到日志中锦针。
什么時(shí)候使用J2SE自帶的日志
我們通常使用slf4j或log4j這兩個(gè)工具記錄日志荠察,那么還需要使用J2SE的日志框架嗎置蜀?當(dāng)然需要,在我們編寫(xiě)一些通用的工具類時(shí)悉盆,為了減少對(duì)第三方的jar包的依賴盯荤,首先要考慮使用java.util.logging。
考慮到slf4j等日志框架提供了日志bridge工具焕盟,為java.util.logging提供了Handler秋秤,所以普通應(yīng)用的開(kāi)發(fā)過(guò)程中也可以考慮使用J2SE自有日志,這樣不但可以減少項(xiàng)目的編譯依賴脚翘,同時(shí)在應(yīng)用實(shí)施時(shí)可以更靈活的選擇日志的輸出工具包灼卢。
典型問(wèn)題分析
該用日志的地方不用
上圖對(duì)異常的處理直接使用e.printStackTrace()顯然是有問(wèn)題的,正確的做法是:要么通過(guò)日志方式輸出錯(cuò)誤信息来农,要么直接拋出異常鞋真,要么創(chuàng)建新的自定義異常拋出。
另:對(duì)于靜態(tài)工具類函數(shù)中的異常處理沃于,最簡(jiǎn)單的方式就是不捕獲涩咖、不記錄日志,直接向上拋出繁莹,如果認(rèn)為異常類型太多檩互,或者意義不明確,可以拋出自定義異常類的實(shí)例咨演。
啰嗦重復(fù)闸昨、沒(méi)有重點(diǎn)
首先上面不應(yīng)該有error級(jí)別的日志。
其次在日志中直接輸出e.toString()雪标,為定位問(wèn)題提供的信息太少零院。
另外需要明確一點(diǎn):日志系統(tǒng)是一個(gè)多線程公用的系統(tǒng),在兩行日志輸出之間有可能會(huì)被插入其他線程的日志記錄村刨,不會(huì)按照我們的意愿順序輸出告抄,后面有更典型的例子。
最后嵌牺,上面的日志可以簡(jiǎn)化為:
logger.debug(“從properties中...{}...{}...”,name, value, e); logger.warn(“從properties中獲取{}發(fā)生錯(cuò)誤:{}”,name, e.toString());
或者直接一句:
logger.warn(“從properties中...{}...{}...”,name, value, e);
或者更完美的:
if(logger.isDebugEnabled()){ logger.warn(“從properties中...{}...”, name, e); }else{ logger.warn(“從properties中獲取{}發(fā)生錯(cuò)誤:{}”, name, e.toString()); }
日志和異常處理的關(guān)系
首先上面的日志信息不夠充分打洼,級(jí)別定義不夠恰當(dāng)。
另外逆粹,既然將異常捕獲并記錄的日志募疮,就不應(yīng)該重新將一個(gè)一模一樣的異常再次拋出去了。如果將異常再次拋出僻弹,那在上層肯定還需要對(duì)該異常進(jìn)行處理阿浓,并記錄日志,這樣就重復(fù)了蹋绽。如果沒(méi)有特別原因芭毙,此處不應(yīng)該捕獲異常筋蓖。
System.out方式的日志
上面的日志形式十分隨意,只適合臨時(shí)的代碼調(diào)試退敦,不允許提交到正式的代碼庫(kù)中粘咖。
對(duì)于臨時(shí)調(diào)試日志,建議在日志的輸出信息中添加一些特殊的連續(xù)字符侈百,也可以用自己的名稱瓮下、代號(hào),這樣可以在調(diào)試完畢后钝域,提交代碼之前讽坏,方便地找到所有臨時(shí)代碼,一并刪除网梢。
日志信息不明確
上面的“添加任務(wù)出錯(cuò)震缭。。战虏〖鹪祝”既沒(méi)有記錄任務(wù)id,也沒(méi)有任務(wù)名稱烦感,軟件部署后發(fā)現(xiàn)錯(cuò)誤后巡社,根據(jù)該日志記錄不能確認(rèn)哪一條任務(wù)錯(cuò)誤,給進(jìn)一步的分析原因帶來(lái)困難手趣。
另外第二個(gè)紅圈中的問(wèn)題有:要使用參數(shù)晌该;一行日志就可以了。
還有一些其他共性的錯(cuò)誤绿渣,就不多說(shuō)了朝群。
忘記日志輸出是多線程公用的
如果有另外一個(gè)線程正在輸出日志,上面的記錄就會(huì)被打斷中符,最終顯示輸出和預(yù)想的就會(huì)不一致姜胖。正確的做法應(yīng)是將這些信息放到一行,如果需要換行可以考慮使用“\r”淀散,如果內(nèi)容較多右莱,考慮增加if (logger.isDebugEnabled())進(jìn)行判斷。而第二個(gè)例子中的輸出有System.out的習(xí)慣档插,相關(guān)內(nèi)容應(yīng)該一行完成慢蜓。
多個(gè)參數(shù)的處理
對(duì)于多參的日志輸出,可以考慮:
public void debug(String format, Object... arguments);
但是在使用多參時(shí)郭膛,會(huì)創(chuàng)建一個(gè)對(duì)象數(shù)組晨抡,也會(huì)有一定的消耗,為此,在對(duì)性能敏感的場(chǎng)景耘柱,可以增加對(duì)日志級(jí)別的判斷圆雁。