論如何打印日志

博客原文

日志滿天飛總是不好的,代碼是程序員的藝術(shù)品,日志也是其中一部分。

寫在前面的話

程序員在程序中打印日志的目的一般有兩個:定位問題顯示程序運行狀態(tài)。試想有那么一個夜晚,突然手機響了,美夢中驚醒,報警短信,然后不到兩分鐘leader電話來了,你用快速又嫻熟的動作爬起來,打開電腦當你打開日志文件準備查問題的時候,傻了,這日志誰寫的,問題被雜亂的日志埋葬了,一時急火攻心,終于通過對照源代碼和日志,在數(shù)小時之后解決了問題??梢娙罩镜闹匾浴?/p>

其實好的日志打印方式還有一個作用,能實現(xiàn)一些業(yè)務(wù)運營方面的統(tǒng)計工作,比如你在每個操作的關(guān)鍵位置都有對當前用戶的操作記下一條日志,這樣就能統(tǒng)計出,該業(yè)務(wù)功能走到每一步的用戶數(shù),從而能分辨出是在哪一步損失了用戶,從而做相應(yīng)的業(yè)務(wù)流程的優(yōu)化。

可能大家認為日志不管咋打印都不會影響功能邏輯,拋開影響系統(tǒng)性能不說,這有點高級了,其實從上面的話中也可以看出,日志其實很重要的,所以下面我將從幾個方面討論一下如何打印日志,以便理清我自己的思路,同時提醒程序員同行們,這樣后面維護你的代碼的程序員,少一點吃屎,更少一點對你的咒罵,為和諧美好的程序員天堂添磚加瓦(好偉大呀,哈哈)。

目錄:

1. 方便定位問題的日志如何打
2. 顯示程序運行狀態(tài)的日志如何打
3. 可以用作運營統(tǒng)計的日志如何打
4. 什么樣的日志不應(yīng)該打

1. 方便定位問題的日志

這是日志的最主要的用途。

  • 日志明確標志屬于哪次請求
    打印的每一條日志中盡量包含請求流水號,以便區(qū)分這條日志是屬于哪次請求的日志,日志樣例:
log.warn("賬戶創(chuàng)建和升級功能,無法識別操作類型(0:創(chuàng)建,1:升級),請求流水號:{},當前值:{}" , flowNo, operType);

如果獲取不到流水號,則需要打印明確能縮小日志歸屬范圍的字段信息,如用戶號、協(xié)議號、手機號等。

  • 在對外提供的接口入口處打印一條日志
    打印接口的唯一標識和中文(如果你English enough可以打英文)簡短描述,并且要將調(diào)用方傳入的參數(shù)原樣打印出來。這樣當系統(tǒng)出現(xiàn)問題時,你就能很容易的判斷出是否是調(diào)用方出現(xiàn)了問題。日志樣例:
log.info("addUpAccount&賬戶創(chuàng)建和升級接口,外部系統(tǒng)入?yún)ⅲ簕}" , JSONObject.toJSONString(request));
  • 在調(diào)用其它系統(tǒng)接口的前后各打印一條日志
    打印所調(diào)用接口的系統(tǒng)名稱/接口名稱和傳入?yún)?shù)/響應(yīng)參數(shù),這樣能方便做問題定界,通過這兩條日志可以清楚地看出是否是所調(diào)用的系統(tǒng)出現(xiàn)了問題,傳染到我了。日志樣例:
log.info("調(diào)用cif業(yè)務(wù)訂購,bcc入?yún)ⅲ簕}"  , JSONObject.toJSONString(request));
log.info("調(diào)用cif業(yè)務(wù)訂購,cif響應(yīng):{}"  , JSONObject.toJSONString(cifResponse));
  • 不符合業(yè)務(wù)邏輯預(yù)期打印一條日志
    打印關(guān)鍵的參數(shù),要能從這些參數(shù)中清楚地看出,誰的操作與預(yù)期不符,為什么與預(yù)期不符。并且唯一定位到這條日志,要包含用戶id或者流水號。日志樣例:
// 校驗重復(fù)簽約
int count = agreementService.countSignAndPre(externalUserId, userInfo.getUserId(), agreementType);
if (count == 1) {
    // 已經(jīng)簽約過,不允許重復(fù)簽約
    log.warn("簽約失敗,不允許重復(fù)簽約!用戶信息:{},協(xié)議類型:{},來源系統(tǒng):{},外部系統(tǒng)用戶號:{}",
        JSONObject.toJSONString(userInfo), agreementType, systemCode, externalUserId);
}
  • 業(yè)務(wù)處理出現(xiàn)異常打印一條日志
    主要打印異常信息和業(yè)務(wù)邏輯描述。日志樣例:
