你真的會打印日誌?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一看這個標題我想大家一定進來想“懟”我,你這不是小題大作嗎?在java中打印日誌不是一件非常簡單的事情嗎?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在java中常用的日誌級別爲DEBUG、INFO、WARN、ERROR四個日誌級別。通常開發環境開啓DEBUG,生產環境開啓INFO級別,採用主流的日誌採集工具包諸如log4j、logback。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但日誌輸出真的有這麼簡單嗎?其實裏面蘊含着很多的規範,或者是最佳實踐,並且還有一些非常有用設計技巧方便查詢關聯日誌的技巧,容我慢慢道來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"輸出日誌的終極目標","attrs":{}},{"type":"text","text":":助力於快速定位問題、解決問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來將圍繞該目標,闡述一下日誌相關的一些最佳實踐。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#48b378","name":"user"}}],"text":"1、日誌的基本規範","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,我們還是簡單介紹一下常用的4個日誌級別,並說明各個級別在使用時應該注意的問題。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"DEBUG輸出程序的調試信息,優雅的DEBUG日誌可以讓我們在排查問題的時候,壓根就不需要使用開發工具的DEBUG斷點調試功能,而是直接看Debug的輸出日誌即可定位問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"打印請求、響應數據包,特別是入口處將所有請求參數打印","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對核心方法,特別核心計算邏輯前後打印當時的輸入與輸出,並日志中顯示包含方法名稱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對核心流程(循環、分支)等條件判斷時輸出必要的入參於與返回結果,清晰的展示程序的運行軌跡。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"INFOINFO日誌我們通常用於記錄系統/組件的基本運行情況和運行狀態,特別適合打印一次性日誌,例如核心類的啓動過程、狀態變更等信息,輸出的內容一定要非常詳細,不要擔心影響性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#6a737d","name":"user"}},{"type":"strong","attrs":{}}],"text":"目前的主流日誌輸出框架例如logback,其日誌的打印基本都是基於異步的,性能已經非常高,無需擔心性能損耗。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"WARN警告級別,通常用於可預知但又不希望發生的情況,典型的使用場景是打印業務類異常日誌,例如參數校驗不通過、權限不足,餘額不足等用戶可處理的;再例如中間件開發時,有一些分支是我們不希望進入的,因爲進入就代表性能差等場景,但這類異常不需要相關係統負責人干預就能得到處理的。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"ERROR通常用來打印系統級別的日誌,需要人爲來干預,通常較大業務規模的公司都會將系統級別的異常(ERROR)接入監控告警中心,一旦持續發生多少條,錯誤率佔比多少,將會觸發告警,相關負責人跟進處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"既然需要人爲來干預,ERROR日誌不僅要打印出錯誤堆棧,同時一定要主動打印出上下文環境,至少可以打印出該異常所在方法的入參,儘量讓人能夠根據錯誤日誌與上下文,就能快速定位到具體的代碼行。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#48b378","name":"user"}}],"text":"2、日誌進階","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述是一些基本的日誌使用規範,分佈式已成爲企業架構的標配,一個應用至少會部署2臺機器,當用戶反饋業務異常時嘗試去跟蹤日誌時會面臨一個問題:去哪臺業務機器上去查詢日誌,如果只有2臺還好辦,大不了一臺臺去嘗試,但如果有10臺,20臺甚至上百臺,在這樣輪詢幾乎不可能實現,那該如何處理呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經典的ELK架構如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3d/3d9a4ffffe86e6d47cf8610bd71b64e1.jpeg","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常,爲了避免在每臺業務機器上部署一個logstash去抽日誌,我們通常建議自定義一個log append,直接將日誌寫入到kafka中,然後再掛logstash從kafka中抽取日誌,寫入到es集羣,然後通過kibana對日誌進行可視化搜索。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後我們查詢日誌就變得類似這樣了:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/28/28baacc28fc0a68fe70cd5fbae56693f.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文並沒有打算探討ELK架構,這個後續應該會單獨展開詳細介紹,而是就算我們接入了ELK,從ELK可以統一查看根據關鍵字查詢日誌了,該日誌會包含所有服務器上的日誌,比單獨一臺一臺去找依然方便了很多。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但這些日誌其實是雜亂無章的,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"查詢出來的日誌與日誌之間沒有任何關聯性","attrs":{}},{"type":"text","text":",而我們在解決特定問題時通常希望日誌的**“隔離性”**,希望我們可以根據一個統一的關鍵字,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"例如請求號篩選出所有相關的日誌,這樣對我們分析排查問題能起到極大的促進作用","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.1 每條日誌包含一個請求序","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那我們如何將“請求號”統一寫入到日誌文件中,肯定不能要求在項目中去修改所有日誌輸出到地方,手動去增加請求編號。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以通過自定義一個append,在append中對用戶的日誌統一進行二次加工。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"logback、log4j都可以自定append,接下來以當前使用最廣logback舉例,和大家介紹一下自定義append。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、繼承 AppenderBase 並初始化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先需要繼承logback的append的基礎類:AppenderBase,入下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e8/e8bf9ef258f28a3f0832b5617b2e649f.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中有一個初始化方法start,通常的做法是先調用super.start()標記append啓動,然後可以在該方法中初始化kafka的消息發送者對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、重寫append方法","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/68/6868cd7cbcd1ca0505e7fb80b441f494.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主要是從ILoggingEvent對象中獲取原始日誌,然後我們對原始日誌加以加工,加工代碼如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8a/8aed600b4345e020dabbbeaae94ed798.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關鍵是reqeustId的獲取,這個通常會配置一個http filter,進入請求鏈中放入到線程本地變量中(ThreadLocal),然後在日誌輸出時從線程上下文環境中獲取,爲了能在線程池等複雜環境下使用,通常可以使用(TransmittableThreadLocal),關於在線程池中傳遞數據,需要使用ttl框架,關於這塊的想象介紹可以查看筆者的另一篇博文:","attrs":{}},{"type":"link","attrs":{"href":"https://blog.csdn.net/prestigeding/article/details/96642492","title":null,"type":null},"content":[{"type":"text","text":"全鏈路壓測必備基礎組件之線程上下文管理之“三劍客”","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#48b378","name":"user"}},{"type":"strong"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文章首發於公衆號「中間件興趣圈」","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/-sww4Yn2Vsn84SdIzrv6XQ","title":"","type":null},"content":[{"type":"text","text":"https://mp.weixin.qq.com/s/-sww4Yn2Vsn84SdIzrv6XQ","attrs":{}}]}]},{"type":"horizontalrule","attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者簡介:丁威,《RocketMQ 技術內幕》一書作者、RocketMQ 開源社區優秀佈道師,公衆號「中間件興趣圈」維護者,主打成體系剖析 Java 主流中間件,已發佈 Kafka、RocketMQ、Dubbo、Sentinel、Canal、ElasticJob 等中間件 15 個專欄。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章