1. 程式人生 > >JAVA日誌記錄方法

JAVA日誌記錄方法

1. 一個最基本的例子

使用Logging框架寫Log基本上就三個步驟
  1. 引入loggerg類和logger工廠類
  2. 宣告logger
  3. 記錄日誌
下面看一個例子
//1. 引入slf4j介面的Logger和LoggerFactoryimport org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {
  //2. 宣告一個Logger,這個是static的方式,我比較習慣這麼寫。  private final static Logger logger = LoggerFactory.getLogger(UserService.class
);

  public boolean verifyLoginInfo(String userName, String password) {
    //3. log it,輸出的log資訊將會是:"Start to verify User [Justfly]
    logger.info("Start to verify User [{}]", userName);
    return false;
  }
} 其中的第二步,關於Logger物件是否要宣告為靜態的業界有過一些討論,Logback的作者最早是推薦使用物件變數的方式來宣告,後來他自己也改變了想法。想詳細瞭解的同學可以去看一下: 
http://slf4j.org/faq.html#declared_static
兩種方式的優劣概述如下:
  • 靜態Logger物件相對來說更符合語義,節省CPU,節省記憶體,不支援注入
  • 物件變數Logger支援注入,對於一個JVM中執行的多個引用了同一個類庫的應用程式,可以在不同的應用程式中對同個類的Logger進行不同的配置。比如Tomcat上部署了倆個應用,他們都引用了同一個lib。

2. Logger介面的方法

Logger介面分為倆個版本,一個帶Marker版本和一個沒帶Marker版本的。帶Marker版本的我沒用過就不介紹了。沒帶Marker版本的介面方法可以分為以下兩組:

2.1 判斷Logger級別是否開啟的方法

  • public boolean isTraceEnabled();
  • public boolean isDebugEnabled();
  • public boolean isInfoEnabled();
  • public boolean isWarnEnabled();
  • public boolean isErrorEnabled();
這組方法的作用主要是避免沒必要的log資訊物件的產生,尤其是對於不支援引數化資訊的Log框架(Log4j 1, commons-logging)。如下面的例子所示,如果沒有加debug級別判斷,在Debug級別被禁用的環境(生產環境)中,第二行的程式碼將沒有必要的產生多個String物件。
1 if(logger.isDebugEnabled()){
2   logger.debug("["+resultCount+"]/["+totalCount+"] of users are returned");
3 } 如果使用了引數資訊的方法,在如下程式碼中,即使沒有新增debug級別(第一行)判斷,在生產環境中,第二行程式碼只會生成一個String物件。 1 if(logger.isDebugEnabled()){
2   logger.debug("[{}]/[{}] of users in group are returned", resultCount,totalCount);
3 } 因此,為了程式碼的可讀性,我一般情況下使用引數化資訊的方法,並且不做Logger級別是否開啟的判斷,換句話說,這組方法我一般情況下不會用。

2.2 log資訊的方法

2.2.1 方法說明

Logger中有五個級別:track,debug,info,warn,error。對於每個級別,分別有五個log方法,以info級別為例子:
  • public void info(String msg);
無引數的log方法,例子:
logger.info("開始初始化配置檔案讀取模組"); 輸出
2014-08-11 23:36:17,783 [main] INFO  c.j.training.logging.service.UserService - 開始初始化配置檔案讀取模組
  • public void info(String format, Object arg);
支援一個引數的引數化log方法,例子: logger.info("開始匯入配置檔案[{}]","/somePath/config.properties"); 輸出 2014-08-11 23:36:17,787 [main] INFO  c.j.training.logging.service.UserService - 開始匯入配置檔案[/somePath/config.properties]
  • public void info(String format, Object arg1, Object arg2);
支援倆個引數的引數化log方法,例子:
logger.info("開始從配置檔案[{}]中讀取配置項[{}]的值","/somePath/config.properties","maxSize"); 輸出 2014-08-11 23:36:17,789 [main] INFO  c.j.training.logging.service.UserService - 開始從配置檔案[/somePath/config.properties]中讀取配置項[maxSize]的值
  • public void info(String format, Object... arguments);
