Java編碼常見的Log日志打印問題


前言

    本文總結了作者在Java代碼檢視中遇到的一些關於日志打印的問題,並給出修改建議。因能力有限,難免存在錯漏,歡迎指正。

 

. 不規范的異常打印

    使用slf4j日志組件時,logger.error(與log.warn)接受Throwable參數,以打印異常名和詳細的堆棧信息(可能內部調用e.printStackTrace())。

    但書寫打印語句時,需要注意格式。例如:

1 logger.error("Best print: ", e);
2 logger.error("Good print: {}", e); //a.
3 logger.error("Bad print: " + e);   //b. 或 + e.toString()
4 logger.error("Bad print: " + e.getMessage()); //c. 或: {}", e.getMessage())

    a句仍可打印異常名和堆棧信息,但多輸出一對花括號"{}";b句僅打印異常名;c句打印異常消息字符串。以空指針異常(Runtime異常)為例,b句打印"java.lang.NullPointerException",c句打印"null"(過於簡陋)。

    可使用如下正則表達式排查Java代碼里不規范的異常打印:

^\s*[Ll][Oo][Gg](ger|GER)*\.(error|warn)\("(.+?)"\s*\+\s*(e|ex|e.getMessage\(\))\s*\);.*

    該正則表達式可排查出形如上文b、c句的打印缺陷。考慮到實際代碼書寫風格的差異,會存在少量的漏判和誤判。此外應注意,某些異常(如SQL或IO異常)可能泄露敏感信息,打印異常堆棧之前應根據網絡安全要求做必要的”加工”。

 

二. 不規范的變量打印

    使用slf4j日志組件打印變量時,建議使用”{}”占位符風格(C風格),而不是”+”拼接符(C++風格)。例如:

1 logger.error("Country: {}, Province: {}, City: {}", ctry, prov, city); //占位符
2 logger.error("Country: "+ctry+", Province:" +prov +", City: "+city);   //拼接符

    顯然,占位符風格更直觀,而且不容易出現筆誤——上面的拼接符語句有問題,誰能發現?

    但最常見的變量打印問題,是打印時未使用占位符或拼接符:

1 logger.error("print cannotBePrinted: ", cannotBePrinted);  //Wrong
2 logger.error("print canBePrinted: " + canBePrinted);  //Good
3 logger.error("print canBePrinted: {}", canBePrinted); //Better

    注意,cannotBePrinted或canBePrinted為非Throwable的變量。亦即,異常變量e不適用上述規則,但e.getMessage()適用。

    可使用如下正則表達式排查Java代碼里不規范的變量打印:

^\s*[Ll][Oo][Gg](ger|GER)*\.[a-zA-Z]{4,5}\("([^({})]+?)"\s*\,\s*([^e\s]|\w{2,})\s*\);.*

    考慮到實際代碼書寫風格的差異,該正則表達式會存在漏判和誤判。例如,"logger.error("Best print: ", ex);"會被誤判。

 

. 余的DEBUG級別判斷

    使用slf4j日志組件以Debug級別打印時,debug()方法內部會調用isDebugEnabled()判斷調試級別。因此,下述代碼里的判斷沒有必要:

if(logger.isDebugEnabled())
{
    logger.debug("handleChanges end:{}:{}(ms) ", new Object[] {changes, useTime});
}

 

四. 日志對象logger的聲明

    一般建議日志對象logger聲明為private static final。聲明為private可防止logger對象被其他類非法使用。聲明為static可防止重復new出logger對象,造成資源浪費;還可以防止logger被序列化,造成安全風險。聲明為final是因為在類的生命周期內無需變更logger。

    然而,實際編碼中可根據使用場景稍作變通。例如,static final成員變量通常要求大寫(如LOGGER),如果覺得大寫別扭,可以去除final修飾。又如,期望logger對象可復用時,可如下定義:

protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    這樣,子類可以直接使用繼承來的logger對象打印輸出,而無需再次getLogger()獲取日志對象。

 

五. 日志級別應合理

    如果日志不分級別或級別不合理,則定位問題時就無法快速有效地屏蔽大量低級別信息,給快速定位帶來難度。建議與具體實現有關的日志使用debug級,一般的業務處理日志用info級,不影響業務進行的錯誤用warn級,而記錄異常或重要錯誤的日志應為error級。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM