你真的會打印日志?
點擊上方“中間件興趣圈”,選擇“設為星標”
一看這個標題我想大家一定進來想“懟”我,你這不是小題大作嗎?在java中打印日志不是一件非常簡單的事情嗎?
在java中常用的日志級別為DEBUG、INFO、WARN、ERROR四個日志級別。通常開發(fā)環(huán)境開啟DEBUG,生產(chǎn)環(huán)境開啟INFO級別,采用主流的日志采集工具包諸如log4j、logback。
但日志輸出真的有這么簡單嗎?其實里面蘊含著很多的規(guī)范,或者是最佳實踐,并且還有一些非常有用設計技巧方便查詢關聯(lián)日志的技巧,容我慢慢道來。
輸出日志的終極目標:助力于快速定位問題、解決問題。
接下來將圍繞該目標,闡述一下日志相關的一些最佳實踐。
1、日志的基本規(guī)范
首先,我們還是簡單介紹一下常用的4個日志級別,并說明各個級別在使用時應該注意的問題。
DEBUG 輸出程序的調(diào)試信息,優(yōu)雅的DEBUG日志可以讓我們在排查問題的時候,壓根就不需要使用開發(fā)工具的DEBUG斷點調(diào)試功能,而是直接看Debug的輸出日志即可定位問題。
打印請求、響應數(shù)據(jù)包,特別是入口處將所有請求參數(shù)打印 對核心方法,特別核心計算邏輯前后打印當時的輸入與輸出,并日志中顯示包含方法名稱。 對核心流程(循環(huán)、分支)等條件判斷時輸出必要的入?yún)⒂谂c返回結果,清晰的展示程序的運行軌跡。 INFO INFO日志我們通常用于記錄系統(tǒng)/組件的基本運行情況和運行狀態(tài),特別適合打印一次性日志,例如核心類的啟動過程、狀態(tài)變更等信息,輸出的內(nèi)容一定要非常詳細,不要擔心影響性能。
目前的主流日志輸出框架例如logback,其日志的打印基本都是基于異步的,性能已經(jīng)非常高,無需擔心性能損耗。
WARN 警告級別,通常用于可預知但又不希望發(fā)生的情況,典型的使用場景是打印業(yè)務類異常日志,例如參數(shù)校驗不通過、權限不足,余額不足等用戶可處理的;再例如中間件開發(fā)時,有一些分支是我們不希望進入的,因為進入就代表性能差等場景,但這類異常不需要相關系統(tǒng)負責人干預就能得到處理的。
ERROR 通常用來打印系統(tǒng)級別的日志,需要人為來干預,通常較大業(yè)務規(guī)模的公司都會將系統(tǒng)級別的異常(ERROR)接入監(jiān)控告警中心,一旦持續(xù)發(fā)生多少條,錯誤率占比多少,將會觸發(fā)告警,相關負責人跟進處理。
既然需要人為來干預,ERROR日志不僅要打印出錯誤堆棧,同時一定要主動打印出上下文環(huán)境,至少可以打印出該異常所在方法的入?yún)?,盡量讓人能夠根據(jù)錯誤日志與上下文,就能快速定位到具體的代碼行。
2、日志進階
上述是一些基本的日志使用規(guī)范,分布式已成為企業(yè)架構的標配,一個應用至少會部署2臺機器,當用戶反饋業(yè)務異常時嘗試去跟蹤日志時會面臨一個問題:去哪臺業(yè)務機器上去查詢?nèi)罩?,如果只?臺還好辦,大不了一臺臺去嘗試,但如果有10臺,20臺甚至上百臺,在這樣輪詢幾乎不可能實現(xiàn),那該如何處理呢?
經(jīng)典的ELK架構如下圖所示:
通常,為了避免在每臺業(yè)務機器上部署一個logstash去抽日志,我們通常建議自定義一個log append,直接將日志寫入到kafka中,然后再掛logstash從kafka中抽取日志,寫入到es集群,然后通過kibana對日志進行可視化搜索。
然后我們查詢?nèi)罩揪妥兊妙愃七@樣了:
本文并沒有打算探討ELK架構,這個后續(xù)應該會單獨展開詳細介紹,而是就算我們接入了ELK,從ELK可以統(tǒng)一查看根據(jù)關鍵字查詢?nèi)罩玖?,該日志會包含所有服務器上的日志,比單獨一臺一臺去找依然方便了很多。
但這些日志其實是雜亂無章的,查詢出來的日志與日志之間沒有任何關聯(lián)性,而我們在解決特定問題時通常希望日志的**“隔離性”**,希望我們可以根據(jù)一個統(tǒng)一的關鍵字,例如請求號篩選出所有相關的日志,這樣對我們分析排查問題能起到極大的促進作用。
2.1 每條日志包含一個請求序
那我們?nèi)绾螌ⅰ罢埱筇枴苯y(tǒng)一寫入到日志文件中,肯定不能要求在項目中去修改所有日志輸出到地方,手動去增加請求編號。
我們可以通過自定義一個append,在append中對用戶的日志統(tǒng)一進行二次加工。
logback、log4j都可以自定append,接下來以當前使用最廣logback舉例,和大家介紹一下自定義append。
1、繼承 AppenderBase 并初始化
首先需要繼承l(wèi)ogback的append的基礎類:AppenderBase,入下圖所示:
其中有一個初始化方法start,通常的做法是先調(diào)用super.start()標記append啟動,然后可以在該方法中初始化kafka的消息發(fā)送者對象。
2、重寫append方法
主要是從ILoggingEvent對象中獲取原始日志,然后我們對原始日志加以加工,加工代碼如下圖所示:
關鍵是reqeustId的獲取,這個通常會配置一個http filter,進入請求鏈中放入到線程本地變量中(ThreadLocal),然后在日志輸出時從線程上下文環(huán)境中獲取,為了能在線程池等復雜環(huán)境下使用,通??梢允褂茫═ransmittableThreadLocal),關于在線程池中傳遞數(shù)據(jù),需要使用ttl框架,關于這塊的想象介紹可以查看筆者的另一篇博文:全鏈路壓測必備基礎組件之線程上下文管理之“三劍客”
一鍵三連(關注、點贊、留言)是對我最大的鼓勵。
??點擊下方卡片,關注彈出內(nèi)容,回復「PDF」可領取海量學習資料,共同刷題進步,內(nèi)互相監(jiān)督,記錄成長!


