# Java日志規范
## ? ? ? ?前言
一個在生產環境里運行的程序如果沒有日志是很讓維護者提心吊膽的,有太多雜亂又無意義的日志也是令人傷神。程序出現問題時候,從日志里如果發現不了問題可能的原因是很令人受挫的。本文想討論的是如何在Java程序里寫好日志。
一般來說日志分為兩種:業務日志和異常日志,使用日志我們希望能達到以下目標:
1. 對程序運行情況的記錄和監控;
2. 在必要時可詳細了解程序內部的運行狀態;
3. 對系統性能的影響盡量小
## Java日志框架
Java的日志框架太多了。。。
1. [**Log4j**](http://logging.apache.org/)?或?[**Log4j 2**](http://logging.apache.org/log4j/2.x/)?- Apache的開源項目,通過使用Log4j,我們可以控制日志信息輸送的目的地是控制臺、文件、GUI組件、甚至是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等;用戶也可以控制每一條日志的輸出格式;通過定義每一條日志信息的級別,用戶能夠更加細致地控制日志的生成過程。這些可以通過一個配置文件(XML或Properties文件)來靈活地進行配置,而不需要修改程序代碼。Log4j 2則是前任的一個升級,參考了Logback的許多特性;
2. [**Logback**](http://logback.qos.ch/)?- Logback是由log4j創始人設計的又一個開源日記組件。logback當前分成三個模塊:logback-core,logback- classic和logback-access。logback-core是其它兩個模塊的基礎模塊。logback-classic是log4j的一個改良版本。此外logback-classic完整實現SLF4J API使你可以很方便地更換成其它日記系統如log4j或JDK14 Logging;
3. [**java.util.logging**](http://docs.oracle.com/javase/6/docs/api/java/util/logging/package-summary.html)?- JDK內置的日志接口和實現,功能比較簡;
4. [**Slf4j**](http://www.slf4j.org/)?- SLF4J是為各種Logging API提供一個簡單統一的接口),從而使用戶能夠在部署的時候配置自己希望的Logging API實現;
5. [**Apache Commons Logging**](http://commons.apache.org/proper/commons-logging/)?- Apache Commons Logging (JCL)希望解決的問題和Slf4j類似。
選項太多了的后果就是選擇困難癥,我的看法是沒有最好的,只有最合適的。在比較關注性能的地方,選擇Logback或自己實現高性能Logging API可能更合適;在已經使用了Log4j的項目中,如果沒有發現問題,繼續使用可能是更合適的方式;我一般會在項目里選擇使用Slf4j, 如果不想有依賴則使用java.util.logging或框架容器已經提供的日志接口。
## 3.Java日志最佳實踐
### 定義日志變量
日志變量往往不變,最好定義成**final static**,變量名用**大寫**。
### 日志分級
Java的日志框架一般會提供以下日志級別,缺省打開info級別,也就是debug,trace級別的日志在生產環境不會輸出,在開發和測試環境可以通過不同的日志配置文件打開debug級別。
1. **fatal**?- 嚴重的,造成服務中斷的錯誤;
2. **error**?- 其他錯誤運行期錯誤;
3. **warn**?- 警告信息,如程序調用了一個即將作廢的接口,接口的不當使用,運行狀態不是期望的但仍可繼續處理等;
4. **info**?- 有意義的事件信息,如程序啟動,關閉事件,收到請求事件等;
5. **debug**?- 調試信息,可記錄詳細的業務處理到哪一步了,以及當前的變量狀態;
6. **trace**?- 更詳細的跟蹤信息;
在程序里要合理使用日志分級:
~~~
1 LOGGER.debug("entering getting content");
2 String content =CacheManager.getCachedContent();
3 if(content == null){
4
5 //使用warn,因為程序還可以繼續執行,但類似警告太多可能說明緩存服務不可用了,值得注意
6 LOGGER.warn("Got empty content from cache,need perform database lookup");
7
8 Connection conn = ConnectionFactory.getConnection();
9 if (conn=null) {
10 LOGGER.error("Can't get database connection, failed to return content");//盡量提供詳細的信息,知道錯誤的原因,而不能簡單的寫logger.error("failed")
11 }else{
12 try{
13 content = conn.query(...);
14 }catch ( IOException e ){
15 //異常要記錄錯誤堆棧
16 LOGGER.error("Failed to perform database lookup", e );
17 }finally{
18 ConnectionFactory.releaseConnection(conn);
19 }
20 }
21 }
22 //調試的時候可以知道方法的返回了
23 LOGGER.debug("returning content: "+ content);
24 return content;
~~~
### 基本的Logger編碼規范
1. 在一個對象中通常只使用**一個**Logger對象,Logger應該是**static final**的,只有在少數需要在構造函數中傳遞logger的情況下才使用private final。
~~~
static final logger_LOG=loggerFactory.getLogger(Main.class);
~~~
? ? ? ? ? ? 2. 輸出Exceptions的**全部**Throwable信息,因為logger.error(msg)和logger.error(msg,e.getMessage())這樣的日志輸出方法會丟失掉最重要的StackTrace信息。
~~~
void foo(){
try {
// do something...
}catch ( Exception e ){
_LOG.error(e.getMessage()); // 錯誤
_LOG.error("Bad things : ",e.getMessage()); // 錯誤
_LOG.error("Bad things : ",e); // 正確
}
}
~~~
? ? ? ? ? ? ?3. **不允許**記錄日志后又拋出異常,因為這樣會多次記錄日志,**只允許**記錄一次日志。
~~~
void foo() throws LogException{
try{
// do something...
}catch ( Exception e ){
_LOG.error("Bad things : ", e);
throw new LogException("Bad things : ",e);
}
}
~~~
? ? ? ? ? ? 4. **不允許**出現System print(包括System.out.println和System.error.println)語句。
~~~
void foo() {
try{
// do something...
}catch( Exception e ){
System.out.println(e.getMessage()); // 錯誤
System.err.println(e.getMessage()); // 錯誤
_LOG.error("Bad things : ",e ); // 正確
}
}
~~~
? ? ? ? ? ? 5. **不允許**出現printStackTrace。
~~~
void foo() {
try {
// do something...
}catch ( Exception e ) {
e.printStackTrace(); // 錯誤
_LOG.error("Bad things : ", e ); //正確
}
}
~~~
? ? ? ? ? ?6. 日志性能的考慮,如果代碼為核心代碼,執行頻率非常高,則輸出日志建議增加判斷,尤其是低級別的輸出。
debug日志太多后可能會影響性能,有一種改進方法是:
~~~
if (LOGGER.isDebugEnabled ()) {
LOGGER.debug("returning content: "+ content);
}
~~~
但更好的方法是Slf4j提供的[最佳實踐](http://www.slf4j.org/faq.html#logging_performance):
~~~
LOGGER.debug("returning content: {}", content);
~~~
一方面可以減少參數構造的開銷,另一方面也不用多寫兩行代碼。
7. **有意義**的日志
通常情況下在程序日志里記錄一些比較有意義的狀態數據:程序啟動,退出的時間點;程序運行消耗時間;耗時程序的執行進度;重要變量的狀態變化。
除此之外,在公共的日志里**規避**打印程序的調試或者提示信息。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux