開發過程中記錄日志有哪些好的技巧?
文章目錄
- 開發過程中記錄日志有哪些好的技巧?
- 一、簡介
- 二、記錄日志的目的(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