掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
?有在用,但是大多是 logback 和 log4j 2.x。雖然異步日志的效率 logback 和 log4j 2.x 相差無幾,但 log4j 2.x 仍有些微弱的優(yōu)勢。

創(chuàng)新互聯(lián)公司主營南鄭網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,重慶App定制開發(fā),南鄭h5重慶小程序開發(fā)搭建,南鄭網(wǎng)站營銷推廣歡迎南鄭等地區(qū)企業(yè)咨詢
可以看到,Java 中是存在多種不同日志框架的實(shí)現(xiàn)的,這就會造成 2 個問題:
多框架協(xié)作:在一個項目中,不光有你的代碼,還有各種各樣的框架代碼,比如分布式協(xié)調(diào)會用到 Zookeeper、Curator;RPC 通信會用到 Dubbo、Thrift。為了方便開發(fā),業(yè)務(wù)系統(tǒng)中往往集成了許多第三方框架。我們需要日志來了解各個第三方框架之間的協(xié)作狀態(tài),這些第三方框架又依賴于各個日志框架進(jìn)行輸出。這時候如果直接使用像 logback、log4j 這樣的日志框架,豈不是業(yè)務(wù)系統(tǒng)要接入每個日志框架?
不同框架競爭:如果要引入多個日志框架,我們還需要考慮各個框架的輸出位置。要是多個日志框架同時寫入一個日志文件,還會涉及競爭問題,導(dǎo)致性能無法發(fā)揮。
由此就出現(xiàn)了面向接口的日志框架,它提供了統(tǒng)一的 API。開發(fā)人員在編寫代碼的時候,直接使用這套面向接口的日志框架,當(dāng)業(yè)務(wù)項目人員在使用時,只需要選擇好實(shí)現(xiàn)框架,就可以統(tǒng)一日志實(shí)現(xiàn)框架。
目前使用最為廣泛的日志接口框架是 SLF4J,出自 logback 的開發(fā)者,目前基本已經(jīng)形成規(guī)范。SLF4J 提供了動態(tài)占位符的功能,大大提高了程序的性能,無須開發(fā)人員再對參數(shù)信息進(jìn)行拼接。
比如默認(rèn)情況下程序是 info 級別的,在原先的代碼方式中想要進(jìn)行日志輸出需要自行拼接字符串:
logger.debug("用戶" + userId + "開始下單:" + orderNo + ",請求信息:" + Gson.toJson(req));這會產(chǎn)生一個問題,系統(tǒng)中如果存在大量類似的代碼,同時系統(tǒng)只輸出 info 及 info 以上級別的日志,那么,在輸出 debug 日志時會產(chǎn)生大量的字符串,而并不會輸出 debug 日志,最后造成字符串不停地拼接,浪費(fèi)系統(tǒng)性能。此時,SLF4J 就可以使用占位符的功能編寫日志,比如像下面這樣:
logger.debug("用戶{}開始下單:{},請求信息:", userId, orderNo, Gson.toJson(req));通過這樣的形式,SLF4J 就可以根據(jù)日志等級判斷,只對符合要求的日志進(jìn)行數(shù)據(jù)拼接和打印。
有些時候日志輸出需要進(jìn)行數(shù)值計算,或者 JSON 轉(zhuǎn)換,此時就需要一定的計算任務(wù)。但方法調(diào)用如果被當(dāng)作參數(shù)傳遞的話一樣會被執(zhí)行,所以 Java8 中 SLF4J 還可以通過 Supplier 來傳遞。如下所示:
logger.debug("用戶{}開始下單:{},請求信息:", userId, orderNo, () -> Gson.toJson(req))可以看到,SLF4J 不僅為我們制定了一套面向接口開發(fā)的方式,還為我們明確了如何有效地編寫日志。這也是為什么越來越多人喜歡用 SLF4J。
日志編寫方式
在詳細(xì)介紹了我們在開發(fā)時需要使用的日志框架后,我將正式展開我們的標(biāo)題:“如何編寫‘可觀測’的日志?”我會從日志編寫位置、寫入性能、占位符、可讀性、關(guān)鍵信息隱蔽、減少代碼位置信息的輸出、文件分類、和日志 review 這 8 個點(diǎn)來講解,并將它們分成了 2 個方向:
日志開發(fā)時(前 5 項):怎么樣寫出更有效率的日志?
日志完成后(后 3 項):上線前后有哪些需要注意的?
日志編寫的位置可以說是重中之重,好的日志位置可以幫你解決問題,也可以讓你更加了解代碼的運(yùn)行情況。我總結(jié)了幾點(diǎn)比較重要的編寫日志的位置,以供參考。
系統(tǒng)/應(yīng)用啟動和參數(shù)變更:當(dāng)系統(tǒng)啟動時,可以將相關(guān)的參數(shù)信息進(jìn)行打印,以便出現(xiàn)問題時,更準(zhǔn)確地查詢原因;參數(shù)信息可能并不存儲在本地,需要通過配置中心獲取,而參數(shù)信息有變更時,也需要將變更后的內(nèi)容輸出在日志中。
關(guān)鍵操作節(jié)點(diǎn):最典型的就是在關(guān)鍵位置添加日志,記錄用戶進(jìn)行的某個操作。當(dāng)出現(xiàn)問題時,你可以通過這個位置的日志了解到用戶的操作。同樣你也可以在系統(tǒng)進(jìn)行某些操作時添加日志,比如你準(zhǔn)備啟動某個線程池來進(jìn)行數(shù)據(jù)處理時,可以加上日志便于以后分析問題。
大型任務(wù)進(jìn)度上報:當(dāng)系統(tǒng)在處理某個比較大型的任務(wù)時,可以在這個過程中增加相關(guān)的日志來表明任務(wù)處理的進(jìn)度,防止因?yàn)殚L時間沒有處理而無法得知程序執(zhí)行的狀態(tài),比如在文件下載時,可以按照百分比來定時/定次地上報數(shù)據(jù)。
異常:當(dāng)程序出現(xiàn)異常時,我們通常是通過 try-catch 來記錄當(dāng)時的情況,然后以日志的形式表現(xiàn)出來。如果是通過 try-catch 處理,你需要注意日志編寫的位置。如果你需要將日志在本層拋出,則不需要進(jìn)行日志記錄,否則會出現(xiàn)日志重復(fù)的問題。如果你除了異常以外還需要記錄其他的內(nèi)容,則可以通過定制異常信息來實(shí)現(xiàn)。
日志的寫入性能則會受到如下 5 個因素的影響:
日志編寫位置:日志編寫的位置在程序中十分重要,如果在 for 循環(huán)中編寫,因?yàn)檫@個循環(huán)會持續(xù)很多次,那么就會產(chǎn)生大量的日志記錄。此時可以考慮一下,這個日志的記錄是否有必要。
日志數(shù)量:如果你大量地編寫日志,那么日志的質(zhì)量一定會降低。同時,大量的日志會讓你很難去查看問題,反而成了一種負(fù)擔(dān)。在高訪問量時,過多的日志也會影響程序的執(zhí)行效率。
日志編寫等級:我在上一節(jié)中講過,日志等級很容易被濫用,不正確的日志等級會導(dǎo)致我們查詢問題的時間增加。
日志輸出級別:這里指的是對于配置日志輸出級別的選擇。在線上環(huán)境,不建議使用 debug 級別,因?yàn)榫€上一直有請求,debug 級別會輸出大量的基礎(chǔ)和請求信息,極其浪費(fèi)資源,因此建議開啟 info 或者以上。
無用輸出參數(shù):不對大字段、無用字段輸出,因?yàn)檫@很影響程序執(zhí)行效率和日志的內(nèi)容。我曾遇到一個案例,A 同學(xué)在線上打印了一個完整的 HTML 內(nèi)容,導(dǎo)致當(dāng)日的部分日志內(nèi)容錯亂,部分日志無法檢索,原因就在于 HTML 存在多行內(nèi)容導(dǎo)致無法解析的問題。當(dāng)開發(fā)人員到線上服務(wù)器上查看時,日志文件的大小已經(jīng)擴(kuò)大了 3 倍。
好的日志一定是便于你去排查問題的。在編寫日志時你一定要思考這個日志可以幫你做什么。
在介紹日志接口框架時我提到過,在日志編寫時盡可能地選擇基于占位符的編寫方式。這里我會告訴你為什么要用占位符:
日志的可讀性也是日志編寫的關(guān)鍵之一。一個好的日志就像一篇好的文章,能讓你很快了解到這個日志中的關(guān)鍵信息。我在工作中發(fā)現(xiàn)很多人寫的日志都是無意義的,根本無法幫你定位問題的根源,比如像下面的這段代碼:
boolean success = doSomeThing();
if (success) {
logger.info("數(shù)據(jù)保存成功!");
}
這段代碼乍一看似乎沒什么問題,但是運(yùn)行后系統(tǒng)會大量地輸出“數(shù)據(jù)保存成功!”的消息,這個輸出和沒有是一樣的,起不到任何的作用。
我總結(jié)了幾點(diǎn)在日志中容易遺漏的信息:
對于關(guān)鍵的信息不顯示或者進(jìn)行掩碼顯示,以免信息被盜取后出現(xiàn)數(shù)據(jù)內(nèi)容泄漏。推特在 2018 年曾將用戶打印在日志中,這一行為泄露了 3.3 億人。
如果不是必要,盡量不要在日志格式中輸出當(dāng)前日志所在的代碼行和方法名稱信息。如果你看過 logback,log4j 的源碼你就知道,這都是通過獲取當(dāng)前線程堆棧快照信息來進(jìn)行實(shí)現(xiàn)的,這種實(shí)現(xiàn)方式會極大地影響程序執(zhí)行的效率。
在 log4j 的文檔中有這樣一段話:“使用同步方式進(jìn)行獲取位置信息會慢 1.3 到 5 倍,如果是使用異步日志,因?yàn)闀婕翱缇€程獲取位置信息,會慢 30 到 100 倍。原文:https://logging.apache.org/log4j/2.0/manual/async.html#Location。
所以,關(guān)閉代碼位置信息的輸出可以節(jié)省系統(tǒng)資源的使用,提升性能。
文件分類可以幫助你提高檢索日志信息時的效率。將不同的業(yè)務(wù)邏輯按照不同的日志文件來分類,可以保證你看到的信息都是和這個功能相關(guān)的,不會被其他的日志干擾。這也是在大型系統(tǒng)中經(jīng)常會使用到的功能。
比如拉勾的單點(diǎn)登錄系統(tǒng),就會將用戶的極驗(yàn)驗(yàn)證功能和登錄驗(yàn)證功能拆成兩個單獨(dú)的日志文件,當(dāng)出現(xiàn)問題時,可以根據(jù)相關(guān)功能的日志來快速篩查問題,減少了篩選所需的時間。
每一次功能上線后,除了要對業(yè)務(wù)功能進(jìn)行回歸,還要對日志進(jìn)行觀察,確認(rèn)日志內(nèi)容的輸出情況,比如日志內(nèi)容是否符合預(yù)期,會不會有不合適的地方?
好的日志不是一次就能寫好的,一定是要和代碼一樣不停地迭代,才能寫出更方便處理問題,也更具有可讀性的日志。
就像店家在賣出商品后還要負(fù)責(zé)其售后,編寫完日志,對于它的管理也是十分重要的。好的日志管理方式可以提高閱讀日志的效率,而這需要開發(fā)人員和運(yùn)維人員共同協(xié)作。
日志的格式布局會影響運(yùn)維人員將這些日志內(nèi)容收集與管理的效率。如果編寫者和管理者能夠通過協(xié)商,規(guī)定出一套完整的日志格式,這樣就能在排查問題時事半功倍。
我會簡單介紹幾點(diǎn)在日志編寫時需要注意的事項:
日志歸檔是一件很重要的事情。如果你將日志內(nèi)容全部寫到一個文件中,這個日志文件會變得越來越煩冗,不利于日志的收集和查看。
一般情況下,我們會對日志按照日期來歸檔,每天生成一個日志文件,這樣在日志備份和恢復(fù)時,可以按照日期來進(jìn)行。如果感覺天級別的日志仍然太大了,可以考慮按照小時細(xì)分。
今天我們了解了日志編寫的工具、日志編寫需要注意的 8 個事項以及日志管理的方式,有哪些是你原來犯過的錯誤,又有哪些是你原來沒有想到的呢?歡迎你在留言區(qū)分享與討論。希望你在日后的日志編寫中可以注意到這些問題。?

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流