结构化日志:出错时你最想要的好朋友
目錄
- 介紹
- 什么是日志?
- Grab中日志的狀況
- 為什么改變?
- 結(jié)構(gòu)化日志
- 支持不同格式的多寫
- 開發(fā)中類似生產(chǎn)環(huán)境的日志
- 因果順序
- 但為什么要結(jié)構(gòu)化記日志?
原文:Structured Logging: The Best Friend You’ll Want When Things Go Wrong
介紹
?? 在這篇文章里,我們重點(diǎn)介紹結(jié)構(gòu)化日志。我們討論是是什么,為什么好,以及如何構(gòu)建一個(gè)框架更好的與我們當(dāng)前基于Elastic stack的日志后端集成,使我們更好,更高效記日志。
?? 結(jié)構(gòu)化日志是我們竭力做的很大一部分,結(jié)構(gòu)化日志能讓我們減少bug解決時(shí)間(MTTR),中斷時(shí)幫助開發(fā)人員更快地緩解問題。
什么是日志?
?? 日志是包含有關(guān)系統(tǒng)中發(fā)生一些事件的幾行文本信息,并且起著幫助我們了解后端正在發(fā)生的事情的重要作用。日志通常放置于重要事件的代碼中(例如:成功操作某些數(shù)據(jù)庫,或者指派司機(jī)給乘客),或我們感興趣留意的代碼中。
?? 當(dāng)有錯(cuò)誤時(shí),正常開發(fā)者做的第一件事情就是查看日志——有點(diǎn)像瀏覽系統(tǒng)的歷史,并且找出發(fā)生了什么。因此,在服務(wù)中斷、錯(cuò)誤、構(gòu)建失敗時(shí),日志成為開發(fā)人員最好的朋友。
現(xiàn)在的日志具有不同的格式和功能
- 日志格式:從基于鍵-值(像syslog)到非常結(jié)構(gòu)化和詳細(xì)(像JSON)。由于日志主要用于開發(fā)者的眼睛,因此日志詳細(xì)和結(jié)構(gòu)化程度決定了開發(fā)者查詢和閱讀日志的速度。數(shù)據(jù)越結(jié)構(gòu)化——每行日志就越大,盡管更易于查詢和包含更豐富的信息。
- 等級日志(或日志等級):不同等級對應(yīng)著不同重要性的日志。可見性可限制單個(gè)等級,僅限于某些重要性或等級以上的日志(如:僅記錄WARN和更高等級)。通常日志等級在生產(chǎn)環(huán)境中是靜態(tài)的,查找DEBUG等級的日志通常需要重新部署。
- 日志集后端:日志有不同的日志集后端,也意味著不同的后端(如:Splunk, Kibana等)決定了日志的樣式或者用他們能做些什么。一些人可能比其他人使用的更多。
- 因果順序:日志可能也可能不會(huì)保存寫入的實(shí)際時(shí)間。這很重要,因?yàn)闀r(shí)間的確切程度決定了我們通過日志預(yù)測事件順序的準(zhǔn)確程度。
- 日志關(guān)聯(lián):我們服務(wù)于后端服務(wù)的無數(shù)請求。能看到與特定請求或特定事件相關(guān)的所有日志,幫助我們深入到特定請求的相關(guān)信息中(例如:試圖登記騎乘的特定乘客)。
?? 將此與過多可用的日志庫結(jié)合起來,很容易讓開發(fā)人員懵逼,無法決定使用什么。此外,每個(gè)庫都有自己的優(yōu)缺點(diǎn),因此討論可能很快變得主觀化和極端化——因此,為你的程序選擇適當(dāng)?shù)膸旌秃蠖朔浅V匾?/p>
?? 我們在Grab中使用不同類型的日志庫。然而,隨著需求的變化——我們也發(fā)現(xiàn)我們自己正在重新評估日志策略。
Grab中日志的狀況
?? Grab的Golang服務(wù)的數(shù)量持續(xù)增長。大多數(shù)服務(wù)使用syslog鍵值格式的日志,由于簡單,并且容易讀寫,因此是服務(wù)端程序中最常見的格式。所有這些日志可能是少量的公共庫實(shí)現(xiàn),不同的服務(wù)直接引用這些庫來使用。
?? 我們使用基于云的SaaS供應(yīng)商作為這些日志的前端,應(yīng)用程序產(chǎn)出的日志寫入文件中并發(fā)送給我們的日志供應(yīng)商,從而可以實(shí)時(shí)查看和查詢。很長一段時(shí)間里使用的非常不錯(cuò),也無任何磕絆。
?? 然而,隨著時(shí)間推移,我們的日志清單上升到了前所未有的等級,發(fā)現(xiàn)我們自己正在重新審視并且重新評估如何記日志。出現(xiàn)的一些問題:
- 減少日志量的努力在某些程度上是成功的——但也是艱巨而痛苦的。一部分原因是幾乎所有的日志都是單一的日志等級——INFO
?? 這個(gè)問題不是在單個(gè)服務(wù)中,而是在所有服務(wù)都很普遍。為了緩解,有些服務(wù)對日志抽樣,有些服務(wù)完全刪除了日志。后者會(huì)后患無窮,因此我們必須改善日志等級。
- 當(dāng)時(shí)對我們來說使用供應(yīng)商有點(diǎn)昂貴,也有些顧慮——主要受限于DSL(查詢語言)。有很多優(yōu)秀的開源的替代方案——Elastic stack是其中的一個(gè)。我們的工程師確信我們可以管理我們的日志基礎(chǔ)架構(gòu)并更好地管理成本——這導(dǎo)致了提議構(gòu)建Elastic堆棧日志集群。 Elasticsearch比我們當(dāng)時(shí)的供應(yīng)商強(qiáng)得多,而且我們當(dāng)前的庫不足以充分利用其功能,因此我們需要在日志中有更好的結(jié)構(gòu)并輕松與Elastic堆棧集成的庫。
- 我們的日志庫中有些小問題:
- 單一的初始化方案更難做單元測試
- 單一的日志接口減少了日志核心功能的擴(kuò)展性,因?yàn)閹缀跛蟹?wù)直接導(dǎo)入日志接口
- 不支持多寫的開箱即用。
- 如果我們寫個(gè)日志庫,必須要解決這些問題——并鼓勵(lì)使用最佳實(shí)踐
- Grab的關(guān)鍵路徑(單個(gè)訂單流程請求經(jīng)過的服務(wù)數(shù)量)大小已經(jīng)增長了。平均,單個(gè)訂單請求涉及的微服務(wù)——每一個(gè)都不同。因此,我們大規(guī)模的運(yùn)營時(shí),很有必要對單個(gè)請求容易地查看流經(jīng)的所有的服務(wù)日志——然而這不是我們的日志庫自動(dòng)完成的功能。因此,我們也想要更容易、更好的日志關(guān)聯(lián)。
- 日志是某個(gè)時(shí)間點(diǎn)的事件。事件的順序給與我們系統(tǒng)發(fā)生了什么的完整歷史。然而,我們Golang服務(wù)的核心日志庫沒有保存日志的產(chǎn)生時(shí)間(而是寫入時(shí)間)。這導(dǎo)致了在幾微妙內(nèi)產(chǎn)生的日志造成混亂。這不僅使開發(fā)者的生活更困難,而且?guī)缀鯚o法準(zhǔn)確的獲得系統(tǒng)的歷史事件。這就是我們想改進(jìn)和啟用因果排序的日志——是了解系統(tǒng)事件的關(guān)鍵一步。
為什么改變?
?? 如上所述,我們知道怎么記日志會(huì)有些問題。為了最好的解決問題,并且在不影響現(xiàn)有的架構(gòu)和服務(wù)盡量的解決問題,決定從頭啟動(dòng)一個(gè)新庫。這個(gè)庫應(yīng)該能解決已知的問題,也包含修改現(xiàn)有的庫無法實(shí)現(xiàn)的功能。扼要重述,我們想解決的:
- 增加日志等級
- 更好的日志結(jié)構(gòu)
- 容易集成到Elastic stack
- 鼓勵(lì)使用最佳實(shí)踐
- 日志關(guān)聯(lián)更容易、更好
- 改進(jìn)并啟用日志的因果排序,以便更好地了解服務(wù)分配
調(diào)查結(jié)構(gòu)化日志。結(jié)構(gòu)化日志在全世界非常受歡迎,廣泛被采用。容易的集成到我們的Elastic stack中,也解決了我們的很多痛點(diǎn)。
結(jié)構(gòu)化日志
?? 記住我們之前的問題和需求,我們用Golang新建了一個(gè)庫,有一下功能:
動(dòng)態(tài)日志等級
允許我們在運(yùn)行時(shí)從配置管理系統(tǒng)改變初始化的日志等級——這是之前無法做到和被鼓勵(lì)的。
現(xiàn)在,日志等級更有實(shí)際意義。現(xiàn)在開發(fā)人員可以用常用的WARN或者INFO部署,當(dāng)出現(xiàn)問題時(shí),僅更改配置就能更新日志的等級到DEBUG,并且調(diào)試時(shí)他們的服務(wù)能輸出更多的日志。這也有助于我們控制日志成本。我們支持和我們的配置管理系統(tǒng)簡單容易低集成。
日志結(jié)構(gòu)一致性
日志天生是無結(jié)構(gòu)化的,不像數(shù)據(jù)庫模式的死板或者自由格式的文本那樣無結(jié)構(gòu)化。我們 Elastic stack后端主要基于帶有映射(像松散的模式)的索引(類似于表)。為此,我們需要用一致性結(jié)構(gòu)的JSON輸出(例如,在相同JSON字段下不能輸出整數(shù)和字符串,因?yàn)檫@會(huì)導(dǎo)致Elasticsearch索引失敗)。另外,我們意識(shí)到我們的主要目標(biāo)之一是控制日志成本,因?yàn)閹缀趺總€(gè)字段的結(jié)構(gòu)和索引都沒有意義——只添加對我們有用的結(jié)構(gòu)。
為了解決這些,我們構(gòu)建一個(gè)允許我們確定地為日志添加結(jié)構(gòu)。這是建立在我們可以用特殊的字段名和類型添加鍵值對的架構(gòu)之上。根據(jù)該模式生成代碼,并使用生成的代碼確保事物的一致的格式且不會(huì)中斷。我們稱這種模式(鍵名和類型對的集合)為Common Grab Log Schema (CGLS)。我們僅向CGLS中添加結(jié)構(gòu)是很重要的——CGLS中包含的所有內(nèi)容在不同的字段中格式化,其他內(nèi)容在生成的JSON中的單個(gè)字段中格式化。這有助于保持我們的結(jié)構(gòu)一直并且易于使用Elastic stack。
圖2:Golang后端服務(wù)的通用抓取日志架構(gòu)概述支持使用Grab-Kit即插即用
?? 我們通過對Grab-Kit內(nèi)部支持進(jìn)行初始化,使用簡單并且開箱即用,因此,開發(fā)者無需修改即可使用。此外,作為整體的一部分,我們基于追蹤中存在的請求ID添加了自動(dòng)的日志關(guān)聯(lián),這確保了具有該跟蹤ID的特定請求生成所有日志。
可配置的日志格式
?? 我們主要的需求是構(gòu)建一個(gè)有足夠的表現(xiàn)和一致性,以便更好的與Elastic stack后端集成,在下游無需經(jīng)過花哨的解析。因此,該日志庫具有的表現(xiàn)力和可配置性足以允許任何日志格式(我們可以對不同功能的用例寫不同的日志格式,例如,開發(fā)設(shè)置中的可讀格式和產(chǎn)品設(shè)置中的輸出JSON格式),默認(rèn)是輸出JSON格式。這確保了我們可以生成與Elastic stack兼容的日志輸出,但仍然可以針對不同的用例進(jìn)行配置。
支持不同格式的多寫
作為日志庫功能擴(kuò)展的一部分,我們需要足夠的可配置性,以便能夠發(fā)送不同的日志到有不一樣的設(shè)置的不同地方。例如,異步發(fā)送易讀的格式的FATAL日志到Slack,同時(shí)將所有的常用日志發(fā)送的我們的Elastic stack后端。該日志庫包括支持將這些”核心“連接到任意可能的程度——確保這些日志器被用在此類高度專業(yè)化的情況。
開發(fā)中類似生產(chǎn)環(huán)境的日志
開發(fā)者從一開始就看到了控制臺(tái)日志,然而,有結(jié)構(gòu)化的JSON日志一般認(rèn)為是產(chǎn)品的日志,而且更易于搜索。為了更好的在開發(fā)過程中利用,并且讓開發(fā)者直接在Kibana中看到他們的日志,我們提供了docker化版本的Kibana,可以在本地運(yùn)行以接收結(jié)構(gòu)化日志。這可以讓開發(fā)者直接使用結(jié)構(gòu)化日志并且在Kibana中看到——就像生產(chǎn)環(huán)境中那樣。
這個(gè)日志庫讓我們用更好的方式打日志。最顯而易見的影響是我們能簡單的訪問日志,能夠使用更好的過濾和條件來更好的查詢。
圖3:開發(fā)中類似生產(chǎn)環(huán)境的日志因果順序
有精確歷史記錄的事件讓在生產(chǎn)環(huán)境的系統(tǒng)中調(diào)試問題更容易——因?yàn)閮H看到歷史記錄就能很快的猜測出錯(cuò)誤原因并且修復(fù)。為此,結(jié)構(gòu)化日志庫在日志器中添加了精確的寫入時(shí)的納秒時(shí)間戳。這與類似JSON結(jié)構(gòu)化的格式結(jié)合讓根據(jù)這些字段排序所有的日志成為可能——因此我們以他們發(fā)生的確切的順序看日志——在日志中實(shí)現(xiàn)因果順序。這是看起來的低調(diào),但是使調(diào)試容易的強(qiáng)大功能。
圖4:使用Y'ALL的因果順序日志但為什么要結(jié)構(gòu)化記日志?
現(xiàn)在你已經(jīng)知道了我們?nèi)罩静呗员澈蟮臍v史和原因,讓我們總結(jié)下從中獲得的福利。
一開始,有明確定義和結(jié)構(gòu)化(像JSON)的日志有很多好處,包括但不僅限于:
更好的根本原因分析: 使用結(jié)構(gòu)化日志,我們可以提取和執(zhí)行更強(qiáng)大的查詢,這對于非結(jié)構(gòu)化的日志是不可能的。開發(fā)人員可以在查找與情況相關(guān)的日志時(shí)進(jìn)行更多信息性查詢。不僅于此,日志關(guān)聯(lián)和因果順序?qū)Ω玫睦斫夥植际饺罩境蔀榭赡堋2幌駸o結(jié)構(gòu)的數(shù)據(jù),我們僅局限于全文或者少數(shù)的日志類型,結(jié)構(gòu)化日志達(dá)到了全新的水平。
- 更高的透明度或更好的可觀察性:使用結(jié)構(gòu)化日志,提高了系統(tǒng)發(fā)生情況的可見性——因?yàn)楝F(xiàn)在你可以用更好,更有變現(xiàn)力的方式記錄信息。這可讓你更透明的觀察系統(tǒng)發(fā)生的情況,并且讓你系統(tǒng)在更長的周期內(nèi)更容易的維護(hù)和調(diào)試。
更好的標(biāo)準(zhǔn)化:使用單一,定義明確,結(jié)構(gòu)化的方式去記日志使我們的日志標(biāo)準(zhǔn)化——這減少了通過日志確定系統(tǒng)發(fā)生的事件的認(rèn)知,并且更易采用。而不是通過100種不同類型的日志,而是只有一種格式。這也是日志庫的目標(biāo)之一——通過Golang后端服務(wù)日志庫的標(biāo)準(zhǔn)化使用。
我們還獲得額外的好處:
- 動(dòng)態(tài)日志等級: 使代碼中的日志等級富有意義——我們可以用基線設(shè)置部署,并且僅當(dāng)我們需要的時(shí)候切換到更低的等級(debug等級的日志)。有助于我們降低日志成本,同樣也減少開發(fā)人員在調(diào)試時(shí)通常需要搜查的無關(guān)日志。
日志中面向未來的一致性: 通過采用通用模式,確保我們堅(jiān)持使用相同的模式,即使明天我們的日志基礎(chǔ)架構(gòu)改變——我們做好未來的準(zhǔn)備。我們可以簡單在我們的日志器中暴露一個(gè)函數(shù),而不是手動(dòng)地指定要記錄的內(nèi)容。
開發(fā)中類似生產(chǎn)的日志環(huán)境: docker化的Kibana使開發(fā)人員享受到與生產(chǎn)環(huán)境中的Kibana同樣的好處。這也更激勵(lì)開發(fā)認(rèn)識(shí)使用Elastic stack并探索它的功能,像基于日志數(shù)據(jù)構(gòu)建儀表盤,有更好的查看方式,等等。
希望你喜歡這篇文章并發(fā)現(xiàn)它的有用之處。歡迎提出意見和更正。
轉(zhuǎn)載于:https://www.cnblogs.com/YYRise/p/10749430.html
總結(jié)
以上是生活随笔為你收集整理的结构化日志:出错时你最想要的好朋友的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 团队计划会议
- 下一篇: 转载 Net多线程编程—System.T