萬萬沒想到! logger.info() 還能導致線上故障?

來源 | 阿飛的博客

事故代碼

直入主題,生產環境日誌級別爲warn,請看如下這行代碼:

LOGGER.info("the DTO info: {}", JSON.toJSONString(DTO));

先做個小調查,你覺得這段代碼會不會有問題。

如果你的答案爲“有問題”,並且你有充足的理由,那麼這篇文章已經沒有往下看的必要了,因爲你已經掌握了筆者此文要傳達的知識點。如果你的答案爲“沒有問題”或者“無法分辨”,那麼,請繼續往下看。

原因分析

這段代碼主要有兩個需要注意的地方:

  1. 日誌級別爲info,而線上環境是warn級別。我們可以得出結論,線上環境肯定不會輸出這行日誌。
  2. 打印日誌的行爲中有JSON序列化動作。

第二點是此文的關鍵。我們假設DTO是一個很小的對象,JSON序列化時間以及開銷可以忽略不計,那麼這行代碼依然沒有問題。但是,如果DTO是一個很大的對象,比如10k,甚至100k,即使快如fastjson,其耗時依然高達數百毫秒,並且非常消耗CPU。如果是在高併發的系統中,這麼大的開銷完全不可接受,甚至可能就會拖垮整個系統。

有同學就會說了,我不是info日誌麼,爲什麼還會執行這行代碼?請繼續往下看。我們首先看一下slf4j中logger.info()這個方法是如何申明的:第二個參數爲Object類型。我們的代碼中傳遞給第二個參數的值爲:JSON.toJSONString(DTO),很明顯這行代碼是傳遞一個String類型的字段給Object arg。那麼String如何來呢?答案也很明顯,必須先執行JSON序列化才能得到String。那麼logger.info這個info在什麼時候起作用呢?答案是它只能在輸出日誌這個動作時起作用:

public void info(String format, Object arg);

解決方案

如何解決這個問題?很簡單,在輸入日誌時加個級別判斷(需要說明的是,這種規範很容易被忽略,比如項目成員更替時,很容易引入有問題的代碼。所以筆者寫了一段腳本:掃描所有Java代碼,如果logger.info()中有JSON序列化動作,那麼必須判斷優先級後才能輸出日誌。即可以簡單的認爲它的前一行代碼必須是logger.isInfoEnabled()。如果你的項目有CICD環境,那麼把這段腳本集成到掃描規範中,纔是解決這個問題最完美的方案):

if(LOGGER.isInfoEnabled()) {
    LOGGER.info("the DTO info: {}", JSON.toJSONString(DTO));
}

當然,需要說明的是,通過上面的分析,如果我們的打印日誌那行代碼中沒有JSON序列化等耗時動作的話,那麼日誌級別判斷就沒必要了,比如下面這行代碼:

String reqId = "...";
String msg = "...";
LOGGER.info("the DTO info: {}", msg);

END -


往期推薦

幹掉菜鳥?微信又推出新功能:一鍵寄快遞

IntelliJ IDEA 2021.2 正式發佈了!

如何從 100 億 URL 中找出相同的 URL?

CEO不當了,CTO也不做了!我要回去寫代碼,這纔是我所熱愛的!

用谷歌搜索技術問題一定比用百度好?也未必...



喜歡本文歡迎轉發,關注我訂閱更多精彩

關注我回復「加羣」,加入Spring技術交流羣

本文分享自微信公衆號 - 程序猿DD(didispace)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章