springboot整合logback用於日志輸出
我們項目的基本框架已經完成,http請求已經可以訪問,現在給我們的框架添加日志記錄的功能並能將每天的記錄記錄到文件中去
在這里,我們使用logback日志組件來進行項目的日志管理
(一).logback簡介
Logback是由Log4j的創始人設計的另一種開源日志組件,當前分為以下幾個模塊:
logback-core:是所有模塊的基礎
logback-classic:它是log4j的一個改良版本,同時它完整實現了slf4j API,使我們可以在其他日志系統,如log4j和JDK14 Logging中進行轉換
logback-access:訪問模塊和Servlet容器集成,提供通過Http來訪問日志的功能
(二) .整合示例
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
在resource中添加logback.xml配置文件
這里我們輸出了兩個日志文件,第一個日志文件輸出了所有的信息,包括普通的info信息、sql 以及報錯信息,相當於我們項啟動時控制台輸出的信息
第二個日志文件只輸出所有的報錯信息(error文件)
<?xml version="1.0" encoding="UTF-8"?>
<!--scan為true時,如果配置文件發生改變將會進行重新加載 -->
<!-- scanPeriod屬性設置監測配置文件修改的時間間隔,默認單位為毫秒,在scan為true時才生效 -->
<configuration debug="false" scan="false" scanPeriod="1 seconds">
<!--定義日志文件的存儲地址 勿在 LogBack 的配置中使用相對路徑-->
<property name="LOG_HOME" value="../logs" />
<!-- 彩色日志 -->
<!-- 彩色日志依賴的渲染類 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- Console 輸出設置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 不用彩色控制台輸出 -->
<!--<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">-->
<!--<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>-->
<!-- 按照每天生成日志文件 -->
<appender name="DAYINFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件輸出的文件名-->
<FileNamePattern>${LOG_HOME}/bailievable_info.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天數-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<!--<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</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>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<appender name="DAYERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件輸出的文件名-->
<FileNamePattern>${LOG_HOME}/bailievable_error.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天數-->
<MaxHistory>30</MaxHistory>
</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">
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度%msg:日志消息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 將控制指定name包下的所有類的日志的打印,通過level設置打印級別 -->
<logger name="org.springframework" level="ERROR"/>
<logger name="org.apache.commons" level="ERROR"/>
<logger name="org.mybatis" level="ERROR"/>
<logger name="microsoft.exchange.webservices.data" level="ERROR"/>
<logger name="org.apache.ibatis.io.ResolverUtil" level="ERROR"/>
<logger name="org.apache.ibatis.logging" level="ERROR"/>
<logger name="com.alibaba.druid" level="ERROR"/>
<logger name="org.apache.http" level="ERROR" />
<logger name="com.creditease.bixin.dao" level="debug" additivity="false">
<appender-ref ref="STDOUT" />
<appender-ref ref="DAYINFO" />
</logger>
<!-- 日志輸出級別 -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="DAYERROR" />
<appender-ref ref="DAYINFO" />
</root>
</configuration>
接下來啟動項目,然后訪問請求

我們發現日志打印出來了,接下來我們去看看文件有沒有生成

我們發現日志文件也順利生成了。接下來讓我們了解一些logback的原理和規則
Logger,Appenders 與 Layouts
在 logback 里,最重要的三個類分別是
- Logger
- Appender
- Layout
Logger 類位於 logback-classic 模塊中, 而 Appender 和 Layout 位於 logback-core 中,這意味着, Appender 和 Layout 並不關心 Logger 的存在,不依賴於 Logger,同時也能看出, Logger 會依賴於 Appender 和 Layout 的協助,日志信息才能被正常打印出來。
分層命名規則
為了可以控制哪些信息需要輸出,哪些信息不需要輸出,logback 中引進了一個 分層 概念。每個 logger 都有一個 name,這個 name 的格式與 Java 語言中的包名格式相同。這也是前面的例子中直接把一個 class 對象傳進 LoggerFactory.getLogger() 方法作為參數的原因。
logger 的 name 格式決定了多個 logger 能夠組成一個樹狀的結構,為了維護這個分層的樹狀結構,每個 logger 都被綁定到一個 logger 上下文中,這個上下文負責厘清各個 logger 之間的關系。
例如, 命名為 io.beansoft 的 logger,是命名為 io.beansoft.logback 的 logger 的父親,是命名為 io.beansoft.logback.demo 的 logger 的祖先。
在 logger 上下文中,有一個 root logger,作為所有 logger 的祖先,這是 logback 內部維護的一個 logger,並非開發者自定義的 logger。
可通過以下方式獲得這個 logger :
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
日志打印級別
logger 有日志打印級別,可以為一個 logger 指定它的日志打印級別。
如果不為一個 logger 指定打印級別,那么它將繼承離他最近的一個有指定打印級別的祖先的打印級別。這里有一個容易混淆想不清楚的地方,如果 logger 先找它的父親,而它的父親沒有指定打印級別,那么它會立即忽略它的父親,往上繼續尋找它爺爺,直到它找到 root logger。因此,也能看出來,要使用 logback, 必須為 root logger 指定日志打印級別。
日志打印級別從低級到高級排序的順序是:
TRACE < DEBUG < INFO < WARN < ERROR
如果一個 logger 允許打印一條具有某個日志級別的信息,那么它也必須允許打印具有比這個日志級別更高級別的信息,而不允許打印具有比這個日志級別更低級別的信息。
package io.beansoft.logback.demo.universal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; /** * * * @author beanlam * @date 2017年2月10日 上午12:20:33 * @version 1.0 * */ public class LogLevelDemo { public static void main(String[] args) { //這里強制類型轉換時為了能設置 logger 的 Level ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo"); logger.setLevel(Level.INFO); Logger barlogger = LoggerFactory.getLogger("com.foo.Bar"); // 這個語句能打印,因為 WARN > INFO logger.warn("can be printed because WARN > INFO"); // 這個語句不能打印,因為 DEBUG < INFO. logger.debug("can not be printed because DEBUG < INFO"); // barlogger 是 logger 的一個子 logger // 它繼承了 logger 的級別 INFO // 以下語句能打印,因為 INFO >= INFO barlogger.info("can be printed because INFO >= INFO"); // 以下語句不能打印,因為 DEBUG < INFO barlogger.debug("can not be printed because DEBUG < INFO"); } }
打印結果是:
WARN com.foo - can be printed because WARN > INFO
INFO com.foo.Bar - can be printed because INFO >= INFO
logback 內部運行流程

當應用程序發起一個記錄日志的請求,例如 info() 時,logback 的內部運行流程如下所示
- 獲得過濾器鏈條
- 檢查日志級別以決定是否繼續打印
- 創建一個
LoggingEvent對象 - 調用 Appenders
- 進行日志信息格式化
- 發送
LoggingEvent到對應的目的地
有關性能問題
關於日志系統,人們討論得最多的是性能問題,即使是小型的應用程序,也有可能輸出大量的日志。打印日志中的不當處理,會引發各種性能問題,例如太多的日志記錄請求可能使磁盤 IO 成為性能瓶頸,從而影響到應用程序的正常運行。在合適的時候記錄日志、以更好的方式發起日志請求、以及合理設置日志級別方面,都有可能造成性能問題。
關於性能問題,以下幾個方面需要了解
- 建議使用占位符的方式參數化記錄日志
- logback 內部機制保證 logger 在記錄日志時,不必每一次都去遍歷它的父輩以獲得關於日志級別、Appender 的信息
- 在 logback 中,將日志信息格式化,以及輸出到目的地,是最損耗性能的操作
