關於日誌打印的幾點建議以及非最佳實踐

  日誌的打印在軟件開發過程當中必不可少,通常分爲兩個大類:程序員

  • 操做日誌
  • 系統日誌

  操做日誌,主要針對的是用戶,例如在Photoshop軟件中會記錄本身操做的步驟,便於用戶本身查看。web

  系統日誌,主要針對的是軟件開發人員(包括測試、維護人員),也就是說這部分的日誌用戶是看不到的,也就是咱們一般所說的debug日誌。算法

  在大學中所謂的實踐項目或者老師佈置的做用中,一般是不會在乎日誌,除非在做業中有特別的須要,每每在開發過程當中直接打印控制檯語句來調試程序,這是極爲不專業的調試開發過程。因此這也就致使了一個問題,大學畢業和工做時銜接不上最大的問題不在於技術上的難度,而是日誌打印的問題。這個看似不起眼的問題對於應屆生來講每每是「惡夢」,操做日誌相對比較好理解,用戶作了什麼就記錄什麼;而打印系統日誌則無從下手,每每通常有下面幾個方面——3W:spring

  • Where:不清楚在何處打印日誌
  • Who:不清楚打印什麼級別的日誌
  • What:不清楚日誌應該包含什麼內容

  本篇着重講解系統日誌,因此如下「日誌」均爲「系統日誌」的簡稱。我將針對這幾個方面對系統日誌的打印作一個簡要的總結。另外對Java中經常使用的日誌打印框架(log4j)的幾種使用方式作一個示範。apache

WHERE

  1.程序入口編程

  在入口打印日誌是由於這個時候傳遞進來的參數沒有通過任何處理,將它打印在日誌文件中能一眼就知道程序的原始數據是否符合咱們的預期,是否是傳遞進來的原始數據就出現 的問題。api

  2.異常捕獲服務器

  在異常打印出詳細的日誌能讓你快速定位錯誤在哪裏,例如在程序拋出異常捕獲時,在平時咱們常常就是直接在控制檯打印出堆棧信息e.printStackTrace(),但在實際的生產環境更加艱苦,更別說有IDE來讓你查看控制檯信息,此時就須要咱們將堆棧信息記錄在日誌中,以便發生異常時咱們能準肯定位程序在哪裏出錯。app

  3.重要信息框架

  這一點可能很寬泛,由於不一樣的業務邏輯重點可能並不同,例如在有的重要參數不能爲空,此時就須要判斷是否爲空,若是爲空則記錄到日誌中;還有的例如傳遞進來的參數通過一系列的算法處理事後,此時也須要打印日誌來查看是否計算正確。但切記,儘可能不要直接在for循環中打印日誌,特別是for循環特別大時,這樣你的日誌可能分分鐘被衝得不見蹤影,甚至帶來性能上的影響。

WHO

         日誌打印一般有四種級別,從高到底分別是:ERROR、WARN、INFO、DEBUG。應該選用哪一種級別就是個很重要的問題。

         首先明確日誌級別中的優先級是什麼意思,在你的系統中若是開啓了某一級別的日誌後,就不會打印比它級別低的日誌。例如,程序若是開啓了INFO級別日誌,DEBUG日誌就不會打印,但不打印不表明不產生,這在後面會提到。一般在生產環境中開啓INFO日誌。

         那麼應該打印什麼級別的日誌呢?首先咱們應該明確誰在看日誌。

         一般來講,系統出了問題客戶不會進到系統對着黑黢黢的控制檯查看日誌輸出,因此日誌所面對的主體對象必然是軟件開發人員(包括測試測試、維護人員)。

         下面咱們假設幾種場景來幫助咱們理解日誌級別。

         首先,程序開發結束後交由給測試人員進行測試,測試人員根據測試用例發現某個用例的輸出和預期不符,此時他的第一反應該是查看日誌。此時的日誌是INFO級別日誌不會出現DEBUG級別的日誌,如今就須要根據日誌打印分爲兩種狀況決定他下一步操做:

  1. 經過查看INFO日誌發現是因爲本身操做失誤,形成了程序結果和預期不符合,這種狀況不是程序出錯,因此並非bug,不須要開發人員到場。
  2. 經過查看INFO日誌發現本身的操做正確,根據INFO日誌查看並非操做失誤形成,這個時候就須要開發人員到場確認。
  3. 以上兩種狀況是理想狀況,測試人員僅根據INFO級別的日誌就能判斷出程序的輸出結果與預期不符是由於本身操做失誤仍是程序bug。更爲現實的狀況實際是測試人員並不能根據INFO級別的日誌判斷是不是本身失誤仍是程序bug。

  綜上,INFO級別的日誌應該是能幫助測試人員判斷這是不是一個真正的bug,而不是本身操做失誤形成的。

  假設測試人員如今已經初步判斷這是一個bug,而且這個bug不那麼明顯,此時就須要開發人員到場確認。

  開發人員到達現場後,第一步應該是查看INFO日誌初步做初步判斷驗證測試人員的見解,接着若是不能判斷出問題所在則應該是將日誌級別調整至DEBUG級別,打印出DEBUG級別的日誌,經過DEBUG日誌來分析定位bug出在哪裏。

  因此,DEBUG級別的日誌應該是能幫助開發人員分析定位bug所在的位置。

  ERROR和WARN的級別都比INFO要高,因此在設定日誌級別在INFO時,這二者的日誌也會被打印。根據上面INFO和DEBUG級別的區別以及適用人員能夠知道,ERROR和WARN是同時給測試和開發觀察的。

  WARN級別稱之爲「警告」,這個「警告」實際上就有點含糊了,它不算錯,你能夠選擇忽視它,但也能夠選擇重視它。例如,如今一個WARN日誌打出這麼一條日誌「系統有崩潰的風險」,這個時候就須要引發足夠的重視,它表明如今不會崩潰,可是它有崩潰的風險。或者出現「某用戶在短期內將密碼輸出不少次事後才進入了系統」,這個時候是否是系統被暴力破解了呢?等等,這個級別日誌如同它的字面含義,給你一個警告,你能夠選擇忽視,也能夠重視,但至少它如今不會給系統帶來其餘影響。

  ERROR級別稱之爲「錯誤」,這個含義就更明顯了,就是系統出現了錯誤,須要處理。最爲常見的就是捕獲異常時所打印的日誌。

  上面咱們介紹了四種日誌級別的區別,特別須要注意的是INFO級別和DEBUG級別所適用的人員。那麼咱們該如何選擇哪一個級別的日誌輸出呢?

  如下是個人我的理解:

INFO

  1. 程序入口,這能讓開發人員確認參數是否爲本身所爲。
  2. 計算結果,測試關心的程序的輸出結果是否符合預期,那麼對於計算過程不該該關心,僅給出計算結果就能判斷是否符合預期。

DEBUG  

  對於DEBUG級別,我認爲更關心的是過程,以及更爲具體的相關信息,由於幫助它的定位在於幫助開發人員定位bug,定位bug就須要較爲詳細的參數信息才能定位。例如對於某個具體的算法過程,可使用DEBUG打印,開發人員不只關心結果,同時在結果不正確時應該能根據DEBUG日誌查詢計算過程是否出現誤差

WARN

  某個不常走到的分支,對於常規的操做是不該該打印WARN日誌的,只有在知足某個條件才能走到的分支,且這個分支引發了「警覺」,此時就應該打印WARN日誌。

ERROR

  毫無疑問出現錯誤,程序不能繼續運行下去就應該打印ERROR日誌,這個錯誤並非業務上的錯誤。例如,新增某個用戶發現已經存在時,此時雖然新增失敗,但不能說程序出現錯誤就打印ERROR日誌;在刪除某個用戶發現用戶已經被鎖定時,此時也不能說由於程序不能按照刪除的邏輯繼續運行下去就應該打印ERROR日誌。

WHAT

  應該打印什麼內容?打印的內容必定要從實際出發。也就是說若是在實際的生產環境中,你的用戶量很大,日誌在不停地刷新,如何定位某個用戶的整個登陸以及後續的操做呢?固然就是根據用戶名來跟蹤。因此打印內容的第一要素就是要能便於定位;定位事後也許用戶在好幾個模板中進行操做,仍是定位,這個時候定的是模塊的位;還有一點固然就是用戶操做時的具體參數;最後一點就是用戶幹了什麼。

  總結就是,[id, module, params, content](關鍵字,模塊,參數,內容)。

  以上就是對日誌打印的幾點建議,說的不全面,拋磚引玉。下面是對日誌打印框架(log4j)的非最佳實踐。



在Spring中使用log4j日誌框架

  Spring中使用log4j日誌框架能夠說是最爲常見的應用場景了,咱們將結合Spring對log4j作一個簡單的示範。

  在IDEA中建立一個Maven構建的Web項目,項目結構以下圖所示:

  pom.xml中的依賴以下:

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.6.6</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.6.6</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.16</version>
</dependency>

  在resources(IDEA中resources就是classpath路徑)中新建一個log4j.properties文件,以下所示:

 1 log4j.rootLogger = INFO, stdout, logfile
 2 #日誌輸出到控制檯
 3 log4j.appender.stdout = org.apache.log4j.ConsoleAppender
 4 log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
 5 log4j.appender.stdout.ConversionLayout = %d [%t] %-5p %c - %m%n
 6 #日誌輸出到文件
 7 log4j.appender.logfile = org.apache.log4j.DailyRollingFileAppender
 8 log4j.appender.logfile.File = /Users/yulinfeng/Log/log
 9 log4j.appender.logfile.maxFileSize=10240KB     #日誌的最大容量爲10M
