內容
從實際項目需求出發,以最快的速度實現SpringBoot下Logback的配置。然后先后實踐測試了SpringBoot內置的基礎配置(SizeBasedTriggeringPolicy)、按時間划分日志文件的配置(TimeBasedRollingPolicy) 、同時按時間和大小划分日志文件的配置(SizeAndTimeBasedRollingPolicy)、不同級別日志輸出到不同日志文件四種情況。
版本
IDE:IDEA 2017.2.2 x64
JDK:1.8.0_171
manve:3.3.3
SpringBoot:1.5.9.RELEASE
Logback:1.1.11(SpringBoot中適配的版本)
適合人群
Java開發人員
說明
轉載請說明出處:SpringBoot從入門到進階——學會Logback日志的配置和搭建
GitHub倉庫:https://github.com/leo-zz/SpringBootDemo
參考
官方文檔中filters內容: https://Logback.qos.ch/manual/filters.html
官方文檔中appenders內容:https://Logback.qos.ch/manual/appenders.html
注意
修改過電腦時間后,intelij idea的光標無法聚焦到代碼區,需要重啟 intelij idea才能繼續編寫代碼。
步驟
快速配置出項目所需的Logback
項目對於日志的要求:
通常,我們對於項目的日志會有如下要求:
1、時效性:保存30天
2、按時間分割:每個小時生成1個文件夾
3、按大小分割:每個日志文件大小不超過50MB
4、限定體積:日志文件總大小20GB
5、按級別分割:不同級別的日志保存到不同的文件中
下面,我們先基於這些常見的需求,搭建出一個SpringBoot項目。
Logback配置:
application.yaml
#其他配置
...
#指明日志存放位置,在jar包所在路徑下的logs文件夾中
logging:
file: logs/application-gatewayservice-${server.port}.logs
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 引入SpringBoot的默認配置文件defaults.xml --> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/> <!-- 引入SpringBoot中內置的控制台輸出配置文件console-appender.xml --> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <!-- 引入自定義的文件輸出配置文件logback-spring-file-level.xml --> <include resource="logback-spring-file-level.xml" /> <!-- 設置root logger的級別為INFO,並將控制台輸出和文件輸出中的appender都添加到root logger下 --> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="WARN_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> <jmxConfigurator/> </configuration>
logback-spring-file-level.xml
<?xml version="1.0" encoding="UTF-8"?> <included> <!--只輸出INFO Level到日志文件的appender--> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--日志文件輸出的文件名,%i用來標記分割日志的序號 --> <fileNamePattern>${LOG_FILE}.INFOLevel.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern> <!-- 單個日志文件最大50MB, 保存30天的日志, 日志文件空間為20GB --> <maxFileSize>50MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <!--配置日志的級別過濾器,只保留INFO Level的日志--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度,%msg:日志消息,%n是換行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!--只輸出WARN Level到日志文件的appender--> <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- %i用來標記分割日志的序號 --> <fileNamePattern>${LOG_FILE}.WARNLevel.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern> <!-- 單個日志文件最大50MB, 保存30天的日志, 日志文件空間為20GB --> <maxFileSize>50MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <!--配置日志的級別過濾器,只保留WARN Level的日志--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>WARN</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!--只輸出ERROR Level到日志文件的appender--> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- %i用來標記分割日志的序號 --> <fileNamePattern>${LOG_FILE}.ERRORLevel.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern> <!-- 單個日志文件最大50MB, 保存30天的日志, 日志文件空間為20GB --> <maxFileSize>50MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <!--配置日志的級別過濾器,只保留ERROR Level的日志--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> </included>
項目啟動后,日志文件會保留在jar包所在路徑下的logs文件夾中,並且按照日志級別,輸出到不同的文件中。
同時,每小時會生成一個日志文件,如果當日志文件的大小超過50MB時,會分割日志文件。日志系統會保留30天的日志文件,且當所有日志文件的大小超過20GB時,日志系統會在下一天的0時,刪除部分日志文件,使日志文件總體的大小控制在20GB以內。
項目源碼地址:FastConfig
SpringBoot內置的基礎配置:
application.yaml
#指明日志存放位置
logging:
file: logs/application-gatewayservice-${server.port}.logs
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<jmxConfigurator/>
</configuration>
其中的base.xml,引自SpringBoot的jar包中的基礎配置:
base.xml
可以看到base.xml中引入了defaults.xml,以及控制台輸出日志配置文件console-appender.xml和文件輸出日志配置文件file-appender.xml。
<?xml version="1.0" encoding="UTF-8"?>
<!--Base logback configuration provided for compatibility with Spring Boot 1.1-->
<included>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<include resource="org/springframework/boot/logging/logback/file-appender.xml"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</included>
defaults.xml和console-appender.xml在本示例中不做討論。
file-appender.xml
通過文件輸出日志配置文件,可以看到默認情況下SpringBoot的日志系統限定單個文件大小為10MB。
<?xml version="1.0" encoding="UTF-8"?>
<included>
<appender name="FILE" class="ch.qos.Logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.Logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${LOG_FILE}.%i</fileNamePattern>
</rollingPolicy>
<triggeringPolicy class="ch.qos.Logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
</included>
打印日志的測試接口
@Controller public class LogController { Logger logger=Logger.getLogger("LogController"); @GetMapping("/log") @ResponseBody public String testLog(){ for (int i=0;i<1000;i++) logger.info(String.valueOf(i)); return "999"; } }
SizeBasedTriggeringPolicy的測試
使用Postman進行日志接口的重復調用,此處請求100次"/log"。
日志文件超過10MB,日志系統會自動分割日志文件,使得單個文件大小在10MB以內。
配置文件的fileNamePattern{LOG_FILE}.%i
中的i為序號。
通過本例的測試可知,最早的日志文件,序號越大,每次分割文件時,所有日志文件的序號都會更新。最早的日志文件序號始終是最大的。
如果刪除了舊的日志文件,會發生什么呢?
使用Postman再次調用日志測試接口生成日志,可以看到日志系統會重新編號。
觀察大小10MB的日志文件,其中大約9.6W行數據。因此平均一行的日志大概104個字節。
項目源碼地址:DefaultConfig
按時間划分日志文件
SpringBoot內置的日志系統使用SizeBasedTriggeringPolicy將日志文件在占用空間的維度進行分割。下面我們使用TimeBasedRollingPolicy,將日志文件按時間維度進行分割,示例保留了3天的日志,並且日志文件的最大容量為20MB。
項目配置文件application.yaml不變。
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <include resource="logback-spring-file.xml" /> <root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<jmxConfigurator/>
</configuration>
logback-spring-file.xml
<?xml version="1.0" encoding="UTF-8"?>
<included>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 此處的${LOG_FILE}是yaml文件中配置的logging.file屬性 %d{yyyy-MM-dd_HH}區分了年月日以及小時-->
<FileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd_HH}.log</FileNamePattern>
<!-- 日志文件保留近3天,並且最大容量為20MB -->
<MaxHistory>3</MaxHistory>
<totalSizeCap>20MB</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
</included>
注意:file-appender的配置,位置一定要放在<ROOT>標簽之前,否則報如下錯誤:
java.lang.IllegalStateException: Logback configuration error detected:
Could not find an appender named [FILE]. Did you define it below instead of above in the configuration file?
TimeBasedRollingPolicy測試
測試totalSizeCap
開啟項目后運行一段時間,可以觀察到日志文件會按照FileNamePattern中規定的最小時間周期HH(小時)分割日志文件。
使用Postman調用日志測試接口生成日志,使單個日志文件的大小超過20MB。
整點過后,發現13點和14點的日志文件被刪除,剩余日志文件的大小保持在20MB以內。
由此測試可知配置文件中totalSizeCap的含義:所有日志文件的大小在一個計時周期內(小時HH)可以超過totalSizeCap設定的容量,但是在下一個計時周期開始時,日志系統會清理日志文件,確保日志文件總體的大小在totalSizeCap以內。
測試MaxHistory
當日志容量不超過totalSizeCap時,更改系統日期為10月30日,10月31日,11月1日,11月2日,並調用日志測試接口生成日志。
當記錄11月2日的日志時,日志系統會清除11月1日的日志文件,保留10月30日、10月31日、11月2日三天的日志。
再將日期改成11月10日,調用日志測試接口生成日志,日志系統會清除11月2日的日志文件,保留10月30日、10月31日、11月10日三天的日志。由此可見MaxHistory只是限定保存日志的天數,而不是限定保存最近自然日日志的天數,
通過上面的觀察可以發現,在新的日志計時周期到來時,如果所有日志文件大小之和超過totalSizeCap,或日志記錄天數超過MaxHistory。日志系統會優先刪除最近日期的日志文件,使的日志大小在totalSizeCap內,且日志記錄天數在MaxHistory內。
項目源碼地址:TimeBasedConfig
同時按時間和大小划分日志文件的配置
配置文件application.yaml不變。
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<!--測試按照時間分割日志文件-->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<include resource="logback-spring-file.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
<jmxConfigurator/>
</configuration>
Logback-spring-file.xml
<?xml version="1.0" encoding="UTF-8"?>
<included>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- %i用來標記分割日志的序號 -->
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
<!-- 單個日志文件最大10MB, 保存3天的歷史日志, 所有日志文件最大50MB -->
<maxFileSize>10MB</maxFileSize>
<maxHistory>3</maxHistory>
<totalSizeCap>50MB</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
</included>
SizeAndTimeBasedRollingPolicy測試
項目啟動后的日志信息:
測試maxFileSize:
調用日志測試接口生成日志,當單個日志文件大小超過10MB時,會新建日志文件存儲超過10MB部分的日志內容,並且新日志文件的序號大於舊日志文件的序號(序號規則與Spring內置使用的SizeBasedTriggeringPolicy相反)。
測試maxHistory:
分別更改系統日期為10月30日,10月31日,11月1日,11月2日並調用日志測試接口生成日志。
當記錄11月2日的日志時,日志系統會清除11月1日的日志文件,保留10月30日,10月31日,11月2日三天的日志。
測試totalSizeCap:
調用日志測試接口生成日志,使所有日志文件大小之和超過50MB。此時日志仍然在記錄,日志文件大小還在增長。
將系統時間調至1小時后,繼續調用日志測試接口生成日志,日志文件大小超過60MB,此時日志仍然在記錄,日志文件大小還在增長。
將系統時間調至1天后,日志系統進行了日志文件的清理,日志系統清除了11月2日的日志文件,保留10月30日,10月31日,11月3日三天的日志,並使的日志文件大小在50MB范圍內。
可見此示例中SizeAndTimeBasedRollingPolicy清理的周期(一天)與TimeBasedRollingPolicy不同(一個小時)。
項目源碼地址:SizeAndTimeBasedConfig
按日志級別輸出到不同的文件中
配置文件application.yaml不變。
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--測試按照時間分割日志文件-->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<include resource="logback-spring-file-level.xml" /> <root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
<jmxConfigurator/>
</configuration>
logback-spring-file-level.xml
<?xml version="1.0" encoding="UTF-8"?>
<included>
<!--只保留INFO Level的日志-->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- %i用來標記分割日志的序號 -->
<fileNamePattern>${LOG_FILE}.INFOLevel.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
<!-- 單個日志文件最大10MB, 保存3天的歷史日志, 所有日志文件最大50MB -->
<maxFileSize>10MB</maxFileSize>
<maxHistory>3</maxHistory>
<totalSizeCap>50MB</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender> <!--只保留 WARN Level的日志-->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- %i用來標記分割日志的序號 -->
<fileNamePattern>${LOG_FILE}.WARNLevel.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
<!-- 單個日志文件最大10MB, 保存3天的歷史日志, 所有日志文件最大50MB -->
<maxFileSize>10MB</maxFileSize>
<maxHistory>3</maxHistory>
<totalSizeCap>50MB</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender> <!--保留ERROR Level的日志-->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- %i用來標記分割日志的序號 -->
<fileNamePattern>${LOG_FILE}.ERRORLevel.%d{yyyy-MM-dd_HH}.%i.log</fileNamePattern>
<!-- 單個日志文件最大10MB, 保存3天的歷史日志, 所有日志文件最大50MB -->
<maxFileSize>10MB</maxFileSize>
<maxHistory>3</maxHistory>
<totalSizeCap>50MB</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
</included>
Controller
@Controller public class TestLogController { Logger logger=Logger.getLogger("TestLogController"); @GetMapping("/logInfo") @ResponseBody public String testLog(){ for (int i=0;i<1000;i++) logger.info(String.valueOf(i)); return "999"; } @GetMapping("/logWarn") @ResponseBody public String testLogWarn(){ for (int i=0;i<1000;i++) logger.warning(String.valueOf(i)); return "999"; } @GetMapping("/logError") @ResponseBody public String testLogError(){ for (int i=0;i<1000;i++) logger.severe(String.valueOf(i)); return "999"; } }
按日志級別輸出到不同文件的測試:
先后調用測試接口 http://localhost:7081/logInfo、http://localhost:7081/logWarn、http://localhost:7081/logError,分別記錄INFO、WARN、ERRER級別的日志。
INFO日志
WARN日志
ERRER日志
可見,通過定義多個Appender,結合filter實現了不同級別的日志輸出到不同文件中。
項目源碼地址:LevelConfig