支援多個引數的引數化log方法,對比上面的倆個方法來說,會多增加構造一個Object[]的開銷。例子:
logger.info("在配置檔案[{}]中讀取到配置項[{}]的值為[{}]","/somePath/config.properties","maxSize", 5); 輸出
2014-08-11 23:36:17,789 [main] INFO  c.j.training.logging.service.UserService - 在配置檔案[/somePath/config.properties]中讀取到配置項[maxSize]的值為[5]
  • public void info(String msg, Throwable t);
無引數化記錄log異常資訊 logger.info("讀取配置檔案時出現異常",new FileNotFoundException("File not exists")); 輸出 2014-08-11 23:36:17,794 [main] INFO  c.j.training.logging.service.UserService - 讀取配置檔案時出現異常
java.io.FileNotFoundException: File not exists
  at cn.justfly.training.logging.service.UserServiceTest.testLogResult(UserServiceTest.java:31) ~[test-classes/:na]
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_45]
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_45]
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_45]
  at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_45]
引數化說明
在上面的例子中,我們可以看到log資訊中的{}將會按照順序被後面的引數所替換。這樣帶來了一個好處:如果在執行時不需要列印該Log,則不會重複產生String物件。

2.2.2 如何Log Exception

2.2.2.1 把Exception作為Log方法的最後一個引數

上面講的引數化Log方法的中的最後一個引數如果是一個Exception型別的物件的時候,logback將會列印該Exception的StackTrace資訊。看下面的這個例子:
logger.info("讀取配置檔案[{}]時出錯。","/somePath/config.properties",new FileNotFoundException("File not exists")); 上面的程式碼在執行的時候會輸出如下內容:
2014-08-12 00:22:49,167 [main] INFO  c.j.training.logging.service.UserService - 讀取配置檔案[/somePath/config.properties]時出錯。
java.io.FileNotFoundException: File not exists
  at cn.justfly.training.logging.service.UserServiceTest.testLogResult(UserServiceTest.java:30) [test-classes/:na]
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_45]
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_45]
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_45]
  at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_45]
  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) [junit.jar:na]
  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit.jar:na]
  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) [junit.jar:na]
  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) [junit.jar:na]
  at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) [junit.jar:na]

2.2.2.2 Exception不會替換log資訊中的引數

另外需要注意的時,該Exception不會作為引數化內容中的引數進行替換。比如下面的程式碼:
logger.info("讀取配置檔案[{}]時出錯。異常為[{}]","/somePath/config.properties",new FileNotFoundException("File not exists")); 其執行結果如下所示,第二個引數沒有進行替換 2014-08-12 00:25:37,994 [main] INFO  c.j.training.logging.service.UserService - 讀取配置檔案[/somePath/config.properties]時出錯。異常為[{}]
java.io.FileNotFoundException: File not exists
  at cn.justfly.training.logging.service.UserServiceTest.testLogResult(UserServiceTest.java:30) [test-classes/:na]
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_45]
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_45]
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_45]
  at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_45]

2.2.2.3 引數化Exception

如果你就是不想要列印StackTrace,就是要將其引數化的話怎麼弄?一般情況下不建議這麼做,因為你把Exception中有用的東西吃掉了。但是如果你非要這麼做的話,也不是不可以,有倆個方法:
  • 把Exception的toString()方法的返回值作為引數
例子如下所示,注意我們不用ex.getMessage()而是用toString()方法,原因在於不是每個Message例項都有Message,但是預設的toString()方法裡面包括有Message logger.info("讀取配置檔案[{}]時出錯。異常為[{}]","/somePath/config.properties",new FileNotFoundException("File not exists").toString()); 執行結果為: 2014-08-12 00:29:24,018 [main] INFO  c.j.training.logging.service.UserService - 讀取配置檔案[/somePath/config.properties]時出錯。異常為[java.io.FileNotFoundException: File not exists]
  • 不要讓Exception成為最後一個引數
例子如下: logger.info("讀取引數[{}]的時候出錯:[{}], 請檢查你的配置檔案[{}]","maxSize",new FileNotFoundException("File not exists"),"/somePath/config.properties"); 執行結果為: 2014-08-12 00:35:11,125 [main] INFO  c.j.training.logging.service.UserService - 讀取引數[maxSize]的時候出錯:[java.io.FileNotFoundException: File not exists], 請檢查你的配置檔案[/somePath/config.properties]