10 log4j.appender.logfile.Append = true    #是否追加寫進文件
11 log4j.appender.logfile.Threshold = DEBUG    #輸出DEBUG級別日誌到文件中
12 log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
13 log4j.appender.logfile.layout.ConversionPattern = %d [%t] %-5p %c - %m%n

  第1行,log4j.rootLogger=INFO, stdout, logfile。這是log4j的根配置,第一個參數表示輸出什麼級別的日誌,後面的參數表示輸出的位置,位置能夠是控制檯,也能夠是文件,語法爲log4j.rootLogger=[level], appendername……,在這裏定義了兩個輸出位置,名字無所謂取設麼,有意義便可。日誌級別從高到低分別是:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL,log4j建議只使用ERROR、WARN、INFO、DEBUG四個級別,也就是也就是在上面提到過的。

  第三、7行就分別指定了stdout和logfile日誌的輸出位置,log4j一共提供了5個。

  org.apache.log4j.ConsoleAppender(控制檯)

  org.apache.log4j.FileAppender(文件)

  org.apache.log4j.DailyRollingFileAppender(天天產生一個日誌文件)

  org.apache.log4j.RollingFileAppender(文件大小達到必定大小後產生一個新的文件)

  org.apache.log4j.WriterAppender(將日誌信息以流格式發送到任意位置)

       第4行表示日誌信息的格式,一共有如下幾種。

       org.apache.log4j.HTMLLayout(以HTML表格輸出)

       org.apache.log4j.PatternLayout(靈活的自定義格式輸出)

       org.apache.log4j.SimpleLayout(簡單的格式輸出,只包括日誌級別和日誌信息的字符串)

       org.apache.log4j.TTCCLayout(包含線程、日誌級別、日誌所在類和日誌信息的字符串)

       一般爲了更爲靈活的打印日誌,咱們會選擇PatternLayout佈局的日誌,同時經過ConversionPattern自定義輸出格式。

  按照上面的配置,咱們就能夠在代碼中進行日誌的輸出了。因爲是在Spring框架下使用log4j,因此就要使用Spring對log4j進行初始化,在web.xml中對log4j進行初始化。

<web-app>
    <display-name>log web</display-name>

    <context-param>
        <param-name>log4jConfigurationLocation</param-name>
        <param-value>classpath*:log4j.properties</param-value>
    </context-param>

    <!--每隔60s掃描log4j的配置文件,這裏配置的log4jRefreshInterval參數表示能不用重啓web服務器就能動態更改log4j日誌級別,這也是和Spring整合的一大好處-->
    <context-param>
        <param-name>log4jRefreshInterval</param-name>
        <param-value>60000</param-value>
    </context-param>
    <listener>
        <!--從spring4.2.1開始Log4jConfigListener已經被廢棄,最好使用log4j2對應的org.apache.logging.log4j.web.Log4jServletContextListener
.Log4jServletContextListener-->
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
</web-app>

  此時在代碼邏輯中加入如下代碼便可根據咱們的配置輸出系統日誌。

private Logger log = Logger.getLogger(Test.class);
log.info(「test info」);

  上面是全部日誌文件都輸出到一個文件的狀況,在實際中咱們頗有可能針對不一樣的模塊輸出到不一樣到日誌文件。

       修改log4j.properties:

 1 log4j.rootLogger = INFO, module1, module2
 2 #輸出到控制檯
 3 log4j.appender.stdout = org.apache.log4j.ConsoleAppender
 4 log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
 5 log4j.appender.stdout.ConversionLayout = %d [%t] %-5p %c - %m%n
 6 #模塊1輸出的日誌文件
 7 log4j.appender.module1 = org.apache.log4j.DailyRollingFileAppender
 8 log4j.appender.module1.File = /Users/yulinfeng/Log/module1
 9 log4j.appender.module1.maxFileSize=10240KB     #日誌的最大容量爲10M
10 log4j.appender.module1.Append = true    #是否追加寫進文件
11 log4j.appender.module1.Threshold = DEBUG    #輸出DEBUG級別日誌到文件中
12 log4j.appender.module1.layout = org.apache.log4j.PatternLayout
13 log4j.appender.module1.layout.ConversionPattern = %d [%t] %-5p %c - %m%n
14 #模塊2輸出的日誌文件
15 log4j.appender.module2 = org.apache.log4j.DailyRollingFileAppender
16 log4j.appender.module2.File = /Users/yulinfeng/Log/module2
17 log4j.appender.module2.maxFileSize=10240KB     #日誌的最大容量爲10M
18 log4j.appender.module2.Append = true    #是否追加寫進文件
19 log4j.appender.module2.Threshold = DEBUG    #輸出DEBUG級別日誌到文件中
20 log4j.appender.module2.layout = org.apache.log4j.PatternLayout
21 log4j.appender.module2.layout.ConversionPattern = %d [%t] %-5p %c - %m%n

  在模塊1中輸出日誌文件時其實就是參數不一樣而已:

private Logger log = Logger.getLogger(「module1」);
log.info(「test info」);

  在模塊2中:

private Logger log = Logger.getLogger(「module2」);
log.info(「test info」);

  以上就是在Spring中使用log4j日誌框架的非最佳實踐。

  最後,還要介紹另一種打印日誌的方式,上面的方式將會在每一個類中都定義一個Logger對象,這樣的代碼相對於業務邏輯來講實際是不想關,此時就能夠利用Spring中的AOP面向切面編程打印日誌。這裏可能不是全部的人都能接觸到利用AOP來打印日誌,這裏暫時不作詳細介紹。

 

 

這是一個能給程序員加buff的公衆號 

相關文章
相關標籤/搜索