結構化日誌:出錯時你最想要的好朋友

原文:Structured Logging: The Best Friend You’ll Want When Things Go Wrongdocker

介紹

   在這篇文章裏,咱們重點介紹結構化日誌。咱們討論是是什麼,爲何好,以及如何構建一個框架更好的與咱們當前基於Elastic stack的日誌後端集成,使咱們更好,更高效記日誌。數據庫

   結構化日誌是咱們竭力作的很大一部分,結構化日誌能讓咱們減小bug解決時間(MTTR),中斷時幫助開發人員更快地緩解問題。後端

什麼是日誌?

   日誌是包含有關係統中發生一些事件的幾行文本信息,而且起着幫助咱們瞭解後端正在發生的事情的重要做用。日誌一般放置於重要事件的代碼中(例如:成功操做某些數據庫,或者指派司機給乘客),或咱們感興趣留意的代碼中。架構

   當有錯誤時,正常開發者作的第一件事情就是查看日誌——有點像瀏覽系統的歷史,而且找出發生了什麼。所以,在服務中斷、錯誤、構建失敗時,日誌成爲開發人員最好的朋友。併發

如今的日誌具備不一樣的格式和功能框架

  • 日誌格式:從基於鍵-值(像syslog)到很是結構化和詳細(像JSON)。因爲日誌主要用於開發者的眼睛,所以日誌詳細和結構化程度決定了開發者查詢和閱讀日誌的速度。數據越結構化——每行日誌就越大,儘管更易於查詢和包含更豐富的信息。
  • 等級日誌(或日誌等級):不一樣等級對應着不一樣重要性的日誌。可見性可限制單個等級,僅限於某些重要性或等級以上的日誌(如:僅記錄WARN和更高等級)。一般日誌等級在生產環境中是靜態的,查找DEBUG等級的日誌一般須要從新部署。
  • 日誌集後端:日誌有不一樣的日誌集後端,也意味着不一樣的後端(如:Splunk, Kibana等)決定了日誌的樣式或者用他們能作些什麼。一些人可能比其餘人使用的更多。
  • 因果順序:日誌可能也可能不會保存寫入的實際時間。這很重要,由於時間的確切程度決定了咱們經過日誌預測事件順序的準確程度。
  • 日誌關聯:咱們服務於後端服務的無數請求。能看到與特定請求或特定事件相關的全部日誌,幫助咱們深刻到特定請求的相關信息中(例如:試圖登記騎乘的特定乘客)。

   將此與過多可用的日誌庫結合起來,很容易讓開發人員懵逼,沒法決定使用什麼。此外,每一個庫都有本身的優缺點,所以討論可能很快變得主觀化和極端化——所以,爲你的程序選擇適當的庫和後端很是重要。異步

   咱們在Grab中使用不一樣類型的日誌庫。然而,隨着需求的變化——咱們也發現咱們本身正在從新評估日誌策略。分佈式

Grab中日誌的情況

   Grab的Golang服務的數量持續增加。大多數服務使用syslog鍵值格式的日誌,因爲簡單,而且容易讀寫,所以是服務端程序中最多見的格式。全部這些日誌多是少許的公共庫實現,不一樣的服務直接引用這些庫來使用。函數

   咱們使用基於雲的SaaS供應商做爲這些日誌的前端,應用程序產出的日誌寫入文件中併發送給咱們的日誌供應商,從而能夠實時查看和查詢。很長一段時間裏使用的很是不錯,也無任何磕絆。

   然而,隨着時間推移,咱們的日誌清單上升到了史無前例的等級,發現咱們本身正在從新審視而且從新評估如何記日誌。出現的一些問題:

  • 減小日誌量的努力在某些程度上是成功的——但也是艱鉅而痛苦的。一部分緣由是幾乎全部的日誌都是單一的日誌等級——INFO

圖1:使用日誌等級

   這個問題不是在單個服務中,而是在全部服務都很廣泛。爲了緩解,有些服務對日誌抽樣,有些服務徹底刪除了日誌。後者會後患無窮,所以咱們必須改善日誌等級

  • 當時對咱們來講使用供應商有點昂貴,也有些顧慮——主要受限於DSL(查詢語言)。有不少優秀的開源的替代方案——Elastic stack是其中的一個。咱們的工程師確信咱們能夠管理咱們的日誌基礎架構並更好地管理成本——這致使了提議構建Elastic堆棧日誌集羣。 Elasticsearch比咱們當時的供應商強得多,並且咱們當前的庫不足以充分利用其功能,所以咱們須要在日誌中有更好的結構輕鬆與Elastic堆棧集成的庫。
  • 咱們的日誌庫中有些小問題:
    • 單一的初始化方案更難作單元測試
    • 單一的日誌接口減小了日誌核心功能的擴展性,由於幾乎全部服務直接導入日誌接口
    • 不支持多寫的開箱即用。
  • 若是咱們寫個日誌庫,必需要解決這些問題——並鼓勵使用最佳實踐
  • Grab的關鍵路徑(單個訂單流程請求通過的服務數量)大小已經增加了。平均,單個訂單請求涉及的微服務——每個都不一樣。所以,咱們大規模的運營時,頗有必要對單個請求容易地查看流經的全部的服務日誌——然而這不是咱們的日誌庫自動完成的功能。所以,咱們也想要更容易、更好的日誌關聯
  • 日誌是某個時間點的事件。事件的順序給與咱們系統發生了什麼的完整歷史。然而,咱們Golang服務的核心日誌庫沒有保存日誌的產生時間(而是寫入時間)。這致使了在幾微妙內產生的日誌形成混亂。這不只使開發者的生活更困難,並且幾乎沒法準確的得到系統的歷史事件。這就是咱們想改進和啓用因果排序的日誌——是瞭解系統事件的關鍵一步。

爲何改變?

   如上所述,咱們知道怎麼記日誌會有些問題。爲了最好的解決問題,而且在不影響現有的架構和服務儘可能的解決問題,決定從頭啓動一個新庫。這個庫應該能解決已知的問題,也包含修改現有的庫沒法實現的功能。扼要重述,咱們想解決的:

  • 增長日誌等級
  • 更好的日誌結構
  • 容易集成到Elastic stack
  • 鼓勵使用最佳實踐
  • 日誌關聯更容易、更好
  • 改進並啓用日誌的因果排序,以便更好地瞭解服務分配

調查結構化日誌。結構化日誌在全世界很是受歡迎,普遍被採用。容易的集成到咱們的Elastic stack中,也解決了咱們的不少痛點。

結構化日誌

   記住咱們以前的問題和需求,咱們用Golang新建了一個庫,有一下功能:

動態日誌等級

容許咱們在運行時從配置管理系統改變初始化的日誌等級——這是以前沒法作到和被鼓勵的。

如今,日誌等級更有實際意義。如今開發人員能夠用經常使用的WARN或者INFO部署,當出現問題時,僅更改配置就能更新日誌的等級到DEBUG,而且調試時他們的服務能輸出更多的日誌。這也有助於咱們控制日誌成本。咱們支持和咱們的配置管理系統簡單容易低集成。

日誌結構一致性

日誌天生是無結構化的,不像數據庫模式的死板或者自由格式的文本那樣無結構化。咱們 Elastic stack後端主要基於帶有映射(像鬆散的模式)的索引(相似於表)。爲此,咱們須要用一致性結構的JSON輸出(例如,在相同JSON字段下不能輸出整數和字符串,由於這會致使Elasticsearch索引失敗)。另外,咱們意識到咱們的主要目標之一是控制日誌成本,由於幾乎每一個字段的結構和索引都沒有意義——只添加對咱們有用的結構。

爲了解決這些,咱們構建一個容許咱們肯定地爲日誌添加結構。這是創建在咱們能夠用特殊的字段名和類型添加鍵值對的架構之上。根據該模式生成代碼,並使用生成的代碼確保事物的一致的格式且不會中斷。咱們稱這種模式(鍵名和類型對的集合)爲Common Grab Log Schema (CGLS)。咱們僅向CGLS中添加結構是很重要的——CGLS中包含的全部內容在不一樣的字段中格式化,其餘內容在生成的JSON中的單個字段中格式化。這有助於保持咱們的結構一直而且易於使用Elastic stack

圖2:Golang後端服務的通用抓取日誌架構概述

支持使用Grab-Kit即插即用

   咱們經過對Grab-Kit內部支持進行初始化,使用簡單而且開箱即用,所以,開發者無需修改便可使用。此外,做爲總體的一部分,咱們基於追蹤中存在的請求ID添加了自動的日誌關聯,這確保了具備該跟蹤ID的特定請求生成全部日誌。