try {
    //業(yè)務(wù)邏輯處理
} catch (BizException e) {
    log.warn("賬戶升級過程中出現(xiàn)業(yè)務(wù)異常", e);
}catch (Exception e) {
    log.error("賬戶升級過程中出現(xiàn)無法識別的異常", e);
}
  • 極少出現(xiàn)的else情況打印一條日志
    打印業(yè)務(wù)功能描述和該極少出現(xiàn)的參數(shù)值,還有唯一定位一次請求的請求流水號。日志樣例:
if("0".equals(operType)){ 
    //創(chuàng)建賬戶
}else if("1".equals(operType)){ 
    //升級賬戶
}else{
    log.warn("賬戶創(chuàng)建和升級功能,無法識別操作類型(0:創(chuàng)建,1:升級),請求流水號:{},當前值:{}" , flowNo, operType);
}

2. 顯示程序運行狀態(tài)的日志

系統(tǒng)程序日復(fù)一日/月復(fù)一月/年復(fù)一年的運行著,查看系統(tǒng)的運行狀態(tài)是必不可少的,比如一些可能和耗時的業(yè)務(wù)處理,批處理,IO操作等。

  • 耗時(可能出現(xiàn)超時)業(yè)務(wù)邏輯處理時間打印一條日志
    打印業(yè)務(wù)邏輯描述和處理時間。日志樣例:
long startTime = System.currentTime();  
//業(yè)務(wù)邏輯處理
log.info("綁定銀行卡總耗時 : "  +  (System.currentTime() - startTime) + "ms"); 
  • 批量處理大量數(shù)據(jù)時顯示處理進度打印n條日志
    在夜間做對賬處理時,一般都是批量處理大量數(shù)據(jù),這是應(yīng)打印的日志包括:對賬功能描述和處理進度,每處理1000(視情況而定)條打印一條。日志樣例:
log.info("業(yè)務(wù)開通狀態(tài)對賬功能處理中,總數(shù)據(jù)量:{},當前處理到:{}", total, current);

3. 可用作運營統(tǒng)計的日志

從業(yè)務(wù)上來說,一個大的功能往往是由多個小功能組成,這些大的功能組成一個完整的系統(tǒng)。比如一個系統(tǒng)包括注冊用戶/開通某項業(yè)務(wù)/綁定銀行卡/消費下單,從而達到一個電商類系統(tǒng)的最終目的。針對這個系統(tǒng)運營人員經(jīng)常會要一些數(shù)據(jù)如多少注冊用戶,多少開通業(yè)務(wù)用戶,多少綁卡用戶,多少下過單的用戶,如果是這樣的粗粒度的統(tǒng)計需求,可能寫幾條sql摟一下數(shù)據(jù)庫就搞定了。但是如果需要更加細致統(tǒng)計,如綁卡功能分為這樣幾步:用戶實名校驗,銀行卡校驗,發(fā)送短信驗證碼,校驗短信驗證碼,簽協(xié)議綁卡。日志樣例:

...
log.info("用戶綁卡,實名校驗,用戶信息:{}", JSONObject.toJSONString(userInfo));
...
log.info("用戶綁卡,銀行卡校驗,用戶信息:{}", JSONObject.toJSONString(userInfo));
...
log.info("用戶綁卡,發(fā)送短信驗證碼,用戶信息:{}", JSONObject.toJSONString(userInfo));
...
log.info("用戶綁卡,校驗短信驗證碼,用戶信息:{}", JSONObject.toJSONString(userInfo));
...
log.info("用戶綁卡,簽協(xié)議綁卡,用戶信息:{}", JSONObject.toJSONString(userInfo));
...

通過后臺日志可以做到細粒度的運營統(tǒng)計分析,當然還有其他更專業(yè)的技術(shù)實現(xiàn)這種運營分析的功能,這種方法可以作為一個過渡期的方法。只要有一個好的打印日志的習慣,通過大數(shù)據(jù)的分析,日志還有其它的價值。

4. 不應(yīng)該打印的日志

  • 無意義的日志
    日志樣例:
log.info("調(diào)用客戶系統(tǒng)開始...");
...
log.info("調(diào)用客戶系統(tǒng)結(jié)束...");
...
log.info("用戶簽約失敗...");

當我們看到這樣的日志的時候,沒有包含任何有意義的信息。調(diào)用開始,我想知道誰調(diào)用的,入?yún)⑹巧?;調(diào)用結(jié)束,我想知道調(diào)用結(jié)果,出參是啥,調(diào)用成功還是失??;同樣,用戶簽約失敗,我想知道的是哪個用戶失敗,為什么失??;顯然都沒展示,所以這樣的日志是沒有任何意義的。

  • 混淆信息的日志
    所打印出來的日志都應(yīng)該是清楚準確的表達,當你不能確定具體原因的時候,不能在日志中只是展示一種可能的情況,這樣會影響對問題的判斷,這樣的日志千萬不能打。日志樣例:
Connection connection = ConnectionFactory.getConnection();  
if (connection == null) {  
    log.warn("System initialized unsuccessfully");  
}  

結(jié)束lala

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

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

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