Java應用層數據鏈路追蹤(附優雅打印日誌姿勢)

我是3y,一年CRUD經驗用十年的markdown程序員👨🏻‍💻常年被譽爲優質八股文選手

今天來聊些大家都用得上的東西:數據鏈路追蹤。之前引入了系統的監控來快速定位應用操作系統上的問題,而業務問題呢?在這篇文章中你可以看到用註解的方式打印日誌,也能看到簡易版的全鏈路追蹤是怎麼實現的。

不多BB,開始吧

01、註解日誌打印

日誌的搭建我在austin最開始的前幾篇已經有提及了,之前一直在等我的基友@蠻三刀醬他的日誌組件庫上傳到Maven庫,好讓我使用使用下。在最近,他已經更新了兩個版本,然後傳到了Maven庫了,所以我就來接入了

這個組件庫做的事情就是使用註解的方式來打印日誌信息,並支持SpEL解析自定義上下文以及自定義函數。它支持的東西聽起來很牛逼,但說白了就是讓記錄日誌的方式做得更裝逼。我們寫個破代碼還能裝逼,這誰受得了!這誰頂得住!

現在我已經把註解在方法上定義了,當該方法被調用時,它打印了以下的日誌:

看起來很好用,對不對?通過一個註解,我就能把方法的入參信息打印出來,有bizTypebizId給我們自定義,那就可以很方便地定位出打印日誌的地方了,並且他還貼心response返回值也輸出到日誌上。

至少在這個接口上,這非常符合我這個場景的需求,我們再通過一張圖稍微重溫下這個send接口到底做了什麼事:

接口層面打印入參信息以及返回值就能定位到很多問題(懂的都懂),使用註解還不用幹擾到我們正常的業務代碼就能打印出這麼好的日誌信息了(這個逼是裝上了

它的實現原理並不複雜,感興趣的小夥伴可以拉代碼自己看看,先看readmd再看代碼!!

GitHub:https://github.com/qqxx6661/logRecord

總的來說,他通過SpEL表達式來讀取到#sendRequest入參對象的信息,而註解解析則用的是Spring AOP。至於自定義上下文以及自定義函數我在這是沒用到的,至少在austin項目場景下,我感覺都沒什麼用。哦,對了,它還能將日誌輸出到別的管道(MQ)。可惜的是,我這場景也用不到。

在目前的實現下,我就只有這個接口能用到該組件,我承認他在某些場景是很好用。

但它是有侷限性的:打印的日誌信息跟方法參數強相關:如果要打印方法參數以外的變量那需要用到上下文Context 或者自定義函數 。自定義函數的使用姿勢是有侷限性的,我們並不能把日誌所涉及的變量都抽取到某函數上。如果用上下文Context的話,還是得嵌入業務代碼裏,那爲啥不直接拼裝好日誌打呢?

我一度懷疑是不是我的使用姿勢不對,跟基友探討了下,我的應用場景下還得自己抽取LogUtils進行日誌打印。

02、數據鏈路追蹤

從上面的接口打印的日誌以及能很快地排查出接入層的問題了,其實重頭戲其實是在處理層上,回顧下處理層目前做的事情:

在處理層上會有不少的平臺過濾規則,這些過濾規則大多都不是針對於消息模板的,而是針對於userId(接收者)的。在這個處理過程中,記錄下每個消息模板中的每個用戶的執行情況就尤其重要了。

1、定位和排查問題。如果客戶反饋用戶收不到短信,一般情況下都在這個處理的過程中導致的(可能是被去重,可能是調用接口出問題)

2、對模板執行的整體鏈路數據分析。一個消息模板一天發送的量級,中途被每個規則過濾的量級,成功下發的量級以及消息最後被點擊的量級。除了點擊數據,其他的數據都來源處理層

基於上面的背景,我設計了一套埋點的規則,在處理關鍵鏈路上打上對應的點位📝

目前點位的信息是不全的,隨着系統的完善和接入各個渠道,這裏的點位信息還會繼續增加,只要我們認爲有哪些地方是需要記錄下來的,就可以增加。

可能看到這裏你會覺得有些抽象,我請求一次接口打印下日誌就容易懂啦:

// 1、接入層打印日誌(returnStr打印處理結果,而msg打印出入參信息)
2022-01-08 15:44:53.512 [http-nio-8080-exec-7] INFO  com.java3y.austin.utils.LogUtils - 
{"bizId":"1","bizType":"SendService#send","logId":"34df87fc-0489-46c1-b39f-cafd7652f55b",
"msg":"{\"code\":\"send\",\"messageParam\":{\"extra\":null,\"receiver\":\"13288888888\",\"variables\":{\"title\":\"yyyyyy\",\"contentValue\":\"66661641627893157\"}},\"messageTemplateId\":1}","operateDate":1641627893512,"returnStr":"{\"code\":\"00000\",\"msg\":\"操作成功\"}","success":true,"tag":"operation"}

// 2、處理層打印入口日誌(表示成功消費到Kafka的消息 state=10)
2022-01-08 15:44:53.622 [org.springframework.kafka.KafkaListenerEndpointContainer#6-0-C-1] INFO  com.java3y.austin.utils.LogUtils - 
{"businessId":1000000120220108,"ids":["13288888888"],"state":10,"timestamp":1641627893622}

// 3、處理層打印入口日誌(表示成功消費到Kafka的原始日誌)
2022-01-08 15:44:53.622 [org.springframework.kafka.KafkaListenerEndpointContainer#6-0-C-1] INFO  com.java3y.austin.utils.LogUtils - 
{"bizType":"Receiver#consumer","object":{"businessId":1000000120220108,"contentModel":{"content":"66661641627893157"},"deduplicationTime":1,"idType":30,"isNightShield":0,"messageTemplateId":1,"msgType":10,"receiver":["13288888888"],"sendAccount":66,"sendChannel":30,"templateType":10},"timestamp":1641627893622}

// 4、處理層打印邏輯過濾日誌(state=20,表示這條消息由於配置了丟棄,已經丟棄掉)
2022-01-08 15:44:53.623 [pool-8-thread-3] INFO  com.java3y.austin.utils.LogUtils - 
{"businessId":1000000120220108,"ids":["13288888888"],"state":20,"timestamp":1641627893622}

我打印日誌的核心邏輯是:

  • 入口側(這裏包括接口的入口以及剛消費Kafka的入口)需要打印出原始的信息。原始信息有了,纔好對問題進行定位和排查,至少幫助我們復現
  • 在處理過程中使用某個標識來標明處理的過程(10代表成功消費Kafka,20代表該消息已經被丟棄...),並且日誌的格式是統一的這樣後續我們可以統一清洗該日誌信息

至於打日誌的過程就很簡單了,只要抽取一個LogUtils類就好咯:

那對於點擊是怎麼追蹤的呢?其實也好辦,在下發的鏈接上拼接businessId就好了。只要我們能拿到點擊的數據,在鏈接上就可以判斷是否存在track_code_bid字符,進而找到是哪個用戶點擊了哪個模板消息。

無論是打點日誌還是原始日誌,businessId會跟隨着消息的生命週期始終。而businessId的構成只是通過消息模板內容+時間而成

03、後續

現在已經打印出對應的數據鏈路信息了,但這是不夠的,這只是將數據鏈路信息寫到了服務器的本地上,還需要考慮以下的情況:

1、運行應用的服務器一般是集羣,日誌數據會記錄到不同的機器上,排查和定位問題只能登錄各個服務器查看

2、鏈路的數據需要實時,通過提供Web後臺的界面功能快速讓業務方自助查看整個流程

3、鏈路的數據需要離線保存用於對數據的分析以及留備份(本地日誌往往存放不超過30天)

後面這些功能都會一一實現,優先會接入ELK來有統一查詢日誌信息的入口以及配置相關的業務監控或告警,敬請期待。

點個贊一點都不過分吧?我是3y,下期見。

關注我的微信公衆號【Java3y】除了技術我還會聊點日常,有些話只能悄悄說~ 【對線面試官+從零編寫Java項目】 持續高強度更新中!求star!!原創不易!!求三連!!

austin項目源碼Gitee鏈接:gitee.com/austin

austin項目源碼GitHub鏈接:github.com/austin

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