前言
大家好,我是 JavaPub。日誌是我們定位問題的得力助手,也是我們團隊間協作溝通(甩鍋)、明確責任歸屬(撕B)的利器。沒有日誌的程式執行起來就如同脫韁的野🐎。列印日誌非常重要。今天我們來聊聊日誌列印的 N 個好建議~
選擇合適的日誌等級
在開發中我們有常見的四種日誌列印等級,debug、info、warn、error,要選擇合適的等級列印,不要上來直接 info。
-
error: 錯誤日誌,指比較嚴重的問題,會對系統和有業務造成傷害。運維監控重點關注。
-
warn: 警告日誌,不會對系統執行造成大的影響,一般由開發人員關注。
-
info: 關鍵日誌,為了保留系統執行關鍵指標,比如函式的入參、出參,時間等資訊。
-
debug: 開發日誌,在開發除錯階段,記錄物件資料在關鍵處理步驟中的變化情況、快速定位。
要列印函式的入參、出參
記錄日誌並不是要把所有資訊都記錄下來,那日誌儲存就要大到上天。我們只記錄關鍵有效的日誌,有效日誌才是 battle 🆚 時殺手鐧。
哪些算是有效日誌?比如函式的入口處,列印入參,還包括使用者唯一標識 (uid)、鏈路標識 (traceId) 等。函式出口列印返回值及時間等。
public String GetName(Request req, Integer id){
log.debug("method start param: {}", req.UserID);
String name = "JavaPub";
log.debug("method end result: {}", name);
return name;
}
列印日誌物件要做判空處理,避免阻斷流程
為了列印一行日誌,程式寫掛了。空指標異常在任何程式碼中都是最常見的異常之一。
反例:當 book 物件是 NULL 的話,這行日誌就會拋空指標異常。
public void doSome(Book book){
log.info("do do and print log: {}". book.getName());
// do something...
...
}
不要使用日誌系統的(Log4j、Logback),要使用 Slf4j
Slf4j 是使用門面模式的日誌框架,可以解耦具體的日誌實現。可以在不修改程式碼的情況下,更換底層的日誌框架。
正例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(JavaPub.class);
對低階別的日誌輸出,必須進行日誌級別開關判斷
對於 trace、debug、info 這些比較低的日誌級別,必須進行日誌級別開關。
正例:
開關判斷邏輯通常放在日誌工具類中。
public void doSomething(){
User user = new User(1, "技術自媒體", "JavaPub");
if (logger.isDebugEnabled()) {
logger.debug("print debug log. 666 is {}", user.getName());
}
}
反例:
public void doSth(){
String name = "JavaPub";
logger.trace("print debug log" + name);
logger.debug("print debug log" + name);
logger.info("print info log" + name);
// 業務邏輯
...
}
當日志級別是 warn 時,以上日誌不會列印,但是會執行字串拼接操作,如果列印值是物件的話,還會執行 toString()
方法,浪費了系統資源,因此建議加上日誌開關判斷。
不要用e.printStackTrace()列印日誌
反例:
public void doSomething(){
try{
// 業務程式碼
...
} catch (Exception e){
e.printStackTrace();
}
}
-
e.printStackTrace()
列印出的日誌包含堆疊資訊,導致我們的日誌資訊不規整、增加定位問題的難度。如果使用 ELK 分析日誌也會非常困難。 -
e.printStackTrace()
語句產生的字串記錄的是堆疊資訊,如果資訊太長太多,字串常量池所在的記憶體塊沒有空間了,即記憶體滿了,系統請求也將被阻塞。
正例:
public void doSomething(){
try{
// 業務邏輯
...
} catch (Exception e){
log.error("程式異常 failed", e);
}
}
列印全部的異常資訊,方便定位問題
反例:
沒有列印系統異常 e
,無法定位出現了什麼型別的異常。
public void doSth(){
try{
// 業務邏輯
...
} catch (Exception e){
log.error("發生了一個異常");
}
}
不要列印重複日誌
在巢狀邏輯程式碼中列印重複日誌,增加系統資源消耗佔用。
反例:
public void doSomething(String s){
log.info("do something and print log: {}", s);
doSubSomething(s);
}
private void doSubSomething(String s){
log.info("do sub something and print log: {}", s);
// 寫點業務邏輯
...
}
正例:
應該直接刪掉或者將為 debug 日誌級別。
日誌儘量使用英文
反例:
建議:儘量在列印時日誌時輸出英文,防止中文編碼與終端不一致導致列印出現亂碼,對排查故障造成感染。
核心業務邏輯,在每個分支首行都列印日誌
在編寫核心業務邏輯程式碼時,遇到 if...else 或者 switch 這樣的分支條件,在行首列印日誌,透過日誌可以快速排查定位異常。
public void doSomething(){
if(user.isVip()){
log.info("該使用者是 JavaPub 會員,Id:{},開始處理會員邏輯",user,getUserId());
// TODO 會員邏輯
}else{
log.info("該使用者是非會員,Id:{},開始處理非會員邏輯",user,getUserId())
// TODO 非會員邏輯
}
}
不要列印無意義的日誌(不攜帶上下文、日誌鏈路 id)
反例:
不攜帶任何業務資訊的日誌,對故障排查意義不大。
public void doSomething(){
log.info("do something and print log. i am NB");
// TODO 業務邏輯
...
}
正例:
- 日誌一定要攜帶業務資訊相關內容,有利於快速定位問題原因
public void doSomething(Request req, User user){
log.info("do something and print log, id={}, trace_id={}", user.GetId, req.GetTraceId);
// TODO 業務邏輯
...
}
如何列印日誌呢?總的來說不要讓你的程式在黑盒總執行,列印關鍵資訊、保證在出現異常時透過日誌快速定位到那裡就可以啦。