3. Log什麼

前面講了怎麼使用Loggger的方法log日誌,下面繼續講講在什麼地方需要記錄什麼級別的log,以及需要記錄什麼內容。

3.1 如何使用不同級別的Log

SLF4J把Log分成了Error,Warn,Info,Debug和Trace五個級別。我們可以把這倆個級別分成兩組

3.1.1 使用者級別

Error、Warn和Info這三個級別的Log會出現在生產環境上,他們必須是運維人員能閱讀明白的

3.1.1.1 Error

  • 影響到程式正常執行、當前請求正常執行的異常情況,例如:
    • 開啟配置檔案失敗
    • 第三方應用網路連線異常
    • SQLException
  • 不應該出現的情況,例如:
    • 某個Service方法返回的List裡面應該有元素的時候缺獲得一個空List
    • 做字元轉換的時候居然報錯說沒有GBK字符集

3.1.1.2 Warn

  • 不應該出現但是不影響程式、當前請求正常執行的異常情況,例如:
    • 有容錯機制的時候出現的錯誤情況
    • 找不到配置檔案,但是系統能自動建立配置檔案
  • 即將接近臨界值的時候,例如:
    • 快取池佔用達到警告線

3.1.1.3 Info

  • 系統執行資訊
    • Service方法的出入口
    • 主要邏輯中的分步驟
  • 外部介面部分
    • 客戶端請求引數和返回給客戶端的結果
    • 呼叫第三方時的呼叫引數和呼叫結果

3.1.2 開發級別

Debug和Trace這倆個級別主要是在開發期間使用或者當系統出現問題後開發人員介入除錯的時候用的,需要有助於提供詳細的資訊。

3.1.2.1 Debug

  • 用於記錄程式變數,例如:
    • 多次迭代中的變數
  • 用於替代程式碼中的註釋
如果你習慣在程式碼實現中寫: //1. 獲取使用者基本薪資
//2. 獲取使用者休假情況
//3. 計算使用者應得薪資 不妨這麼寫試試 logger.debug("開始獲取員工[{}] [{}]年基本薪資",employee,year);

logger.debug("獲取員工[{}] [{}]年的基本薪資為[{}]",employee,year,basicSalary);
logger.debug("開始獲取員工[{}] [{}]年[{}]月休假情況",employee,year,month);

logger.debug("員工[{}][{}]年[{}]月年假/病假/事假為[{}]/[{}]/[{}]",employee,year,month,annualLeaveDays,sickLeaveDays,noPayLeaveDays);
logger.debug("開始計算員工[{}][{}]年[{}]月應得薪資",employee,year,month);

logger.debug("員工[{}] [{}]年[{}]月應得薪資為[{}]",employee,year,month,actualSalary);

3.1.2.2 Trace

主要用於記錄系統執行中的完整資訊,比如完整的HTTP Request和Http Response

3.2 Log中的要點

3.2.1 Log上下文

在Log中必須儘量帶入上下文的資訊,對比以下倆個Log資訊,後者比前者更有作用
  • "開始匯入配置檔案"
  • "開始匯入配置檔案[/etc/myService/config.properties]"

3.2.2 考慮Log的讀者

對於使用者級別的Log,它的讀者可能是使用了你的框架的其他開發者,可能是運維人員,可能是普通使用者。你需要儘量以他們可以理解的語言來組織Log資訊,如果你的Log能對他們的使用提供有用的幫助就更好了。 下面的兩條Log中,前者對於非程式碼維護人員的幫助不大,後者更容易理解。
  • "開始執行getUserInfo 方法,使用者名稱[jimmy]"
  • "開始獲取使用者資訊,使用者名稱[jimmy]"
下面的這個Log對於框架的使用者提供了極大的幫助
  • "無法解析引數[12 03, 2013],birthDay引數需要符合格式[yyyy-MM-dd]"

3.2.3 Log中的變數用[]與普通文字區分開來

把變數和普通文字隔離有這麼幾個作用
  • 在你閱讀Log的時候容易捕捉到有用的資訊
  • 在使用工具分析Log的時候可以更方便抓取
  • 在一些情況下不容易混淆
對比以下下面的兩條Log,前者發生了混淆:
  • "獲取使用者lj12月份發郵件記錄數"
  • "獲取使用者[lj1][2]月份發郵件記錄數"

