通常前端開發同窗,對日誌其實不太敏感,畢竟前端大多數狀況下,不太關心日誌。即便有,也可能調用一些第三方的統計,好比百度統計或者別的等。在 Node.js
(下文中簡稱node
) 推動過程當中,也發現咱們日常打日誌太隨意,該打的日誌沒有打,打的一些關鍵日誌缺乏必要上下文信息,致使在線上定位問題的時候很困難。javascript
本文主要梳理了目前咱們團隊在nodejs開發中日誌方面存在的問題,以及經過統一日誌規範,但願達到什麼樣的效果。html
ELK
裏不能很好的解析&檢索 (PS: ELK
文章在路上)ELK
檢索requestId
,方便微服務下定位問題參考一些日誌的最佳實踐,目前將node日誌分爲以下幾種類型(scope
):前端
desc
: 系統啓動、運行過程當中,打的日誌,代表系統的一些啓動日誌、啓動參數等,也包含在 不能 捕獲到http上下文的時候,打的日誌stat
: 系統性能統計日誌,應用會定時收集一些性能信息,便於查詢應用當前狀態visit
: 每一個http請求相關的日誌,會包含唯一的 requestId,定位該請求相關的全部日誌biz
: 業務數據相關日誌,主要提供給數據統計使用只使用 FATAL
、ERROR
、WARN
、INFO
和 DEBUG
等級。java
- FATAL - 致使程序退出的嚴重系統級錯誤,不可恢復,當錯誤發生時,系統管理員須要當即介入,通常應用代碼 不 使用。
- ERROR - 運行時異常以及預期以外的錯誤,也須要當即處理,但緊急程度低於FATAL,當錯誤發生時,影響了程序的正確執行。須要注意的是這兩種級別屬於服務本身的錯誤,須要管理員介入,用戶輸入出錯不屬於此分類,請求後端、讀文件、數據庫等超時、返回錯誤結構,屬於ERROR
- WARN - 預期以外的運行時情況,表示系統可能出現問題。對於那些目前還不是錯誤,然而不及時處理也會變成錯誤的狀況,也能夠記爲WARN,如磁盤太低。
- INFO - 有意義的事件信息,記錄程序正常的運行狀態,好比收到請求,成功執行。經過查看INFO,能夠快速定位WARN,ERROR, FATAL。INFO不宜過多,一般狀況下不超過 DEBUG 的10%。
- DEBUG - 與程序運行時的流程相關的詳細信息以及當前變量狀態。
日誌格式統一採用 JSON ,便於 ELK
解析處理。node
日誌中的各個字段的值,都應該儘可能使用 英文 ,不使用中文。nginx
日誌具體字段,分爲 基礎數據 + 擴展數據。基礎數據,是底層日誌框架自帶的,全部日誌都會包含。擴展數據,不一樣類型的日誌,包含不一樣的字段。git
目前使用的 node-bunyan
日誌庫,官方文檔,基礎字段包含以下:github
下面定義的各個數據類型的擴展數據,不是 所有的字段,僅包含該日誌類型下,必需的字段。這些必需的擴展字段,須要在 ELK
中創建索引,方便定位各類問題。web
desc
類型日誌,擴展字段:TODOstat
類型日誌,擴展字段:{ perf: {rss: xxxx, cpu: xxx} }
visit
類型日誌,擴展字段:biz
{
///////////// 基礎數據 ////////
v: 1,
level: 20,
///////////// 擴展字段 ////////
// 標誌日誌類型
scope: "visit",
//事件類型:在 visit 的日誌類型下,還會細分不一樣的事件,好比 client-req、client-res、 普通trace、請求後端service-start, service-end, service-err等。
event: "trace",
//客戶端ID,追蹤用戶、設備會話。在web端,能夠是長期的cookie;在APP端,能夠是device-id等
rrdid: "",
//本次請求的唯一ID,串聯本次請求的全部相關日誌
req_id: "some-uuid-for-request",
//本次請求的用戶ID
uid: "",
//本次請求的客戶端相關數據,經過 ctx.logger 打日誌時,自動加上
d: {
url: "/some/path?include-query",
//客戶端ip
ip: "10.138.10.1",
//客戶端的 userAgent
ua: ""
},
//本次node請求的處理時間,毫秒
tm: 500,
//該日誌相關的上下文數據,儘可能拼成一個字符串,放在 extra 裏
extra: "",
//ERROR 級別日誌,最好包含error相關信息,好比請求後端相關參數等
err: {
msg: "",
stack: ""
},
//調用後端服務相關參數和響應
service_req: {
host: "",
path: "",
payload: ""
},
service_res: {
//http狀態碼
http_code: 200,
//響應時間
tm: 100,
//響應的body
body: "",
//異常信息
err: ""
}
}
複製代碼
開發者目前只關心 visit
類型的日誌,即和某一次http請求相關聯的日誌。desc
和stat
類型的日誌,統一由開發框架封裝後實現,業務開發 不用 關心。下面講的,都是針對 visit
類型的日誌。數據庫
一次http請求,會打出一系列相關聯的日誌。在node層,一般一次請求,會進一步轉發給N個後端服務,而後對後端數據進行一些處理、合併等操做,最後渲染頁面或是輸出JSON。所以,一次請求相關的日誌,大致分爲如下幾種 event
:
client-req
: client請求到達node層,統一由框架打日誌,開發 不 關心service-start
: node對某個後端服務發起請求,由通用請求庫負責打日誌,開發 不 關心service-end
: node請求某個後端服務結束,由通用請求庫負責打日誌,開發 不 關心service-err
: node請求後端服務異常,由通用請求庫負責打日誌,開發 不 關心。調用後端服務異常,日誌級別爲 WARN
,不是 ERROR
trace
: node中業務層打的日誌,若是異常,能幫助定位本次請求相關問題client-res
: 結束client的請求,打印本次請求的http code,本次請求處理時間等,由框架統一打,開發 不 關心開發同窗在打日誌時,應該謹慎的選擇級別,INFO
(含)級別以上,都應該能對定位問題、具體業務統計需求有要求,才能使用。大部分狀況下,可使用 DEBUG
級別,線上 不會 開啓DEBUG
級別。
針對打印 visit
類型的日誌,調用 ctx.logger
(基於Koa
的框架) 屬性打日誌,推薦參數都傳遞 JSON
,具體方法以下:
ctx.logger.debug({msg: "", "extra": "a=1 b=2 c=value"});
ctx.logger.info({msg: "xxx", "extra": "其餘的額外字段"});
ctx.logger.warn({msg: "xxx", "extra": "額外上下文數據"});
//ERROR級別日誌,應該提供 Error 對象
ctx.logger.error({msg: 'xxx', err: error, extra: ""});
複製代碼
注意1,額外的參數,推薦存放在 extra
字段中,統一拼成 string
;若是確實有必要單獨出每一個字段, 禁止 額外的參數佔用上述通用字段名!!
注意2,基礎數據中的msg
字段,禁止 包含具體的上下文數據,和該日誌相關的上下文數據,應該拼成字符串,放在單獨的 extra
字段中。好比,某個用戶登陸接口,但願統計調用次數,能夠這樣打印:
ctx.logger.info({msg: "user login", "extra": 'mobile=18712387101 code=xxxx k3=value3'});
複製代碼