新聞中心
歡迎在新的一年來到我的博客。在一個巴黎devops maillist上回復了一個關于監(jiān)控和日志監(jiān)控之后,我想起了很久以前我的一個博客計劃。

創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設、高性價比淮安網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式淮安網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設找我們,業(yè)務覆蓋淮安地區(qū)。費用合理售后完善,十余年實體公司更值得信賴。
盡管在寫這篇博文的時候,我是在負責運維工作,不過本文主要是寫給開發(fā)者的。
對我來說,明白如何記錄日志和記錄什么,是軟件工程師必須明了的最艱巨的任務之一。之所以這么說,是因為這項任務與預測(divination)類 似,你不知道當你要調試的時候需要些什么信息……我希望這10個建議能幫助你更好地在應用程序中記錄日志,讓運維工程師們受益。:)
1. 你不應自己寫log
絕對不要,即便是用printf或者是自己寫入到log文件,又或自己處理logrotate。請給你的運維同志們省省心,調用標準庫或者系統(tǒng)API來完成它。
這樣,你可以保證程序的運行與其他系統(tǒng)組件好好相處,把log寫到正確的位置或者網(wǎng)絡服務上,而不需要專門的系統(tǒng)配置。
假如你要使用系統(tǒng)API,也就是syslog(3),學習好怎么用它。
如果你更喜歡用logging庫,在Java里面你有很多選擇,例如Log4j,JCL,slf4j和logback。我最喜歡用slf4j和logback的組合,因為它們特別給力,而且相對地容易配置(還允許使用JMX進行配置或者重載配置文件)。
slf4j最好的是你可以修改logging控制臺的位置。如果你在編寫一個庫,這會變得非常重要,因為這可以讓庫的使用者使用自己的logging控制臺而不需要修改你的庫。
其他語言當然也有多種logging庫,例如ruby的Log4r,stdlib logger,和幾近完美的Jordan Sissel’s Ruby-cabin。
如果你想糾結CPU占用問題,那么你不用看這篇文章了。還有,不要把log語句放在緊內部循環(huán)體內,否則你永遠看不出區(qū)別來。
2. 你應在適當級別上進行l(wèi)og
如果你遵循了上述第一點的做法,接下來你要對你程序中每一個log語句使用不同的log級別。其中最困難的一個任務是找出這個log應該是什么級別
以下是我的一些建議:
- TRACE level: 如果使用在生產(chǎn)環(huán)境中,這是一個代碼異味(code smell)。它可以用于開發(fā)過程中追蹤bug,但不要提交到你的版本控制系統(tǒng)
- DEBUG level: 把一切東西都記錄在這里。這在debug過程中最常用到。我主張在進入生產(chǎn)階段前減少debug語句的數(shù)量,只留下最有意義的部分,在調試(troubleshooting)的時候激活。
- INFO level: 把用戶行為(user-driven)和系統(tǒng)的特定行為(例如計劃任務…)
- NOTICE level: 這是生產(chǎn)環(huán)境中使用的級別。把一切不認為是錯誤的,可以記錄的事件都log起來
- WARN level: 記錄在這個級別的事件都有可能成為一個error。例如,一次調用數(shù)據(jù)庫使用的時間超過了預設時間,或者內存緩存即將到達容量上限。這可以讓你適當?shù)匕l(fā)出警報,或者在調試時更好地理解系統(tǒng)在failure之前做了些什么
- ERROR level: 把每一個錯誤條件都記錄在這。例如API調用返回了錯誤,或是內部錯誤條件
- FATAL level: 末日來了。它極少被用到,在實際程序中也不應該出現(xiàn)多少。在這個級別上進行l(wèi)og意味著程序要結束了。例如一個網(wǎng)絡守護進程無法bind到socket上,那么它唯一能做的就只有l(wèi)og到這里,然后退出運行。
記住,在你的程序中,默認的運行級別是高度可變的。例如我通常用INFO運行我的服務端代碼,但是我的桌面程序用的是DEBUG。這是因為你很難在 一臺你沒有接入權限的機器上進行調試,但你在做用戶服務時,比起教他們怎么修改log level再把生成的log發(fā)給你,我的做法可以讓你輕松得多。當然你可以有其他的做法:)
3. honor the log category
我在第一點中提到的大部分logging庫允許指定一個logging類別。它可以分類log信息,并基于logging框架的配置,在最后以某一形式進行l(wèi)og或是不進行。
通常,Java開發(fā)者在log語句處使用完整,合格的類名作為類別名。如果你的程序遵循單一職責原則(Single responsibility principle,原文有誤),這種模式還不錯。
在Java的logging庫中,Log類別是按等級劃分的,例如在 com.daysofwonder.ranking.ELORankingComputation會匹配到頂級的 com.daysofwonder.ranking。這可以讓運營工程師配置一個對此類別下指定的所有ranking子系統(tǒng)作用的logging。如果需 要的話,還可以同時生成子類別的logging配置。
拓展開來,我們講解一下特定情況下的調試。假設你在做一個應答用戶請求的服務端軟件(如REST API)。它正在對my.service.api.
4. 你應該寫有意義的log
這可能是最重要的建議了。沒有什么比你深刻理解程序內部,卻寫出含糊的log更糟了。
在你寫日志信息之前,總要提醒自己,有突發(fā)事件的時候,你唯一擁有的只有來自log文件,你必須從中明白發(fā)生了什么。這可能就是被開除和升職之間的微妙的差距。
當開發(fā)者寫log的時候,它(log語句)是直接寫在代碼環(huán)境中的,在各種條件中我們應該寫入基于當前環(huán)境的信息。不幸的是,在log文件中并沒有這些環(huán)境,這可能導致這些信息無法被理解。
解決這個情況(在寫warn和error level時尤為重要)的一個方法是,添加輔助信息到log信息中,如果做不到,那么改為把這個操作的作用寫下。
還有,不要讓一個log信息的內容基于上一個。這是因為前面的信息可能由于(與當前信息)處于不同的類別或者level而沒被寫入。更壞的情況是,它因多線程或異步操作,在另一個地方(或是以另一方式)出現(xiàn)。
5. 日志信息應該用英語
這個建議可能有點奇怪,尤其是對法國佬(French guy)來說。我還是認為英語遠比法語更簡煉,更適應技術語言。如果一個信息里面包含超過50%的英語單詞,你有什么理由去用法語寫log呢
把英法之爭丟一邊,下面是這個建議背后的原因:
- 英語意味著你的log是用ASCII編碼的。這非常重要,因為你不會真正知道log信息會發(fā)生什么,或是它被歸檔前經(jīng)過何種軟件層和介質。如果你 的信息里面使用了特殊字符集,乃至UTF-8,它可能并不會被正確地顯示(render),更糟的是,它可能在傳輸過程中被損壞,變得不可讀。不過這還有 個問題,log用戶輸入時,可能有各種字符集或者編碼。
- 如果你的程序被大多數(shù)人使用,而你又沒有足夠的資源做國際化,英語會成為你的不二之選。如果你有國際化,那么讓界面與終端用戶更親近(closer)(這通常不會是你的log)
- 如果你國際化了你的log(例如所有的warning和error level信息),給他們一個特定的有意義的錯誤碼。這樣,用戶做與語言無關的搜索,找到相關信息。這種良好的模式已經(jīng)在虛擬內存(VMS)操作系統(tǒng)中應 用了很久,而我必須承認它非常有用。如果你曾經(jīng)設計過這種模式,你還可以試試這種模式: APP-S-CODE 或者 APP-S-SUB-CODE,它們分別代表:
APP: 應用程序的3字縮寫
S: 嚴重程度的1字縮寫(例如D代表debug,I代表info)
SUB: 這個code所從屬的應用程序的子部分
CODE: 一個數(shù)字代號,指定這個問題中的錯誤
#p#
6. 你應該給log帶上上下文
沒有什么比這樣的log信息更糟的了
- Transaction failed
或是
- User operation succeeds
又或是API異常時:
- java.lang.IndexOutOfBoundsException
沒有相應的上下文,這些信息不過是噪音,它們不會對調試過程中有意義的數(shù)值或是空間起作用(add value and consume space)。
帶上上下文的信息要有價值得多,例如:
- Transaction 234632 failed: cc number checksum incorrect
或是
- User 54543 successfully registered e-mail[email protected]
又或是
- IndexOutOfBoundsException: index 12 is greater than collection size 10
在上面這一例子中的異常,如果你想把它傳播開, 確保在處理的時候帶上與當前l(fā)evel相應的上下文,讓調試更簡單,如下一個java的例子:
- public void storeUserRank(int userId,int rank,String game) {
- try {
- ...deal database ...
- } catch (DatabaseException de) {
- throw new RankingException("Can't store ranking for user "+userId+" in game "+ game + " because " + de.getMessage() );
- }
- }
這樣,rank API的上層客戶端就可以有足夠的上下文信息log這個error。更好的做法是讓上下文成為exception的參數(shù),而不是信息,如果需要的話,上層可以對它進行修正(use remediation)。
保留上下文的一個簡單方法是使用一些java logging庫的MDC實現(xiàn)。MDC是一個每線程關聯(lián)數(shù)組(per thread associative array)??梢孕薷膌ogger設置,讓每一行l(wèi)og總是輸出MDC內容。如果你的程序使用每線程模式,這可以幫助解決保留上下文的問題。這個 java的例子對給定的請求,使用MDC記錄每用戶的信息:
- class UserRequest {
- ...
- public void execute(int userid) {
- MDC.put("user",userid);
- // ... all logged message now will display the user=
for this thread context ... - log.info("Successful execution of request");
- // user request processing is now finished,no need to log our current user anymore
- MDC.remote("user");
- }
- }
提示,MDC系統(tǒng)在異步logging模式中的表現(xiàn)并不好,例如Akka的logging系統(tǒng)。因為MDC是保存在一個每線程存儲區(qū)域的,而且在異 步系統(tǒng)中你無法保證在寫入log的線程是有MDC的那一個。在這種情況下,你需要手動地使用每一個log語句來log這些上下文。
7. 你應該用機器可解析的格式來打日志
Log信息對人很友善,但是對機器就慘了。有時人工地讀這些log文件并不足夠,你需要進行一些自動化過程(例如通過警報和審查)?;蚴悄阆爰写鎯δ愕膌og,以進行搜索。
如下,如果你把log的上下文嵌在string中會發(fā)生什么:
- log.info("User {} plays {} in game {}",userId,card,gameId);
這會生成這樣的文本:
- 2013-01-1217:49:37,656[T1]INFOc.d.g.UserRequestUser1334563plays4ofspadesingame23425656
現(xiàn)在,如果你想使它可解析,你需要下面這個(未測試過的)正則表達式:
- /User(\d+)plays(.+)ingame(\d+)$/
好了,這并不輕松而且容易出錯,把它接入到你代碼中已有的string參數(shù)中。
這個方法怎么樣,我相信Jordan Sissel在他的ruby-cabin庫中第一次介紹的: 在你的log里面加入機器可解析格式的上下文。我們上述的例子中這樣可以使用JSON:
- 2013-01-1217:49:37,656[T1]INFOc.d.g.UserRequestUserplays{'user':1334563,'card':'4ofspade','game':23425656}
現(xiàn)在你的log分析器可以更容易地寫入,更直接地索引,而且你可以釋放logstash所有的威力。
8. 日志不宜太多或太少
這聽著貌似很愚蠢。log的數(shù)量是有一個合適的平衡的。
太多的log會使從中獲得有價值的東西變得困難。當人工地瀏覽這種十分混亂的log,嘗試調試產(chǎn)品在早上3點的一個問題可不是一個好事。
太少的log,你可能無法調試問題: 調試就像在拼一個困難的拼圖,你需要得到足夠的拼塊。
不幸的是,這沒有魔法般的規(guī)則去知道應該log些什么。所以需要嚴格地遵從第一第二點,程序可以變得很靈活,輕松地增減log的長度(verbosity)。
解決這個問題的一個方法是,在開發(fā)過程中盡可能多地進行l(wèi)og(不要被加入用于程序調試的log所迷惑)。當應用程序進入生產(chǎn)過程時,對生成的 log進行一次分析,根據(jù)所發(fā)現(xiàn)的問題增減log語句。尤其是在調試時,在你需要的部分,你可以有更多的上下文或logging,確保在下一個版本中加入 這些語句(可以的話,同時解決它來讓這個問題在記憶中保持新鮮)。當然,這需要運維人員和開發(fā)者之間大量的交流。
這是一個復雜的任務,但是我推薦你重構logging語句,如你重構代碼一樣多。這樣可以在產(chǎn)品的log和它的log語句的修改中有一個緊密的反饋循環(huán)。如果你的組織有一個連續(xù)的交付進程的話,它會十分有效,正如持續(xù)的重構。
Logging語句是與代碼注釋同級的代碼元數(shù)據(jù)。保持logging語句與代碼相同步是很重要的。沒什么比調試時獲得與所運行的代碼毫無關系的信息更糟了。
#p#
9. 你應該考慮閱讀者
為什么要對應用程序做log
唯一的答案是,在某一天會有人去讀它(或是它的意義)。更重要的是,猜猜誰會讀它,這是很有趣的事。對于不同的”誰”,你將要寫下的log信息的內容,上下文,類別和level會大不同。
這些”誰”包括:
- 一個嘗試自己解決問題的終端用戶(想象一個客戶端或桌面程序)
- 一個在調試產(chǎn)品問題的系統(tǒng)管理員或者運維工程師
- 一個在開發(fā)中debug,或者在解決產(chǎn)品問題的開發(fā)者
開發(fā)者了解程序內部,所以給他的log信息可以比給終端用戶的復雜得多。為你的目標閱讀者調整你的表達方式,乃至為此加入額外的類別(dedicate separate catagories)。
10. 你不應該只為調試而log
正如log會有不同的閱讀者,它也有不同的使用理由。即便調試是最顯而易見的閱讀log的目的,你同樣可以有效地把log用在:
- 審查: 有時商業(yè)上會有需求。這可以獲取與管理或者合法用戶的有意義的事件。通常會有一些語句描述這個系統(tǒng)中的用戶在做些什么(例如誰登錄了,誰在編輯……)
- 建檔: log是打上了時間戳的(有時是微妙級的),可以成為一個為程序各部分建檔的好工具。例如記錄一個操作的開始和結束,你可以自動化(通過解析log)或是在調試中,進行性能度量,而不需要把這些度量加到程序中。
- 統(tǒng)計: 如果你每次對一個特定事件(例如特定的錯誤或事件)進行l(wèi)og,你可以對運行中的程序(或用戶行為)進行有趣的統(tǒng)計。這可以添加(hook)到一個警報系統(tǒng)中去連續(xù)地發(fā)現(xiàn)大量error。
總結
我希望這可以幫助你生成更多有用的log。如果我忘記了一些必須的(對你而言)建議,請諒解。對了,如果你看了這篇博客之后并不能更好地進行l(wèi)og,我并不負責
如果這10個建議還不夠的話,盡管在評論中補充更多有用的建議。
標題名稱:寫給開發(fā)者:記錄日志的10個建議
當前網(wǎng)址:http://m.fisionsoft.com.cn/article/dpcidhg.html


咨詢
建站咨詢