3.2.4 Error或者Warn級別中碰到Exception的情況儘量log 完整的異常資訊

Error和Warn級別是比較嚴重的情況,意味著系統出錯或者危險,我們需要更多的資訊來幫助分析原因,這個時候越多的資訊越有幫助。這個時候最好的做法是Log以下全部內容:
  • 你是在做什麼事情的時候出錯了
  • 你是在用什麼資料做這個事情的時候出錯了
  • 出錯的資訊是什麼
對比下面三個Log語句,第一個提供了詳盡的資訊,第二個只提供了部分資訊,Exception的Message不一定包含有用的資訊,第三個只告訴你出錯了,其他的你一無所知。
  • log.error("獲取使用者[{}]的使用者資訊時出錯",userName,ex);
  • log.error("獲取使用者[{}]的使用者資訊時報錯,錯誤資訊:[{}]",userName,ex.getMessage());
  • log.error("獲取使用者資訊時出錯");

3.2.5 對於Exception,要每次都Log StackTrace嗎?

在一些Exception處理機制中,我們會每層或者每個Service對應一個RuntimeException類,並把他們丟擲去,留給最外層的異常處理層處理。典型程式碼如下:
try{
  
}catch(Exception ex){
  String errorMessage=String.format("Error while reading information of user [%s]",userName);
  logger.error(errorMessage,ex);
  throw new UserServiceException(errorMessage,ex);
} 這個時候問題來了,在最底層出錯的地方Log了異常的StackTrace,在你把這個異常外上層拋的過程中,在最外層的異常處理層的時候,還會再Log一次異常的StackTrace,這樣子你的Log中會有大篇的重複資訊。 我碰到這種情況一般是這麼處理的:Log之!原因有以下這幾個方面:
  • 這個資訊很重要,我不確認再往上的異常處理層中是否會正常的把它的StackTrace打印出來。
  • 如果這個異常資訊在往上傳遞的過程中被多次包裝,到了最外層列印StackTrace的時候最底層的真正有用的出錯原因有可能不會被打印出來。
  • 如果有人改變了LogbackException列印的配置,使得不能完全列印的時候,這個資訊可能就丟了。
  • 就算重複了又怎麼樣?都Error了都Warning了還省那麼一點空間嗎?

相關推薦

JAVA日誌記錄方法

1. 一個最基本的例子 使用Logging框架寫Log基本上就三個步驟 引入loggerg類和logger工廠類宣告logger記錄日誌 下面看一個例子 //1. 引入slf4j介面的Logger和LoggerFactoryimport org.slf4j.Logger;import org.slf4

Java日誌記錄工具SLF4J介紹

ack imp 配置文件 index log4j alt title pri tps SLF4J是什麽 SLF4J是一個包裝類,典型的facade模式的工具,對用戶呈現統一的操作方式,兼容各種主流的日誌記錄框架,典型的有log4j/jdk logging/nop/simpl

java日誌記錄示例

程式碼如下所示:import java.io.IOException; import java.util.logging.*; public class LoggerTest { public static void main(String args[]) {

日誌記錄的作用和方法 java

程式中記錄日誌一般有兩個目的:Troubleshooting和顯示程式執行狀態。好的日誌記錄方式可以提供我們足夠多定位問題的依據。日誌記錄大家都會認為簡單,但如何通過日誌可以高效定位問題並不是簡單的事情。這裡列舉下面三個方面的內容,輔以程式碼示例,總結如何寫好日誌,希望對他

F5負載的應用IIS日誌記錄的不是真實IP的處理方法

mage 日誌 .cn 如果 應該 沒有 名稱 技術 https 如果沒有這一項,在服務裏添加上 將F5XForwardedFor.dll拷貝到應用目錄下 添加篩選器: 名稱:F5XForwardedFo

aop日誌記錄方法調用日誌

dex bsp ram org rip his == name 標註 一,使用aop記錄方法調用日誌   1)使用註解與aop做方法調用日誌,只需要把註解添加在要記錄的方法上就可以,不會影響代碼結構   2)實現思路 數據庫表建立>>配置需要環境>>

Log4j記錄日誌使用方法

img doc 導入 add cte ctype In url nic 1.導入相關JAR包 log4j-1.2.15.jar slf4j-api-1.6.1.jar slf4j-log4j12-1.6.1.jar log4jdbc4-1.2.jar 2.配置log4

Spring之AOP實現日誌輸出,記錄方法執行時間

為了更好的瞭解AOP,進行實踐,用AOP實現日誌輸出,記錄方法執行時間。 專案總體結構 專案簡介;專案採用SpringBoot簡單的實現一個訪問模組。再用AOP實現此模組的日誌輸出,記錄方法的執行時間。 pom.xml <project xmlns="http://mav

php 寫入檔案,日誌記錄資訊方法

/** * [write_log 寫入日誌] * @param [type] $data [寫入的資料] * @return [type] [description] */ function write_log($data){ $years = date('Y-m')

java使用slf4j+log4j進行日誌記錄並將ERROR級別資訊入庫

1.maven 座標   <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>lo

Java日誌框架-logback的介紹及配置使用方法(純Java工程)

說明:內容估計有些舊,2011年的,但是大體意思應該沒多大變化,最新的配置可以參考官方文件。 一、logback的介紹 Logback是由log4j創始人設計的又一個開源日誌元件。logback當前分成三個模組:logback-core,logback- classic和

Java記錄日誌(log4j)

  為什麼要寫日誌? 1.在程式開發過程中,方便除錯,並且方便發現程式執行時的錯誤資訊。 2.在生產環境時,方便排除問題。 3.可以業務資料,以便後期對資料分析   實現方式 使用log4j,通過配置,將日誌輸出到控制檯,檔案,資料庫。(因為該文章

Java專案日誌記錄方案

一、概述 1、採用slf4j作為日誌API,採用logback作為日誌輸出工具,用slf4j橋接方式替換掉log4j和commons-logging。 2、採用trace(追蹤)、debug(除錯)、info(資訊)、warn(警告)、error(錯誤

工廠三兄弟之工廠方法模式(一):日誌記錄器的設計

簡單工廠模式雖然簡單,但存在一個很嚴重的問題。當系統中需要引入新產品時,由於靜態工廠方法通過所傳入引數的不同來建立不同的產品,這必定要修改工廠類的原始碼,將違背“開閉原則”,如何實現增加新產品而不影響已有程式碼?工廠方法模式應運而生,本文將介紹第二種工廠模式——

工廠方法模式-Factory Method Pattern 工廠三兄弟之工廠方法模式(一):日誌記錄器的設計

簡單工廠模式雖然簡單,但存在一個很嚴重的問題。當系統中需要引入新產品時,由於靜態工廠方法通過所傳入引數的不同來建立不同的產品,這必定要修改工廠類的原始碼,將違背“開閉原則”,如何實現增加新產品而不影

java spring 記錄使用者增刪改操作日誌

在資料庫中建立操作記錄(方式一) 建立操作記錄(方法二) 使用LOG4J,通過配置LOG4J來獲取業務日誌(Apache Log4j) 用觸發器生成SQL Server2000資料表的操作日誌 基於攔截器的操作日誌儲存方式 Struts2攔截器(自定義攔截器) Spring

java web日誌記錄之spring aop實現方式

實現思路:spring aop切入到bean,在需要寫日誌的方法加入註解AuditLog,如果沒有註解的方法則不記錄日誌。 註解類 @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(Ret

java動態代理詳解,並用動態代理和註解實現日誌記錄功能

動態代理的概念       動態代理是程式在執行過程中自動建立一個代理物件來代替被代理的物件去執行相應的操作,例如, 我們有一個已經投入執行的專案中有一個使用者DAO類UserDao用來對User物件進行資料庫的增刪改查操作,但是有一天,要求在對使用者的增刪改查操作時記錄相

linux操作命令日誌 記錄方法

在linux終端下,為方便檢查操作中可能出現的錯誤,以及避免螢幕滾屏的限制,我們可以把操作日誌記錄下來。常用的工具有screen,script,以及tee等。     1. screen — screen manager with VT100/ANSI terminal e

java獲取日誌記錄通用工具類

日誌註解類: /** * 日誌註解類 * * @author: Rodge * @time: 2018年10月07日 下午11:29:10 * @version: V1.0.0 */ @Retention(RetentionPolicy.RUNT