目錄 1、什麼是日誌? 簡單的說,日誌就是記錄程式的運行軌跡,方便查找關鍵信息,也方便快速定位解決問題。我們 Java 程式員在開發項目時都是依賴 Eclipse/ Idea 等開發工具的 Debug 調試功能來跟蹤解決 Bug,在開發環境可以這麼做,但項目發佈到了測試、生產環境呢?你有可能會說可以 ...
目錄
- 什麼是日誌
- 常用日誌框架
- 日誌級別詳解
- 日誌的記錄時機
- 日誌使用規約
- logback 配置示例
- loh4j2 配置示例
1、什麼是日誌?
簡單的說,日誌就是記錄程式的運行軌跡,方便查找關鍵信息,也方便快速定位解決問題。我們 Java 程式員在開發項目時都是依賴 Eclipse/ Idea 等開發工具的 Debug 調試功能來跟蹤解決 Bug,在開發環境可以這麼做,但項目發佈到了測試、生產環境呢?你有可能會說可以使用遠程調試,但實際並不能允許讓你這麼做。所以,日誌的作用就是在測試、生產環境沒有 Debug 調試工具時開發、測試人員定位問題的手段。日誌打得好,就能根據日誌的軌跡快速定位並解決線上問題,反之,日誌輸出不好不能定位到問題不說反而會影響系統的性能。優秀的項目都是能根據日誌定位問題的,而不是線上調試,或者半天找不到有用的日誌。
2、常用日誌框架
log4j、Logging、commons-logging、slf4j、logback,開發的同學對這幾個日誌相關的技術不陌生吧,為什麼有這麼多日誌技術,它們都是什麼區別和聯繫呢?相信大多數人搞不清楚它們的關係,下麵我將一一介紹一下,以後大家再也不用傻傻分不清楚了。
Logging 【java 自帶工具】
這是 Java 自帶的日誌工具類,在 JDK 1.5 開始就已經有了,在java.util.logging 包下。
Log4j 【框架實現】
Log4j 是 Apache 的一個開源日誌框架,也是市場占有率最多的一個框架。大多數沒用過 Java Logging, 但沒人敢說沒用過 Log4j 吧,反正從我接觸 Java 開始就是這種情況,做 Java 項目必有 Log4j 日誌框架。註意:log4j 在 2015/08/05 這一天被 Apache 宣佈停止維護了,用戶需要切換到 Log4j2上面去。
下麵是官方宣佈原文
On August 5, 2015 the Logging Services Project Management Committee announced that Log4j 1.x had reached end of life. For complete text of the announcement please see the Apache Blog. Users of Log4j 1 are recommended to upgrade to Apache Log4j 2.
Commons-logging 【日誌介面】
上面介紹的 log4j 是一個具體的日誌框架的實現,而 commons-logging 就是日誌門面介面,它也是 apache 最早提供的日誌門面介面,用戶可以根據喜好選擇不同的日誌實現框架,而不必改動日誌定義,這就是日誌門面的好處,符合面向介面抽象編程。
Slf4j 【日誌介面】
全稱:Simple Logging Facade for Java,即簡單日誌門面介面,和 Apache 的 commons-logging是一樣的概念,它們都不是具體的日誌框架,你可以指定其他主流的日誌實現框架。Slf4j也是現在主流的日誌門面框架,使用Slf4j可以很靈活的使用占位符進行參數占位,簡化代碼,擁有更好的可讀性,這個後面會講到。
Logback 【框架實現】
Logback 是 Slf4j 的原生實現框架,同樣也是出自 Log4j一個人之手,但擁有比log4j更多的優點、特性和更做強的性能,現在基本都用來代替 log4j 成為主流。
日誌框架總結
- commons-loggin、slf4j 只是一種日誌抽象門面,不是具體的日誌框架。
- log4j、logback 是具體的日誌實現框架。
- 一般首選強烈推薦使用 slf4j + logback。當然也可以使用slf4j + log4j、commons-logging + log4j 這兩種日誌組合框架。
3、日誌級別詳解
日誌的輸出都是分級別的,不同的設置不同的場合列印不同的日誌。下麵拿最普遍用的 Log4j 日誌框架來做個日誌級別的說明,這個也比較全面,其他的日誌框架也都大同小異。Log4j 的級別類 org.apache.log4j.Level 裡面定義了日誌級別,日誌輸出優先順序由高到底分別為以下8種。
日誌級別及描述
- ERROR:系統發生了錯誤事件,但仍然不影響系統的繼續運行。系統需要將錯誤或異常細節記錄ERROR日誌中,方便後續人工回溯解決。
- WARN: 系統在業務處理時觸發了異常流程(參數驗證不過),但系統可恢復到正常態,下一次業務可以正常執行。如程式調用了一個舊版本的介面,可選參數不合法,非業務預期的狀態但仍可繼續處理等
- INFO: 記錄系統關鍵信息,旨在保留系統正常工作期間關鍵運行指標,開發人員可以將初始化系統配置、業務狀態變化信息,或者用戶業務流程中的核心處理記錄到INFO日誌中,方便日常運維工作以及錯誤回溯時上下文場景復現
- DEBUG: 可以將各類詳細信息記錄到DEBUG里,起到調試的作用,包括參數信息,調試細節信息,返回值信息等。
日誌優先順序別標準順序
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
設置級別和列印級別的關係
如果日誌級別設置 INFO,只有輸出級別為 INFO、WARN,後面的日誌才會正常輸出。
4、日誌的記錄時機
系統初始化
系統初始化時會依賴一些關鍵配置,根據參數不同會提供不一樣的服務。將系統的啟動參數記錄INFO日誌,列印出參數以及服務啟動完成狀態。
業務流程與預期不符
系統中結果與期望不符,應當記錄日誌。常見的合適場景包括外部參數不正確,數據處理問題導致返回碼不在合理範圍內等等。
系統核心的關鍵動作
系統中核心角色觸發的業務動作是需要多加關註的,是衡量系統正常運行的重要指標,建議記錄INFO級別日誌,比如微服務各服務節點交互等。
捕獲到異常時
這類捕獲的異常是系統告知開發人員需要加以關註的,應當記錄日誌,根據實際情況使用warn或者error級別。
外部介面日誌
這類日誌涉及到與外部系統的交互,事關責任問題,建議將原始數據文件內容寫入日誌或資料庫(如mongodb),核心處理邏輯關鍵業務數據也儘量寫入日誌。如果涉及到重發,建議將處理失敗的原始數據文件日誌寫入資料庫,以便重發執行。
5、日誌使用規約
- 使用@SLF4J中的API進行日誌列印。
- 日誌輸出必須採用UTF-8字元集,推薦列印日誌時輸出英文,防止中文不支持而列印出亂碼的情況。
- 不允許記錄日誌後又拋出異常,因為這樣會多次記錄日誌,只允許記錄一次日誌,應拋出異常,頂層列印一次日誌。
try { // 錯誤 } catch (Exception e) { log.error("xxxxxx", e); throw e; }
- 輸出Exceptions的全部堆棧信息,但是不能使用e.printStackTrace()
// 錯誤例子, 丟失掉StackTrace信息 log.error(e.getMessage()); // 錯誤例子,丟失掉StackTrace信息 log.error(“Bad things : {}“, e.getMessage()); // 正確例子 log.error(“Bad things : {}“,e); // e.printStackTrace()的源碼 public void printStackTrace() { printStackTrace(System.err); }
- 禁止system.out 用於日誌記錄。
- 線上必須關閉 DEBUG 級別日誌。
- 非正常的情況,需要根據情況選擇列印warn 或 error 日誌,不能使用錯誤的日誌級別。
try { // ... } catch (Exception e) { // 錯誤LOG.info("XX 發生異常...", e); } // 用 info 記錄 error 日誌,日誌輸出到了 info 日誌文件中了,同事拼命地在 error 錯誤日誌文件里 面找怎麼能找到呢?
- 日誌輸出,必須使用占位符的方式,因為即使信息不列印,也會執行字元串拼接,造成資源浪費。
- 日誌中不允許出現計算或方法調用,防止在列印日誌的時候報錯。
- 輸出的POJO類必須重寫toString方法,否則只輸出對象的hashCode值,沒有參考意義。
- 不記錄對於排查故障毫無意義的日誌信息,日誌信息一定要帶有業務信息。
//錯誤 log.error(“handle failed“); //正確 log.error(“handle failed,id= {}“, id);
- 禁止大量無效重覆的日誌輸出,通常情況下在程式日誌只記錄一些有意義的狀態數據,參考日誌記錄時機。
- 不可以講敏感業務信息記錄入日誌文件。
- 嚴防日誌占滿磁碟,定期檢查磁碟(確定是否有磁碟告警)。
- 不要在千層迴圈中列印日誌
for(int i=0; i<2000; i++){ LOG.info("XX"); } // 這個是什麼意思,如果你的框架使用了性能不高的 Log4j 框架,那就不要在上千個 for 迴圈中列印日誌, // 這樣可能會拖垮你的應用程式,如果你的程式響應時間變慢,那要考慮是不是日誌列印的過多了。
6、logback配置參考
配置說明
- 統一使用logback.xml配置,logback.xml 文件放在 classpath 目錄下。
- 所有的jar包中不建議包含logback.xml文件,避免干擾實際的業務系統。
- 通過在文件logback.xml中引入資源文件log.properties定義logback屬性信息,log.properties根據不同的profile放置在不同位置;
<property resource="log.properties"/>
- log.properties文件
- 屬性命名推薦使用統一使用大寫,以下劃線分隔,參考
APP_NAME = yourAppName LOG_DIR = /export/home/logs/yourSystem/yourAppName LOG_PATTERN = [%date{yyyy-MM-dd HH:mm:ss.SSS}] %level [%mdc{invokeNo}] %C{0}:%line - %message%n
- 註意Logger間的繼承關係,推薦additivity設置false;
- 子logger會預設繼承父logger的appender,將它們加入到自己的Appender中;除非加上了additivity="false",則不再繼承父logger的appender。
- 子logger只在自己未定義輸出級別的情況下,才會繼承父logger的輸出級別。
- 將日誌輸出到文件當中,禁止使用FileAppender,推薦使用提供自動切換功能的RollingFileAppender Log文件位置和命名,目前Log文件的位置統一放在相同目錄下麵。
文件名 | 描述 |
/export/home/logs | 預設日誌路徑(所有日誌的根路徑) |
/export/home/logs/${SYSTEM_NAME}/${APP_NAME} | log.properties中配置的日誌全路徑LOG_DIR |
${LOG_DIR}/all.log | 必選 |
${LOG_DIR}/ all-%d{yyyy-MM-dd}.log | All歷史文件命名 |
${LOG_DIR}/all_error.log | 必選 |
${LOG_DIR}/sql.log | 可選 |
- 日誌按天記錄,單個日誌文件最大不超過2000MB,考慮到有些bug按月規律出現,推薦歷史日誌保留30天。
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_DIR}/all-%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>2000MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy>
- 在出現問題之後,需要立即根據日誌定位問題。對於INFO及以上級別的日誌,要求按照一定順序,輸出以下必要的信息。參考日誌格式定義
<encoder charset="UTF-8"> <pattern>[%date{yyyy-MM-dd HH:mm:ss.SSS}] %level [%mdc{invokeNo}] %C{0}:%line - %message%n</pattern> </encoder>
- 一個完整的Appender配置如下
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property resource="log.properties"/> <contextName>${APP_NAME}</contextName>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder charset="UTF-8"> <pattern>${LOG_PATTERN}</pattern> </encoder> </appender> <appender name="all" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/all.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_DIR}/all-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxHistory>30</maxHistory> <timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>2000MB</maxFileSize> <!-- 每個日誌文件大小不超過2GB --> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <encoder charset="UTF-8"> <pattern>${LOG_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>info</level> </filter> </appender> <appender name="all-error" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/all-error.log</file> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern> ${LOG_DIR}/all-error-%d{yyyy-MM-dd}.%i.log </fileNamePattern> <maxHistory>30</maxHistory> <timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <!-- 每個日誌文件大小不超過2GB --> <maxFileSize>2000MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <encoder charset="UTF-8"> <pattern>
[%date{yyyy-MM-dd HH:mm:ss.SSS}] %level [%mdc{invokeNo}] %C{0}:%line - %message%n </pattern> </encoder> </appender>
<root level="debug"> <appender-ref ref="all"/> <appender-ref ref="all-error"/> <appender-ref ref="console"/> </root> </configuration>
7、Log4j2配置參考
配置說明
- 統一使用log4j2.xml配置,log4j2.xml 文件放在 resource目錄下。
- 註意Logger間的繼承關係,推薦additivity設置false:
- 子logger會預設繼承父logger的appender,將它們加入到自己的Appender中;除非加上了additivity="false",則不再繼承父logger的appender。
- 子logger只在自己未定義輸出級別的情況下,才會繼承父logger的輸出級別。
- 將日誌輸出到文件當中,考慮到RollingRandomAccessFile比RollingFile更靈活,推薦統一使用RollingRandomAccessFile。
- Log文件位置和命名,目前Log文件的位置統一放在相同目錄下麵。
文件名 | 描述 |
/export/home/logs` | 預設日誌路徑(所有日誌的根路徑) |
/export/home/logs/${SYSTEM_NAME}/${APP_NAME} | log.properties中配置的日誌全路徑LOG_DIR |
${LOG_DIR}/all.log | 必選 |
${LOG_DIR}/ all-%d{yyyy-MM-dd}.log | All歷史文件命名 |
${LOG_DIR}/all_error.log | 必選 |
${LOG_DIR}/sql.log | 可選 |
- 日誌按天記錄,單個日誌文件最大不超過3000MB,考慮到有些bug按周規律出現,推薦歷史日誌保留14天。
<RollingRandomAccessFile name="all-append" immediateFlush="true"
fileName="${LOG_DIR}/all.log" filePattern="${LOG_DIR}/all-%d{yyyy-MM-dd}-%i.log"> <Policies> <SizeBasedTriggeringPolicy size="3GB" /> <TimeBasedTriggeringPolicy interval="8" modulate="true" /> <!-- 最多備份14天以內||日誌文件大小達到50GB的日誌||文件數量超過20此處為策略限制,
Delete中可以按自己需要用正則表達式編寫 --> <DefaultRolloverStrategy> <Delete basePath="${filePath}" maxDepth="1"> <IfLastModified age="14d" /> <IfAccumulatedFileSize exceeds="50 GB" /> <IfAccumulatedFileCount exceeds="20" /> </Delete> </DefaultRolloverStrategy>
- 在出現問題之後,需要立即根據日誌定位問題。對於INFO及以上級別的日誌,要求按照一定順序,輸出以下必要的信息。參考日誌格式定義
<PatternLayout> <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%mdc{invokeNo}] %C{2}:%L %M - %msg%n </Pattern> </PatternLayout>
- 一個完整的Appender配置如下
<?xml version="1.0" encoding="UTF-8"?> <!-- Configuration後面的status,這個用於設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時,
你會看到log4j2內部各種詳細輸出。monitorInterval:Log4j能夠自動檢測修改配置文件和重新配置本身,
設置間隔秒數。 --> <configuration status="OFF" monitorInterval=”600″> <properties> <property name="LOG_PATH">/export/home/logs/yourSystem/yourAppName</property> <property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%mdc{invokeNo}]
%C{2}:%L %M - %msg%n </property> <property name="EVERY_FILE_SIZE">3GB</property> <property name="OUTPUT_LOG_LEVEL">info</property> <property name="FILE_COUNT">20</property> <property name="ERROR_FILE_COUNT">3</property> </properties> <!--先定義所有的appender--> <appenders>
<!--輸出控制台的配置--> <Console name="console" target="SYSTEM_OUT"> <!--控制台只輸出level及以上級別的信息(onMatch),其他的直接拒絕(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <!--輸出日誌的格式--> <PatternLayout pattern="${LOG_PATTERN}"/> </Console> <!-- 列印信息,每次大小超過size,則這size大小的日誌會自動存入按年份-月份建立的文件夾下麵併進行壓縮,
作為存檔--> <RollingRandomAccessFile name="all" immediateFlush="true" fileName="${LOG_DIR}/all.log" filePattern="${LOG_DIR}/all-%d{yyyy-MM-dd}-%i.log"> <Filters> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="NEUTRAL"/> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="NEUTRAL"/> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="NEUTRAL"/> <ThresholdFilter level="trace" onMatch="DENY" onMismatch="DENY"/> </Filters> <PatternLayout> <Pattern>${LOG_PATTERN}</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="${EVERY_FILE_SIZE}" /> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> </Policies> <!-- DefaultRolloverStrategy屬性如不設置,則預設為最多同一文件夾下7個文件,這裡設置了20 --> <DefaultRolloverStrategy> <!-- 最多備14 天以內||日誌文件大小達到50GB的日誌||文件數量超過20此處為策略限制,Delete中可以按自
己需要用正則表達式編寫 --> <Delete basePath="${LOG_DIR}" maxDepth="1"> <IfLastModified age="14d" /> <IfAccumulatedFileSize exceeds="50 GB" /> <IfAccumulatedFileCount exceeds="20" /> </Delete> </DefaultRolloverStrategy>
</RollingRandomAccessFile> <RollingRandomAccessFile name="all-error" immediateFlush="true" fileName="${LOG_DIR}/all-error.log" filePattern="${LOG_DIR}/all-error-%d{yyyy-MM-dd}-%i.log"> <Filters> <ThresholdFilter level="error" onMatch="ACCEPT"onMismatch="NEUTRAL"/> <ThresholdFilter level="warn" onMatch="ACCEPT"onMismatch="DENY"/> </Filters> <PatternLayout> <Pattern>${LOG_PATTERN}</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="${EVERY_FILE_SIZE}" /> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> </Policies> <!-- DefaultRolloverStrategy屬性如不設置,則預設為最多同一文件夾下7個文件,這裡設置了20 --> <DefaultRolloverStrategy max="${ERROR_FILE_COUNT}"/> </RollingRandomAccessFile>
</appenders>
<!--然後定義logger,只有定義了logger並引入的appender,appender才會生效--> <loggers> <!--預設的root的logger--> <Logger name="all" level="info" additivity="false"> <AppenderRef ref="all" /> </Logger> <Logger name="all-error" level="error" additivity="false"> <AppenderRef ref="all-error" /> </Logger> <root level="${OUTPUT_LOG_LEVEL}"> <appender-ref ref="console"/> </root> </loggers>
</configuration>