日誌終極總結
目錄
- 什麼是日誌
- 常用日誌框架
- 日誌級別詳解
- 日誌的記錄時機
- 日誌使用規約
- logback 配置示例
- loh4j2 配置示例
1、什麼是日誌?
簡單的說,日誌就是記錄程式的執行軌跡,方便查詢關鍵資訊,也方便快速定位解決問題。我們 Java 程式設計師在開發專案時都是依賴 Eclipse/ Idea 等開發工具的 Debug 除錯功能來跟蹤解決 Bug,在開發環境可以這麼做,但專案釋出到了測試、生產環境呢?你有可能會說可以使用遠端除錯,但實際並不能允許讓你這麼做。所以,日誌的作用就是在測試、生產環境沒有 Debug 除錯工具時開發、測試人員定位問題的手段。日誌打得好,就能根據日誌的軌跡快速定位並解決線上問題,反之,日誌輸出不好不能定位到問題不說反而會影響系統的效能。優秀的專案都是能根據日誌定位問題的,而不是線上除錯,或者半天找不到有用的日誌。
2、常用日誌框架
log4j、Logging、commons-logging、slf4j、logback,開發的同學對這幾個日誌相關的技術不陌生吧,為什麼有這麼多日誌技術,它們都是什麼區別和聯絡呢?相信大多數人搞不清楚它們的關係,下面我將一一介紹一下,以後大家再也不用傻傻分不清楚了。
2.1、Logging 【java 自帶工具】
這是 Java 自帶的日誌工具類,在 JDK 1.5 開始就已經有了,在java.util.logging 包下。
2.2、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.
2.3、Commons-logging 【日誌介面】
上面介紹的 log4j 是一個具體的日誌框架的實現,而 commons-logging 就是日誌門面介面,它也是 apache 最早提供的日誌門面介面,使用者可以根據喜好選擇不同的日誌實現框架,而不必改動日誌定義,這就是日誌門面的好處,符合面向介面抽象程式設計。
2.4、 Slf4j 【日誌介面】
全稱:Simple Logging Facade for Java,即簡單日誌門面介面,和 Apache 的 commons-logging是一樣的概念,它們都不是具體的日誌框架,你可以指定其他主流的日誌實現框架。Slf4j也是現在主流的日誌門面框架,使用Slf4j可以很靈活的使用佔位符進行引數佔位,簡化程式碼,擁有更好的可讀性,這個後面會講到。
2.5、 Logback 【框架實現】
Logback 是 Slf4j 的原生實現框架,同樣也是出自 Log4j一個人之手,但擁有比log4j更多的優點、特性和更做強的效能,現在基本都用來代替 log4j 成為主流。
2.6、日誌框架總結
- commons-loggin、slf4j 只是一種日誌抽象門面,不是具體的日誌框架。
- log4j、logback 是具體的日誌實現框架。
- 一般首選強烈推薦使用 slf4j + logback。當然也可以使用slf4j + log4j、commons-logging + log4j 這兩種日誌組合框架。
3、日誌級別詳解
日誌的輸出都是分級別的,不同的設定不同的場合列印不同的日誌。下面拿最普遍用的 Log4j 日誌框架來做個日誌級別的說明,這個也比較全面,其他的日誌框架也都大同小異。Log4j 的級別類 org.apache.log4j.Level 裡面定義了日誌級別,日誌輸出優先順序由高到底分別為以下8種。
3.1 、日誌級別及描述
- ERROR:系統發生了錯誤事件,但仍然不影響系統的繼續執行。系統需要將錯誤或異常細節記錄ERROR日誌中,方便後續人工回溯解決。
- WARN: 系統在業務處理時觸發了異常流程(引數驗證不過),但系統可恢復到正常態,下一次業務可以正常執行。如程式呼叫了一箇舊版本的介面,可選引數不合法,非業務預期的狀態但仍可繼續處理等
- INFO: 記錄系統關鍵資訊,旨在保留系統正常工作期間關鍵執行指標,開發人員可以將初始化系統配置、業務狀態變化資訊,或者使用者業務流程中的核心處理記錄到INFO日誌中,方便日常運維工作以及錯誤回溯時上下文場景復現
- DEBUG: 可以將各類詳細資訊記錄到DEBUG裡,起到除錯的作用,包括引數資訊,除錯細節資訊,返回值資訊等。
3.2 、日誌優先級別標準順序
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
3.3 、設定級別和列印級別的關係
如果日誌級別設定 INFO,只有輸出級別為 INFO、WARN,後面的日誌才會正常輸出。
4、日誌的記錄時機
4.1、系統初始化
系統初始化時會依賴一些關鍵配置,根據引數不同會提供不一樣的服務。將系統的啟動引數記錄INFO日誌,打印出引數以及服務啟動完成狀態。
4.2、業務流程與預期不符
系統中結果與期望不符,應當記錄日誌。常見的合適場景包括外部引數不正確,資料處理問題導致返回碼不在合理範圍內等等。
4.3、系統核心的關鍵動作
系統中核心角色觸發的業務動作是需要多加關注的,是衡量系統正常執行的重要指標,建議記錄INFO級別日誌,比如微服務各服務節點互動等。
4.4、 捕獲到異常時
這類捕獲的異常是系統告知開發人員需要加以關注的,應當記錄日誌,根據實際情況使用warn或者error級別。
4.5、 外部介面日誌
這類日誌涉及到與外部系統的互動,事關責任問題,建議將原始資料檔案內容寫入日誌或資料庫(如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"> <!-- 每個日誌檔案大小不超過2GB --> <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"> <!-- 日誌每天進行rotate --> <fileNamePattern>${LOG_DIR}/all-%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>${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>