天天看點

開發過程中記錄日志有哪些好的技巧?

開發過程中記錄日志有哪些好的技巧?

文章目錄

  • ​​開發過程中記錄日志有哪些好的技巧?​​
  • ​​一、簡介​​
  • ​​二、記錄日志的目的(why)​​
  • ​​開發調試​​
  • ​​記錄使用者行為​​
  • ​​程式運作狀況​​
  • ​​系統、機器狀況​​
  • ​​三、日志的要素(what)​​
  • ​​時間​​
  • ​​位置​​
  • ​​級别​​
  • ​​内容​​
  • ​​唯一辨別​​
  • ​​事件上下文​​
  • ​​格式化​​
  • ​​其他​​
  • ​​四、記錄日志的一些原則和技巧​​
  • ​​使用架構或子產品​​
  • ​​不能出錯​​
  • ​​避免敏感資訊​​
  • ​​Lazy logging​​
  • ​​異步列印日志​​
  • ​​設定緩存​​
  • ​​對日志歸檔、分類​​
  • ​​結語​​

一、簡介

在軟體開發中,我們出于各種目的,需要将程式運作中的一些狀态記錄在日志中。

日志記錄,并不是越多越好,也不是記錄的越頻繁越好,而是需要我們精心設計記錄日志的時機、内容、格式(以友善後續解析、查詢日志)等等。

二、記錄日志的目的(why)

開發調試

目的是開發期調試程式使用,這種日志量比較大,且沒有什麼實質性的意義,隻應該出現在開發期,而不應該在項目上線之後輸出。

記錄使用者行為

這種類型的日志,記錄使用者的操作行為,用于大資料分析,比如監控、風控、推薦等等。這種日志,一般是給其他團隊分析使用,而且可能是多個團隊,是以一般會有一定的格式要求,開發者應該按照這個格式來記錄,便于其他團隊的使用。當然,要記錄哪些行為、操作,一般也是約定好的,是以,開發者主要是執行的角色。

程式運作狀況

記錄程式的運作狀況,特别是非預期的行為、異常情況,這種日志,主要是給開發、維護人員使用。什麼時候記錄,記錄什麼内容,完全取決于開發人員,開發者具有高度自主性。本文讨論的主要也是指這種類型的日志,因為作為一個服務端開發、運維人員,程式運作日志往往是解決線上問題的救命稻草。

系統、機器狀況

比如網絡請求、系統CPU、記憶體、IO使用情況等等,這種日志主要是給運維人員使用,生成各種更直覺的展現形式,在系統出問題的時候報警。

三、日志的要素(what)

每條日志都可以被當作一個事件(event),紀錄了該事件發生時各個資訊:

時間

日志的時間可以包含多種含義,不同含義的時間傳遞不同的資訊:

指事件發生的時間,而不是日志被列印的時間。該時間附近範圍内,結合該事件及伺服器的網絡、CPU、IO等狀況,可以了解他們之間的相關性,有助于分析事件發生的原因。

持續時間。例如網絡請求的耗時、處理請求的各個階段的耗時等。

事件發生的順序。通過時間戳的順序,了解到一系列事件的發生順序。對多程序、多線程、分布式系統有幫助。

位置

事件發生在哪個子產品、哪個檔案、哪個函數、哪一行代碼裡。

級别

日志的重要程度。用于:

不同的環境(測試、生産)下,列印不同級别的日志

不同級别的日志産生不同級别的監控報警

内容

簡明扼要的描述發生了什麼樣的事情。目的是通過日志本身,而不是重新閱讀相關代碼來搞清楚發生了什麼事情。

例如:logger.warn(‘user_login failed due to unvalid_username’)

唯一辨別

不管是面向使用者的服務、面向機器叢集的服務,都需要一個唯一辨別作為日志的主體,以友善查找該事件主體的其他資訊。(很多中繼資料是不會記在日志裡的,隻會記一個唯一辨別)

