關於代碼中寫日志的一些思考




在我工作過的公司里,有一家公司的代碼里全部都是日志,而另外一家則是一行日志記錄都沒有。

但是在線上環境中,日志記錄是非常重要的,它能幫我們快速定位問題所在。整理之后,我發現下面幾點關於日志的建議是非常有效的。

沒有提示就是最好的提示

用過 類 Unix 系統的人都知道,Unix 系統的提示哲學是沒提示就是最好的提示。如果一個命令執行成功了,那么系統是不會提示的,只有當執行出錯的時候才會報錯。反觀我們的代碼,一般情況下我們的代碼99%都是正常的執行路徑,不會出現邏輯異常或代碼異常,只有1%的時候才會出現異常。在這種情況下,我們當然是選擇在那1%的地方進行日志輸出了。所以寫日志我們應該達成一個共識,那就是:只在邏輯異常或代碼異常的地方加上日志提示。

你應該考慮閱讀者

在我們討論“沒有提示就是最好的提示”時,我們只是站在程序猿的角度來思考如何寫提示,但是有時候閱讀日志的人可能是

  • 一個在調試產品問題的系統管理員或者運維工程師
  • 一個嘗試自己解決問題的終端用戶(想象一個客戶端或桌面程序)
  • 一個在開發中debug,或者在解決產品問題的開發者

對於不同的”誰”,你將要寫下的log信息的內容,上下文,類別和level會大不同。開發者了解程序內部,所以給他的log信息可以比給終端用戶的復雜得多。所以為了兼顧其他人對於程序的理解,有時候需要在一些重要的業務邏輯里增加一些與邏輯異常或代碼異常無關的一些代碼,如:在P2P項目中,當用戶完成充值的時候,你需要打印出用戶充值成功的信息。

簡單地說:我們不僅僅要在邏輯異常或代碼異常的地方加上日志提示,而且也需要在每個環節完成的時候輸出日志提示,這樣會便於我們進行線上問題的定位。

你應在適當級別上進行log

如果你使用了開源日志框架,那么你要對你程序中每一個log語句使用不同的log級別。其中最困難的一個任務是找出這個log應該是什么級別。在 SLF4J 中常用的級別有這么幾個,他們的日志級別從高到低是:ERROR > WARN > INFO > DEBUG。當代碼中的級別大於設置文件中設置的級別時,對應的日志就會輸出。

以下是我的一些建議:

  • DEBUG Level:主要用於開發人員進行代碼調試的時候使用。比如當開發人員發現了一種異常情況,這時候我們就需要輸出一些日志信息,那么這時候就應該使用 log.debug() 進行日志記錄。
  • INFO Level:重要的業務邏輯處理完成。在理想情況下, INFO的日志信息要能讓高級用戶和系統管理員理解, 並從日志信息中能知道系統當前的運行狀態。比如對於一個機票預訂系統來說, 當一個用戶完成一個機票預訂操作之后, 提醒應該給出"誰預訂了從A到B的機票"。另一個需要輸出INFO信息的地方就是一個系統操作引起系統的狀態發生了重大變化(比如數據庫更新, 過多的系統請求)。
  • WARN Level:系統能繼續運行,但是必須引起關注。對於存在的問題一般可以分為兩類: 一種系統存在明顯的問題(比如數據不可用), 另一種就是系統存在潛在的問題, 需要引起注意或者給出一些建議(比如,系統運行在安全模式或者訪問當前系統的賬號存在安全隱患)。總之就是系統仍然可用,但是最好進行檢查和調整。
  • ERROR Level:系統發生了嚴重的錯誤, 必須馬上進行處理, 否則系統將無法繼續運行。比如:NullPointerException、內存不足等。

正確的使用輸出模式

log 輸出模式可以幫助我們在日志中增加一些清晰的上下文信息,不過對添加的信息還是要多加小心。比如說,如果你是每小時輸出一個文件,這樣你的日志文件名中已經包含了部分日期時間信息,因此就沒必要在日志中再包含這些信息。另外在多線程環境下也不要在自己在日志中包含線程名稱, 因為這個也可以在模式中配置。

根據我的經驗, 一個理想的日志模式將包含下列信息:

  • 當前時間(不需要包含日志, 精確到毫秒)
  • 日志級別(如果你關心這個)
  • 線程名稱
  • 簡單的日志名(非全限定名的那種)
  • 日志描述信息

根據實際情況,你還可以添加下面一些信息:

  • 文件名
  • 類名(我想這個應該是全限定名吧)
  • 代碼行號

但要記得,不要用反射去自己獲取文件名、類名等信息再去作為日志信息輸出,這樣會極大降低日志效率。如果需要,你完成可以在 SLF4J 的配置文件中進行配置。

日志信息應該用英語

如果你的程序被大多數人使用,而你又沒有足夠的資源做國際化,英語會成為你的不二之選。

你應該給log帶上上下文

沒有什么比這樣的log信息更糟的了

Transaction failed

或者

User operation succeeds

又或是API異常時:

java.lang.IndexOutOfBoundsException

沒有相應的上下文,這些信息不過是噪音,它們不會對調試過程中有意義的數值或是空間起作用(add value and consume space)。帶上上下文的信息要有價值得多,例如:

Transaction 234632 failed: cc number checksum incorrect

或是:

User 54543 successfully registered e-mail<a href="mailto:user@domain.com">user@domain.com</a>

又或是:

IndexOutOfBoundsException: index 12 is greater than collection size 10

在上面這一例子中的異常,如果你想把它傳播開, 確保在處理的時候帶上與當前level相應的上下文,讓調試更簡單,如下一個 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() );
     }
}

使用占位符而不是拼接字符串

使用占位符更有利於代碼的閱讀,並且可以提高性能。因此你應該寫這樣的日志記錄:

logger.info("User {} login.", userId);

而不是這樣的:

logger.info("User " + userId + " login.");

日志不能打斷業務邏輯

在下面的日志記錄中,如果 user 為空,那么將會拋出異常,那么就會直接導致程序中斷。

logger.info("User {} login fail.", user.getUserInfo);

因此在日志記錄中,要避免日志中發生異常而導致正常的業務邏輯受到影響,這是絕對不允許出現的。

紙上得來終覺淺,說了這么多,但還是要實踐時不斷去應用,去提取適合自己的日志規則,這樣才能寫出好的代碼。

 

出處:http://www.cnblogs.com/chanshuyi/p/how_to_write_log_in_code.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM