我們都有過上機器查日志的經歷,當集群數量增多的時候,這種原始的操作帶來的低效率不僅給我們定位現網問題帶來極大的挑戰,同時,我們也無法對我們服務框架的各項指標進行有效的量化診斷,更無從談有針對性的優化和改進。這個時候,構建具備信息查找,服務診斷,數據分析等功能的實時日志監控系統尤為重要。
ELK (ELK Stack: ElasticSearch, LogStash, Kibana, Beats) 是一套成熟的日志解決方案,其開源及高性能在各大公司廣泛使用。而我們業務所使用的服務框架,如何接入 ELK 系統呢?
業務背景
我們的業務框架背景:
業務框架是基于 NodeJs 的 WebServer 服務使用 winston 日志模塊將日志本地化 服務產生的日志存儲在各自機器的磁盤上 服務部署在不同地域多臺機器我們將整個框架接入 ELK 簡單歸納為下面幾個步驟:
日志結構設計:由傳統的純文本日志改成結構化對象并輸出為 JSON. 日志采集:在框架請求生命周期的一些關鍵節點輸出日志 ES 索引模版定義:建立 JSON 到 ES 實際存儲的映射一、日志結構設計
傳統的,我們在做日志輸出的時候,是直接輸出日志的等級(level)和日志的內容字符串(message)。然而我們不僅關注什么時間,發生了什么,可能還需要關注類似的日志發生了多少次,日志的細節與上下文,以及關聯的日志。 因此我們不只是簡單地將我們的日志結構化一下為對象,還要提取出日志關鍵的字段。
1. 將日志抽象為事件
我們將每一條日志的發生都抽像為一個事件。事件包含:
事件元字段
事件發生時間:datetime, timestamp 事件等級:level, 例如: ERROR, INFO, WARNING, DEBUG 事件名稱: event, 例如:client-request 事件發生的相對時間(單位:納秒):reqLife, 此字段為事件相對請求開始發生的時間(間隔) 事件發生的位置: line,代碼位置; server, 服務器的位置請求元字段
請求唯一ID: reqId, 此字段貫穿整個請求鏈路上發生的所有事件 請求用戶ID: reqUid, 此字段為用戶標識,可以跟蹤用戶的訪問或請求鏈路數據字段
不同類型的事件,需要輸出的細節不盡相同,我們將這些細節(非元字段)統一放到d -- data,之中。使我們的事件結構更加清晰,同時,也能避免數據字段對元字段造成污染。
e.g. 如 client-init事件,該事件會在每次服務器接收到用戶請求時打印,我們將用戶的 ip, url等事件獨有的統一歸為數據字段放到 d 對象中
舉個完整的例子
{ "datetime":"2018-11-07 21:38:09.271", "timestamp":1541597889271, "level":"INFO", "event":"client-init", "reqId":"rJtT5we6Q", "reqLife":5874, "reqUid": "999793fc03eda86", "d":{ "url":"/", "ip":"9.9.9.9", "httpVersion":"1.1", "method":"GET", "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", "headers":"*" }, "browser":"{"name":"Chrome","version":"70.0.3538.77","major":"70"}", "engine":"{"version":"537.36","name":"WebKit"}", "os":"{"name":"Mac OS","version":"10.14.0"}", "content":"(Empty)", "line":"middlewares/foo.js:14", "server":"127.0.0.1"}
新聞熱點
疑難解答
圖片精選