目錄
-
什么是日志
-
常用日志框架
-
日志級別詳解
-
日志的記錄時機
-
日志使用規約
-
logback 配置示例
-
loh4j2 配置示例
什么是日志?
簡單的說,日志就是記錄程序的運行軌跡,方便查找關鍵信息,也方便快速定位解決問題。我們 Java 程序員在開發項目時都是依賴 Eclipse/ Idea 等開發工具的 Debug 調試功能來跟蹤解決 Bug,在開發環境可以這么做,但項目發布到了測試、生產環境呢?你有可能會說可以使用遠程調試,但實際並不能允許讓你這么做。所以,日志的作用就是在測試、生產環境沒有 Debug 調試工具時開發、測試人員定位問題的手段。日志打得好,就能根據日志的軌跡快速定位並解決線上問題,反之,日志輸出不好不能定位到問題不說反而會影響系統的性能。優秀的項目都是能根據日志定位問題的,而不是在線調試,或者半天找不到有用的日志。
常用日志框架
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 這兩種日志組合框架。
日志級別詳解
日志的輸出都是分級別的,不同的設置不同的場合打印不同的日志。下面拿最普遍用的 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,后面的日志才會正常輸出。
日志的記錄時機
系統初始化
系統初始化時會依賴一些關鍵配置,根據參數不同會提供不一樣的服務。將系統的啟動參數記錄INFO日志,打印出參數以及服務啟動完成狀態。
業務流程與預期不符
系統中結果與期望不符,應當記錄日志。常見的合適場景包括外部參數不正確,數據處理問題導致返回碼不在合理范圍內等等。
系統核心的關鍵動作
系統中核心角色觸發的業務動作是需要多加關注的,是衡量系統正常運行的重要指標,建議記錄INFO級別日志,比如微服務各服務節點交互等。
捕獲到異常時
這類捕獲的異常是系統告知開發人員需要加以關注的,應當記錄日志,根據實際情況使用warn或者error級別。
外部接口日志
這類日志涉及到與外部系統的交互,事關責任問題,建議將原始數據文件內容寫入日志或數據庫(如mongodb),核心處理邏輯關鍵業務數據也盡量寫入日志。如果涉及到重發,建議將處理失敗的原始數據文件日志寫入數據庫,以便重發執行。
日志使用規約
- 使用@SLF4J中的API進行日志打印。
- 日志輸出必須采用UTF-8字符集,推薦打印日志時輸出英文,防止中文不支持而打印出亂碼的情況。
- 不允許記錄日志后又拋出異常,因為這樣會多次記錄日志,只允許記錄一次日志,應拋出異常,頂層打印一次日志。
1 try { 2 // 錯誤 3 } catch (Exception e) { 4 log.error("xxxxxx", e); 5 throw e; 6 }
- 輸出Exceptions的全部堆棧信息,但是不能使用e.printStackTrace()
1 // 錯誤例子, 丟失掉StackTrace信息 2 log.error(e.getMessage()); 3 // 錯誤例子,丟失掉StackTrace信息 4 log.error(“Bad things : {}“, e.getMessage()); 5 // 正確例子 6 log.error(“Bad things : {}“,e); 7 8 // e.printStackTrace()的源碼 9 public void printStackTrace() { 10 printStackTrace(System.err); 11 }
- 禁止system.out 用於日志記錄。
- 線上必須關閉 DEBUG 級別日志。
- 非正常的情況,需要根據情況選擇打印warn 或 error 日志,不能使用錯誤的日志級別。
1 try { 2 // ... 3 } catch (Exception e) { 4 // 錯誤LOG.info("XX 發生異常...", e); 5 } 6 // 用 info 記錄 error 日志,日志輸出到了 info 日志文件中了,同事拼命地在 error 錯誤日志文件里 面找怎么能找到呢?
- 日志輸出,必須使用占位符的方式,因為即使信息不打印,也會執行字符串拼接,造成資源浪費。
- 日志中不允許出現計算或方法調用,防止在打印日志的時候報錯。
- 輸出的POJO類必須重寫toString方法,否則只輸出對象的hashCode值,沒有參考意義。
- 不記錄對於排查故障毫無意義的日志信息,日志信息一定要帶有業務信息。
1 //錯誤 2 log.error(“handle failed“); 3 //正確 4 log.error(“handle failed,id= {}“, id);
- 禁止大量無效重復的日志輸出,通常情況下在程序日志只記錄一些有意義的狀態數據,參考日志記錄時機。
- 不可以講敏感業務信息記錄入日志文件。
- 嚴防日志占滿磁盤,定期檢查磁盤(確定是否有磁盤告警)。
- 不要在千層循環中打印日志
1 for(int i=0; i<2000; i++){ 2 LOG.info("XX"); 3 } 4 // 這個是什么意思,如果你的框架使用了性能不高的 Log4j 框架,那就不要在上千個 for 循環中打印日志, 5 // 這樣可能會拖垮你的應用程序,如果你的程序響應時間變慢,那要考慮是不是日志打印的過多了。
logback配置參考
配置說明
- 統一使用logback.xml配置,logback.xml 文件放在 classpath 目錄下。
- 所有的jar包中不建議包含logback.xml文件,避免干擾實際的業務系統。
- 通過在文件logback.xml中引入資源文件log.properties定義logback屬性信息,log.properties根據不同的profile放置在不同位置;
<property resource="log.properties"/>
- log.properties文件
- 屬性命名推薦使用統一使用大寫,以下划線分隔,參考
1 APP_NAME = yourAppName 2 LOG_DIR = /export/home/logs/yourSystem/yourAppName 3 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/${系統名稱}/${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天。
1 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 2 <fileNamePattern>${LOG_DIR}/all-%d{yyyy-MM-dd}.log</fileNamePattern> 3 <maxHistory>30</maxHistory> 4 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 5 <maxFileSize>2000MB</maxFileSize> 6 </timeBasedFileNamingAndTriggeringPolicy> 7 </rollingPolicy>
- 在出現問題之后,需要立即根據日志定位問題。對於INFO及以上級別的日志,要求按照一定順序,輸出以下必要的信息。參考日志格式定義
1 <encoder charset="UTF-8"> 2 <pattern>[%date{yyyy-MM-dd HH:mm:ss.SSS}] %level 3 [%mdc{invokeNo}] %C{0}:%line - %message%n</pattern> 4 </encoder>
- 一個完整的Appender配置如下
1 <?xml version="1.0" encoding="UTF-8"?> 2 <configuration> 3 <property resource="log.properties"/> 4 <contextName>${APP_NAME}</contextName> 5 6 <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> 7 <encoder charset="UTF-8"> 8 <pattern>${LOG_PATTERN}</pattern> 9 </encoder> 10 </appender> 11 12 <appender name="all" class="ch.qos.logback.core.rolling.RollingFileAppender"> 13 <file>${LOG_DIR}/all.log</file> 14 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 15 <fileNamePattern>${LOG_DIR}/all-%d{yyyy-MM-dd}.%i.log</fileNamePattern> 16 <maxHistory>30</maxHistory> 17 <timeBasedFileNamingAndTriggeringPolicy 18 class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 19 <maxFileSize>2000MB</maxFileSize> <!-- 每個日志文件大小不超過2GB --> 20 </timeBasedFileNamingAndTriggeringPolicy> 21 </rollingPolicy> 22 <encoder charset="UTF-8"> 23 <pattern>${LOG_PATTERN}</pattern> 24 </encoder> 25 <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> 26 <level>info</level> 27 </filter> 28 </appender> 29 30 <appender name="all-error" class="ch.qos.logback.core.rolling.RollingFileAppender"> 31 <file>${LOG_DIR}/all-error.log</file> 32 <filter class="ch.qos.logback.classic.filter.LevelFilter"> 33 <level>ERROR</level> 34 <onMatch>ACCEPT</onMatch> 35 <onMismatch>DENY</onMismatch> 36 </filter> 37 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 38 <fileNamePattern> 39 ${LOG_DIR}/all-error-%d{yyyy-MM-dd}.%i.log 40 </fileNamePattern> 41 <maxHistory>30</maxHistory> 42 <timeBasedFileNamingAndTriggeringPolicy 43 class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 44 <!-- 每個日志文件大小不超過2GB --> 45 <maxFileSize>2000MB</maxFileSize> 46 </timeBasedFileNamingAndTriggeringPolicy> 47 </rollingPolicy> 48 <encoder charset="UTF-8"> 49 <pattern> 50 [%date{yyyy-MM-dd HH:mm:ss.SSS}] %level [%mdc{invokeNo}] %C{0}:%line - %message%n 51 </pattern> 52 </encoder> 53 </appender> 54 55 <root level="debug"> 56 <appender-ref ref="all"/> 57 <appender-ref ref="all-error"/> 58 <appender-ref ref="console"/> 59 </root> 60 </configuration>
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/${系統名稱}/${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天。
1 <RollingRandomAccessFile name="all-append" immediateFlush="true" 2 fileName="${LOG_DIR}/all.log" filePattern="${LOG_DIR}/all-%d{yyyy-MM-dd}-%i.log"> 3 <Policies> 4 <SizeBasedTriggeringPolicy size="3GB" /> 5 <TimeBasedTriggeringPolicy interval="8" modulate="true" /> 6 <!-- 最多備份14天以內||日志文件大小達到50GB的日志||文件數量超過20此處為策略限制, 7 Delete中可以按自己需要用正則表達式編寫 --> 8 <DefaultRolloverStrategy> 9 <Delete basePath="${filePath}" maxDepth="1"> 10 <IfLastModified age="14d" /> 11 <IfAccumulatedFileSize exceeds="50 GB" /> 12 <IfAccumulatedFileCount exceeds="20" /> 13 </Delete> 14 </DefaultRolloverStrategy>
- 在出現問題之后,需要立即根據日志定位問題。對於INFO及以上級別的日志,要求按照一定順序,輸出以下必要的信息。參考日志格式定義
1 <PatternLayout> 2 <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%mdc{invokeNo}] %C{2}:%L %M - %msg%n 3 </Pattern> 4 </PatternLayout>
- 一個完整的Appender配置如下
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!-- Configuration后面的status,這個用於設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時, 3 你會看到log4j2內部各種詳細輸出。monitorInterval:Log4j能夠自動檢測修改配置文件和重新配置本身, 4 設置間隔秒數。 5 --> 6 <configuration status="OFF" monitorInterval=”600″> 7 <properties> 8 <property name="LOG_PATH">/export/home/logs/yourSystem/yourAppName</property> 9 <property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%mdc{invokeNo}] 10 %C{2}:%L %M - %msg%n </property> 11 <property name="EVERY_FILE_SIZE">3GB</property> 12 <property name="OUTPUT_LOG_LEVEL">info</property> 13 <property name="FILE_COUNT">20</property> 14 <property name="ERROR_FILE_COUNT">3</property> 15 </properties> 16 17 <!--先定義所有的appender--> 18 <appenders> 19 20 <!--輸出控制台的配置--> 21 <Console name="console" target="SYSTEM_OUT"> 22 <!--控制台只輸出level及以上級別的信息(onMatch),其他的直接拒絕(onMismatch)--> 23 <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> 24 <!--輸出日志的格式--> 25 <PatternLayout pattern="${LOG_PATTERN}"/> 26 </Console> 27 <!-- 打印信息,每次大小超過size,則這size大小的日志會自動存入按年份-月份建立的文件夾下面並進行壓縮, 28 作為存檔--> 29 <RollingRandomAccessFile 30 name="all" 31 immediateFlush="true" 32 fileName="${LOG_DIR}/all.log" 33 filePattern="${LOG_DIR}/all-%d{yyyy-MM-dd}-%i.log"> 34 <Filters> 35 <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="NEUTRAL"/> 36 <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="NEUTRAL"/> 37 <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="NEUTRAL"/> 38 <ThresholdFilter level="trace" onMatch="DENY" onMismatch="DENY"/> 39 </Filters> 40 <PatternLayout> 41 <Pattern>${LOG_PATTERN}</Pattern> 42 </PatternLayout> 43 <Policies> 44 <SizeBasedTriggeringPolicy size="${EVERY_FILE_SIZE}" /> 45 <TimeBasedTriggeringPolicy interval="1" modulate="true" /> 46 </Policies> 47 <!-- DefaultRolloverStrategy屬性如不設置,則默認為最多同一文件夾下7個文件,這里設置了20 --> 48 <DefaultRolloverStrategy> 49 <!-- 最多備14 天以內||日志文件大小達到50GB的日志||文件數量超過20此處為策略限制,Delete中可以按自 50 己需要用正則表達式編寫 --> 51 <Delete basePath="${LOG_DIR}" maxDepth="1"> 52 <IfLastModified age="14d" /> 53 <IfAccumulatedFileSize exceeds="50 GB" /> 54 <IfAccumulatedFileCount exceeds="20" /> 55 </Delete> 56 </DefaultRolloverStrategy> 57 </RollingRandomAccessFile> 58 59 <RollingRandomAccessFile 60 name="all-error" 61 immediateFlush="true" 62 fileName="${LOG_DIR}/all-error.log" 63 filePattern="${LOG_DIR}/all-error-%d{yyyy-MM-dd}-%i.log"> 64 <Filters> 65 <ThresholdFilter level="error" onMatch="ACCEPT"onMismatch="NEUTRAL"/> 66 <ThresholdFilter level="warn" onMatch="ACCEPT"onMismatch="DENY"/> 67 </Filters> 68 <PatternLayout> 69 <Pattern>${LOG_PATTERN}</Pattern> 70 </PatternLayout> 71 <Policies> 72 <SizeBasedTriggeringPolicy size="${EVERY_FILE_SIZE}" /> 73 <TimeBasedTriggeringPolicy interval="1" modulate="true" /> 74 </Policies> 75 <!-- DefaultRolloverStrategy屬性如不設置,則默認為最多同一文件夾下7個文件,這里設置了20 --> 76 <DefaultRolloverStrategy max="${ERROR_FILE_COUNT}"/> 77 </RollingRandomAccessFile> 78 79 </appenders> 80 81 <!--然后定義logger,只有定義了logger並引入的appender,appender才會生效--> 82 <loggers> 83 <!--默認的root的logger--> 84 <Logger name="all" level="info" additivity="false"> 85 <AppenderRef ref="all" /> 86 </Logger> 87 <Logger name="all-error" level="error" additivity="false"> 88 <AppenderRef ref="all-error" /> 89 </Logger> 90 <root level="${OUTPUT_LOG_LEVEL}"> 91 <appender-ref ref="console"/> 92 </root> 93 </loggers> 94 95 </configuration>