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

来源:http://www.cnblogs.com/yulinfeng/archive/2017/10/18/7689388.html
-Advertisement-
Play Games

日誌的列印在軟體開發過程中必不可少,一般分為兩個大類: 操作日誌 系統日誌 操作日誌,主要針對的是用戶,例如在Photoshop軟體中會記錄自己操作的步驟,便於用戶自己查看。 系統日誌,主要針對的是軟體開發人員(包括測試、維護人員),也就是說這部分的日誌用戶是看不到的,也就是我們通常所說的debug ...


  日誌的列印在軟體開發過程中必不可少,一般分為兩個大類:

  • 操作日誌
  • 系統日誌

  操作日誌,主要針對的是用戶,例如在Photoshop軟體中會記錄自己操作的步驟,便於用戶自己查看。

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

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

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

  本篇著重講解系統日誌,所以以下“日誌”均為“系統日誌”的簡稱。我將針對這幾個方面對系統日誌的列印做一個簡要的總結。另外對Java中常用的日誌列印框架(log4j)的幾種使用方式做一個示範。

WHERE

  1.程式入口

  在入口列印日誌是因為這個時候傳遞進來的參數沒有經過任何處理,將它列印在日誌文件中能一眼就知道程式的原始數據是否符合我們的預期,是不是傳遞進來的原始數據就出現 的問題。

  2.異常捕獲

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

  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四個級別,也就是也就是在上面提到過的。

  第3、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的公眾號 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • C#多線程 一、使用線程的理由 1、可以使用線程將代碼同其他代碼隔離,提高應用程式的可靠性。 2、可以使用線程來簡化編碼。 3、可以使用線程來實現併發執行。 二、基本知識 1、進程與線程:進程作為操作系統執行程式的基本單位,擁有應用程式的資源,進程包含線程,進程的資源被線程共用,線程不擁有資源。 2 ...
  • 自己上網查了好多種方法,最後還是選了這種不收費,還挺好用的方法 為了用戶有一個好的體驗我將word、excel、ppt轉Pdf,Pdf轉Swf寫在服務上,因為我當時做的時候Pdf轉Swf會執行pdf2swf.exe彈出黑框,對用戶體驗不好,還有就是ppt轉swf時會有一個彈出框,提示正在發佈,我沒搞 ...
  • 1. JMS基本概念 JMS(Java Message Service) 即Java消息服務。它提供標準的產生、發送、接收消息的介面簡化企業應用的開發。 它支持兩種消息通信模型:點到點(point-to-point)(P2P)模型和發佈/訂閱(Pub/Sub)模型。 P2P 模型規定了一個消息只能有 ...
  • 之前沒課的時候寫過安居客的爬蟲,但那也是小打小鬧,那這次呢, 還是小打小鬧 哈哈,現在開始正式進行爬蟲書寫 首先,需要分析一下要爬取的網站的結構: 作為一名河南的學生,那就看看鄭州的二手房信息吧! 在上面這個頁面中,我們可以看到一條條的房源信息,從中我們發現了什麼,發現了連鄭州的二手房都是這麼的貴, ...
  • 在web.xml中配置載入spring時,發現項目無法運行;而去掉spring的配置時,項目可以被初始化。 此時應考慮到spring的配置文件中存在錯誤,以至於web容器無法對項目成功初始化,在web.xml中配置log4j, 根據列印的信息對spring的配置進行修改。 <context-para ...
  • 作者: 阿布 阿布量化版權所有 未經允許 禁止轉載 "abu量化系統github地址(歡迎+star,你的支持是我更新的動力!)" "本節ipython notebook" 上一節使用AbuFactorBuyBreak和AbuFactorSellBreak且混入基本止盈止損策略AbuFactorAt ...
  • 作者: 阿布 阿布量化版權所有 未經允許 禁止轉載 "abu量化系統github地址" (您的star是我的動力!) "本節ipython notebook" "本節界面操作教程視頻播放地址" 量化系統一般分為回測模塊、實盤模塊。 回測模塊:首先交易者編寫實現一個交易策略,它基於一段歷史的交易數據, ...
  • <settings/> settings的配置內容 <typeAliases/> 別名 表 系統定義的typeAliases 別名 映射的類型 _byte byte _long long _short short _int int _integer int _double double _float ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...