例如:logger.warn(‘user_login failed due to password, username %s’, username)

事件上下文

除了時間、位置、級别、内容,其他的一些有用的資訊。

例如:

logger.warn(‘user_login failed due to wrong password, username %s’, username)

logger.warn(‘user_login failed due to invalid password, username %s’, username)

logger.warn(‘user_login failed due to empty password, username %s’, username)

為了擷取更豐富的上下文,有些資料需要從 Nginx 那裡擷取并傳給本服務,然後列印。

格式化

将上述的各個資訊按照固定的順序列印出來,不僅友善查找(例如使用grep,sed,awk等),也友善收集日志的程式(ES)解析日志。

其他

根據各自的業務特點,還可以在日志中記錄(包括、不限于):

錯誤次數

目前正在處理的請求數

處理的進度(33%,50%,78%。。。)

IP

問題出現時的請求連結

四、記錄日志的一些原則和技巧

使用架構或子產品

Java : Log4j、Log4j2、Commons Logging、Slf4j、Logback

Python 内置的 logging 子產品

beego 架構裡的 ​​http://github.com/astaxie/beego/logs​​ 子產品

其他的一些Go的架構:

Logrus: ​​http://github.com/Sirupsen/logrus​​ (Docker use this)

​​http://github.com/op/go-logging​​

​​http://github.com/golang/glog​​ (from Google, implementation of their C++ glog library in Go)

​​http://github.com/cihub/seelog​​

不能出錯

記錄日志的目的是為了友善發現問題,解決問題,那麼就需要保證記錄日志本身不能出錯。

尤其是對于 Error、Fatal級别的日志,出現的機率低,是以要做好單元測試,以保證記錄日志本身是沒問題的,是不會影響正常業務的。

避免敏感資訊

避免記錄使用者密碼。

避免記錄使用者個人資訊,如身份證号、手機号等。應該隻在日志裡記錄該使用者的唯一辨別,然後根據該唯一辨別去其他的系統(例如資料庫)檢視詳細資訊。

記錄“不可能發生”的事件

雖然正常邏輯下,某些情況是永遠都不可能發生的,但是還是需要給這些不可能發生的情況列印一條日志。

例如 條件語句裡的 else,switch 裡的 default,都需要進行防禦式的程式設計,同時記錄日志。

Lazy logging

日志本身是一個字元串,可以通過多種方式拼接而成。如果根據log level,該條日志不應被列印,那麼就應該避免拼接這個操作,也應該避免字元串拼接裡的函數的調用。

例如:

開發過程中記錄日志有哪些好的技巧?

log level 設定為 DEBUG(第5行),那麼會先列印第12行,再列印第15行

log level 設定為 INFO,依然會列印第12行,說明:

問題1: 即便沒有列印第15行,依然生成了一個字元串

問題2: 而且調用了 getUserCount 這個函數

解決上述的問題1,可以把第15行修改為:

開發過程中記錄日志有哪些好的技巧?

為了解決上述的問題2,一個辦法是:

開發過程中記錄日志有哪些好的技巧?

Java裡的方式:

開發過程中記錄日志有哪些好的技巧?

目前Golang裡也存在這樣的問題,但是還沒找到怎麼做到 lazyLogging。

但是,可以看出上面的方式也不優雅,是以應當避免在列印日志時調用函數。

異步列印日志

網際網路應用程式中,高并發下的寫日志會帶來大量的IO操作,進而影響正常服務的性能。這時候的記日志就需要專門的服務去做,例如把日志打到消息隊列裡,然後再寫到磁盤上。

設定緩存

預設的日志是随時 flush 到console、檔案裡的,通過設定緩存進行批量操作,可以一定程度上優化服務性能。

對日志歸檔、分類

列印到一個檔案裡,會使得該檔案越來越大,不友善查找。

按日志屬性分類:access log,error log,

按日期歸檔:lathspell-api.2023-01-17.log,lathspell-api.2023-01-18.log

結語