LOG日志詳解

歡迎關(guān)注作者簡書
csdn傳送門

LOG日志詳解

參考文檔

Log的用途

問題追蹤:通過日志不僅僅包括我們程序的一些bug,也可以在安裝配置時(shí),通過日志可以發(fā)現(xiàn)問題。

狀態(tài)監(jiān)控:通過實(shí)時(shí)分析日志,可以監(jiān)控系統(tǒng)的運(yùn)行狀態(tài),做到早發(fā)現(xiàn)問題、早處理問題。

安全審計(jì):審計(jì)主要體現(xiàn)在安全上,通過對日志進(jìn)行分析,可以發(fā)現(xiàn)是否存在非授權(quán)的操作。

記錄Log的基本原則

日志的級別劃分

Java日志通??梢苑譃椋篹rror、warn、info、debug、trace五個(gè)級別。在J2SE中預(yù)定義的級別更多,分別為:SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST。

日志對性能的影響

不管是多么優(yōu)秀的日志工具,在日志輸出時(shí)總會對性能產(chǎn)生或多或少的影響,為了將影響降低到最低,有以下幾個(gè)準(zhǔn)則需要遵守:

如何創(chuàng)建Logger實(shí)例:創(chuàng)建Logger實(shí)例有是否static的區(qū)別,在log4j的早期版本,一般要求使用static,而在高版本以及后來的slf4j中,該問題已經(jīng)得到優(yōu)化,獲取(創(chuàng)建)logger實(shí)例的成本已經(jīng)很低。所以我們要求:對于可以預(yù)見的多數(shù)情況下單例運(yùn)行的class,可以不添加static前綴;對于可能是多例居多,尤其是需要頻繁創(chuàng)建的class,我們要求要添加static前綴。

判斷日志級別:
n對于可以預(yù)見的會頻繁產(chǎn)生的日志輸出,比如for、while循環(huán),定期執(zhí)行的job等,建議先使用if對日志級別進(jìn)行判斷后再輸出。
n對于日志輸出內(nèi)容需要復(fù)雜的序列化,或輸出的某些信息獲取成本較高時(shí),需要對日志級別進(jìn)行判斷。比如日志中需要輸出用戶名,而用戶名需要在日志輸出時(shí)從數(shù)據(jù)庫獲取,此時(shí)就需要先判斷一下日志級別,看看是否有必要獲取這些信息。

優(yōu)先使用參數(shù),減少字符串拼接:使用參數(shù)的方式輸出日志信息,有助于在性能和代碼簡潔之間取得平衡。當(dāng)日志級別限制輸出該日志時(shí),參數(shù)內(nèi)容將不會融合到最終輸出中,減少了字符串的拼接,從而提升執(zhí)行效率。

什么時(shí)候輸出日志

日志并不是越多越詳細(xì)就越好。在分析運(yùn)行日志,查找問題時(shí),我們經(jīng)常遇到該出現(xiàn)的日志沒有,無用的日志一大堆,或者有效的日志被大量無意義的日志信息淹沒,查找起來非常困難。那么什么時(shí)候輸出日志呢?以下列出了一些常見的需要輸出日志的情況,而且日志的級別基本都是>=INFO,至于Debug級別日志的使用場景,本節(jié)沒有專門列出,需要具體情況具體分析,但也是要追求“恰如其分”,不是越多越好。

系統(tǒng)啟動(dòng)參數(shù)、環(huán)境變量

系統(tǒng)啟動(dòng)的參數(shù)、配置、環(huán)境變量、System.Properties等信息對于軟件的正常運(yùn)行至關(guān)重要,這些信息的輸出有助于安裝配置人員通過日志快速定位問題,所以程序有必要在啟動(dòng)過程中把使用到的關(guān)鍵參數(shù)、變量在日志中輸出出來。在輸出時(shí)需要注意,不是一股腦的全部輸出,而是將軟件運(yùn)行涉及到的配置信息輸出出來。比如,如果軟件對jvm的內(nèi)存參數(shù)比較敏感,對最低配置有要求,那么就需要在日志中將-Xms -Xmx -XX:PermSize這幾個(gè)參數(shù)的值輸出出來。

異常捕獲處

在捕獲異常處輸出日志,大家在基本都能做到,唯一需要注意的是怎么輸出一個(gè)簡單明了的日志信息。這在后面的問題問題中有進(jìn)一步說明。

函數(shù)獲得期望之外的結(jié)果時(shí)

一個(gè)函數(shù),尤其是供外部系統(tǒng)或遠(yuǎn)程調(diào)用的函數(shù),通常都會有一個(gè)期望的結(jié)果,但如果內(nèi)部系統(tǒng)或輸出參數(shù)發(fā)生錯(cuò)誤時(shí),函數(shù)將無法返回期望的正確結(jié)果,此時(shí)就需要記錄日志,日志的基本通常是warn。需要特別說明的是,這里的期望之外的結(jié)果不是說沒有返回就不需要記錄日志了,也不是說返回false就需要記錄日志。比如函數(shù):isXXXXX(),無論返回true、false記錄日志都不是必須的,但是如果系統(tǒng)內(nèi)部無法判斷應(yīng)該返回true還是false時(shí),就需要記錄日志,并且日志的級別應(yīng)該至少是warn。

關(guān)鍵操作

關(guān)鍵操作的日志一般是INFO級別,如果數(shù)量、頻度很高,可以考慮使用DEBUG級別。以下是一些關(guān)鍵操作的舉例,實(shí)際的關(guān)鍵操作肯定不止這么多。

刪除:刪除一個(gè)文件、刪除一組重要數(shù)據(jù)庫記錄……

添加:和外系統(tǒng)交互時(shí),收到了一個(gè)文件、收到了一個(gè)任務(wù)……

處理:開始、結(jié)束一條任務(wù)……

……

日志輸出的內(nèi)容

ERROR:錯(cuò)誤的簡短描述,和該錯(cuò)誤相關(guān)的關(guān)鍵參數(shù),如果有異常,要有該異常的StackTrace。

WARN:告警的簡短描述,和該錯(cuò)誤相關(guān)的關(guān)鍵參數(shù),如果有異常,要有該異常的StackTrace。

INFO:言簡意賅地信息描述,如果有相關(guān)動(dòng)態(tài)關(guān)鍵數(shù)據(jù),要一并輸出,比如相關(guān)ID、名稱等。

DEBUG:簡單描述,相關(guān)數(shù)據(jù),如果有異常,要有該異常的StackTrace。

在日志相關(guān)數(shù)據(jù)輸出的時(shí)要特別注意對敏感信息的保護(hù),比如修改密碼時(shí),不能將密碼輸出到日志中。

什么時(shí)候使用J2SE自帶的日志

我們通常使用slf4j或log4j這兩個(gè)工具記錄日志,那么還需要使用J2SE的日志框架嗎?當(dāng)然需要,在我們編寫一些通用的工具類時(shí),為了減少對第三方的jar包的依賴,首先要考慮使用java.util.logging。

考慮到slf4j等日志框架提供了日志bridge工具,為java.util.logging提供了Handler,所以普通應(yīng)用的開發(fā)過程中也可以考慮使用J2SE自有日志,這樣不但可以減少項(xiàng)目的編譯依賴,同時(shí)在應(yīng)用實(shí)施時(shí)可以更靈活的選擇日志的輸出工具包。

典型問題分析

該用日志的地方不用

上圖對異常的處理直接使用e.printStackTrace()顯然是有問題的,正確的做法是:要么通過日志方式輸出錯(cuò)誤信息,要么直接拋出異常,要么創(chuàng)建新的自定義異常拋出。

另:對于靜態(tài)工具類函數(shù)中的異常處理,最簡單的方式就是不捕獲、不記錄日志,直接向上拋出,如果認(rèn)為異常類型太多,或者意義不明確,可以拋出自定義異常類的實(shí)例。

啰嗦重復(fù)、沒有重點(diǎn)

首先上面不應(yīng)該有error級別的日志。

其次在日志中直接輸出e.toString(),為定位問題提供的信息太少。

另外需要明確一點(diǎn):日志系統(tǒng)是一個(gè)多線程公用的系統(tǒng),在兩行日志輸出之間有可能會被插入其他線程的日志記錄,不會按照我們的意愿順序輸出,后面有更典型的例子。

最后,上面的日志可以簡化為:

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)系

首先上面的日志信息不夠充分,級別定義不夠恰當(dāng)。

另外,既然將異常捕獲并記錄的日志,就不應(yīng)該重新將一個(gè)一模一樣的異常再次拋出去了。如果將異常再次拋出,那在上層肯定還需要對該異常進(jìn)行處理,并記錄日志,這樣就重復(fù)了。如果沒有特別原因,此處不應(yīng)該捕獲異常。

System.out方式的日志

上面的日志形式十分隨意,只適合臨時(shí)的代碼調(diào)試,不允許提交到正式的代碼庫中。

對于臨時(shí)調(diào)試日志,建議在日志的輸出信息中添加一些特殊的連續(xù)字符,也可以用自己的名稱、代號,這樣可以在調(diào)試完畢后,提交代碼之前,方便地找到所有臨時(shí)代碼,一并刪除。

日志信息不明確

上面的“添加任務(wù)出錯(cuò)。。?!奔葲]有記錄任務(wù)id,也沒有任務(wù)名稱,軟件部署后發(fā)現(xiàn)錯(cuò)誤后,根據(jù)該日志記錄不能確認(rèn)哪一條任務(wù)錯(cuò)誤,給進(jìn)一步的分析原因帶來困難。

另外第二個(gè)紅圈中的問題有:要使用參數(shù);一行日志就可以了。

還有一些其他共性的錯(cuò)誤,就不多說了。

忘記日志輸出是多線程公用的

如果有另外一個(gè)線程正在輸出日志,上面的記錄就會被打斷,最終顯示輸出和預(yù)想的就會不一致。正確的做法應(yīng)是將這些信息放到一行,如果需要換行可以考慮使用“\r”,如果內(nèi)容較多,考慮增加if (logger.isDebugEnabled())進(jìn)行判斷。而第二個(gè)例子中的輸出有System.out的習(xí)慣,相關(guān)內(nèi)容應(yīng)該一行完成。

多個(gè)參數(shù)的處理

對于多參的日志輸出,可以考慮:

public void debug(String format, Object... arguments);

但是在使用多參時(shí),會創(chuàng)建一個(gè)對象數(shù)組,也會有一定的消耗,為此,在對性能敏感的場景,可以增加對日志級別的判斷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容