可配置的日誌格式

   咱們主要的需求是構建一個有足夠的表現和一致性,以便更好的與Elastic stack後端集成,在下游無需通過花哨的解析。所以,該日誌庫具備的表現力和可配置性足以容許任何日誌格式(咱們能夠對不一樣功能的用例寫不一樣的日誌格式,例如,開發設置中的可讀格式和產品設置中的輸出JSON格式),默認是輸出JSON格式。這確保了咱們能夠生成與Elastic stack兼容的日誌輸出,但仍然能夠針對不一樣的用例進行配置。

支持不一樣格式的多寫

做爲日誌庫功能擴展的一部分,咱們須要足夠的可配置性,以便可以發送不一樣的日誌到有不同的設置的不一樣地方。例如,異步發送易讀的格式的FATAL日誌到Slack,同時將全部的經常使用日誌發送的咱們的Elastic stack後端。該日誌庫包括支持將這些」核心「鏈接到任意可能的程度——確保這些日誌器被用在此類高度專業化的狀況。

開發中相似生產環境的日誌

開發者從一開始就看到了控制檯日誌,然而,有結構化的JSON日誌通常認爲是產品的日誌,並且更易於搜索。爲了更好的在開發過程當中利用,而且讓開發者直接在Kibana中看到他們的日誌,咱們提供了docker化版本的Kibana,能夠在本地運行以接收結構化日誌。這可讓開發者直接使用結構化日誌而且在Kibana中看到——就像生產環境中那樣。

這個日誌庫讓咱們用更好的方式打日誌。最顯而易見的影響是咱們能簡單的訪問日誌,可以使用更好的過濾和條件來更好的查詢。

圖3:開發中相似生產環境的日誌

因果順序

有精確歷史記錄的事件讓在生產環境的系統中調試問題更容易——由於僅看到歷史記錄就能很快的猜想出錯誤緣由而且修復。爲此,結構化日誌庫在日誌器中添加了精確的寫入時的納秒時間戳。這與相似JSON結構化的格式結合讓根據這些字段排序全部的日誌成爲可能——所以咱們以他們發生的確切的順序看日誌——在日誌中實現因果順序。這是看起來的低調,可是使調試容易的強大功能。

圖4:使用Y'ALL的因果順序日誌

但爲何要結構化記日誌?

如今你已經知道了咱們日誌策略背後的歷史和緣由,讓咱們總結下從中得到的福利。

一開始,有明肯定義和結構化(像JSON)的日誌有不少好處,包括但不只限於:

  • 更好的根本緣由分析: 使用結構化日誌,咱們能夠提取和執行更強大的查詢,這對於非結構化的日誌是不可能的。開發人員能夠在查找與狀況相關的日誌時進行更多信息性查詢。不只於此,日誌關聯和因果順序對更好的理解分佈式日誌成爲可能。不像無結構的數據,咱們僅侷限於全文或者少數的日誌類型,結構化日誌達到了全新的水平。

  • 更高的透明度或更好的可觀察性:使用結構化日誌,提升了系統發生狀況的可見性——由於如今你能夠用更好,更有變現力的方式記錄信息。這可以讓你更透明的觀察系統發生的狀況,而且讓你係統在更長的週期內更容易的維護和調試。
  • 更好的標準化:使用單一,定義明確,結構化的方式去記日誌使咱們的日誌標準化——這減小了經過日誌肯定系統發生的事件的認知,而且更易採用。而不是經過100種不一樣類型的日誌,而是隻有一種格式。這也是日誌庫的目標之一——經過Golang後端服務日誌庫的標準化使用。

咱們還得到額外的好處:

  • 動態日誌等級: 使代碼中的日誌等級富有意義——咱們能夠用基線設置部署,而且僅當咱們須要的時候切換到更低的等級(debug等級的日誌)。有助於咱們下降日誌成本,一樣也減小開發人員在調試時一般須要搜查的無關日誌。
  • 日誌中面向將來的一致性: 經過採用通用模式,確保咱們堅持使用相同的模式,即便明天咱們的日誌基礎架構改變——咱們作好將來的準備。咱們能夠簡單在咱們的日誌器中暴露一個函數,而不是手動地指定要記錄的內容。

  • 開發中相似生產的日誌環境: docker化的Kibana使開發人員享受到與生產環境中的Kibana一樣的好處。這也更激勵開發認識使用Elastic stack並探索它的功能,像基於日誌數據構建儀表盤,有更好的查看方式,等等。

但願你喜歡這篇文章並發現它的有用之處。歡迎提出意見和更正。

相關文章
相關標籤/搜索