前言
后台程序開發及上線時,一般都會用到Log信息打印及Log日志記錄,開發時通過Log信息打印可以快速的定位問題所在,幫助我們快捷開發。程序上線后如遇到Bug或錯誤,此時則需要日志記錄來查找發現問題所在。
Spring Boot 可以集成很多不同的日志系統,目前有關日志的開源代碼很多,如log4j、sl4j和log4j2,為什么我選擇使用log4j2呢,看完下面兩篇性能的對比,相信你也會選擇log4j2
http://www.jianshu.com/p/483a9cf61c36
https://blog.souche.com/logback-log4j-log4j2shi-ce/?utm_source=tuicool&utm_medium=referral
其中最常用的Apache Log4j,而Log4j 2是Log4j的升級版本,Log4j 2相對於Log4j 1.x 有了很多顯著的改善。所以這篇博客就直接來說說Spring Boot如何集成並配置使用Log4j2.
引入Log4j2依賴包
你使用Maven,則在pom.xml文件中添加以下依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
logging.level.root=error
配置log4j2.xml文件
log4j2配置可以多樣化,支持xml、json、yml格式的配置。如果不配置會使用默認配置
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configuration后面的status,這個用於設置log4j2自身內部的信息輸出,可以不設置,當設置成trace時,
你會看到log4j2內部各種詳細輸出。可以設置成OFF(關閉)或Error(只輸出錯誤信息)
-->
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="App">demo</Property>
<Property name="logDir">logs</Property>
<Property name="splitSize">30 MB</Property>
</Properties>
<Appenders>
<!-- 輸出控制台日志的配置 -->
<Console name="console" target="SYSTEM_OUT">
<!--控制台只輸出level及以上級別的信息(onMatch),其他的直接拒絕(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 輸出日志的格式 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<!-- 打印出所有的信息,每次大小超過size,則這size大小的日志會自動存入按年份-月份建立的文件夾下面並進行壓縮,作為存檔 -->
<RollingRandomAccessFile name="infoLog" fileName="${logDir}/${App}-info.log" immediateFlush="false"
filePattern="${logDir}/$${date:yyyy-MM}/${App}-info-%d{MM-dd-yyyy}-%i.log.gz"
append="true">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} [%t] %-5level %logger{36} %L %M - %msg%xEx%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<SizeBasedTriggeringPolicy size="${splitSize}"/>
</Policies>
<Filters>
<!-- 只記錄info和warn級別信息 -->
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<!-- 指定每天的最大壓縮包個數,默認7個,超過了會覆蓋之前的 -->
<DefaultRolloverStrategy max="50"/>
</RollingRandomAccessFile>
<!-- 存儲所有error信息 -->
<RollingRandomAccessFile name="errorLog" fileName="${logDir}/${App}-error.log" immediateFlush="false"
filePattern="${logDir}/$${date:yyyy-MM}/${App}-error-%d{MM-dd-yyyy}-%i.log.gz"
append="false">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} [%t] %-5level %logger{36} %L %M - %msg%xEx%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<SizeBasedTriggeringPolicy size="${splitSize}"/>
</Policies>
<Filters>
<!-- 只記錄error級別信息 -->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<!-- 指定每天的最大壓縮包個數,默認7個,超過了會覆蓋之前的 -->
<DefaultRolloverStrategy max="50"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- root logger 配置,全局配置,默認所有的Logger都繼承此配置 -->
<!-- AsyncRoot - 異步記錄日志 - 需要LMAX Disruptor的支持 -->
<Root includeLocation="true" additivity="true">
<AppenderRef ref="infoLog"/>
<AppenderRef ref="errorLog"/>
<AppenderRef ref="console"/>
</Root>
<!--第三方的軟件日志級別 -->
<logger name="org.springframework" level="info" additivity="true">
</logger>
</Loggers>
</Configuration>
三個appender:console、infoLog、errorLog
-
console 通過ThresholdFilter過濾規則只輸出info級別的錯誤(onMatch="ACCEPT" onMismatch="DENY" 匹配到的接受,沒有匹配的走人)
-
infoLog 也通過ThresholdFilter的方式輸出到日志,當然了append="true" 會在服務每次啟動的時候追加日志
status和monitorInterval含義
我們看到,Configuration有2個屬性status和monitorInterval,它們分別是log4j2自身組件的日志級別以及重新刷新配置文件的時間,通過配置status可以看到log4j2相關的日志,配置monitorInterval可以通過修改配置文件來改變日志配置。
log4j2的層次結構
從上面的log4j2.xml配置文件中,我們可以看到log4j2的配置文件中主要分為2塊,一塊為appender,描述了如何記錄日志,另外一部分是log config,記錄了哪種日志對應哪種級別。 而不同的LogConfig之間其實是有繼承關系的,子LogConfig會繼承parent的屬性,而所有LogConfig都繼承自Root LogConfig。所以即使只配置了root logger,你一樣可以在任何地方通過LoggerFactory.getLogger獲取一個logger對象,記錄日志。
那么日志之間的繼承關系是由什么決定的呢?看看上面的配置文件中root以外的2個logConfig,只有3個配置:日志級別,name以及appender,從直覺上看應當是name最可能決定了LogConfig的繼承關系,其實也正是如此:com.foo是com.foo.Bar的父級;java是java.util的父級,是java.util.vector的祖先(注意name區分大小寫)。
理解了這一點,我們就能理清log4j2配置的思路:先配置一個root,讓所有需要使用日志的logger繼承,然后對有特別需要的logger進行特殊的配置,比如我們希望org.springframework包只記錄error以及warn級別的log,再比如,我們希望能顯示mybatis執行的sql的日志,都可以進行個性化的配置。
appender的配置
appender是LogConfig的重要組成部分,一個LogConfig可以使用多個appender,一個appender也可以被多個LogConfig使用,appender多種多樣,不同的appender也有不同的屬性和配置,難以一一闡述,需要使用時可以直接查看文檔來進行個性化配置。不過就filters可以單獨拿出來討論一下。
filter有兩個重要屬性onMatch和onMismatch。可以有DENY、ACCEPT或NEUTRAL配置:
- DENY說明不由當前appender處理
- ACCEPT說明由當前filter處理
- NEUTRAL說明如果按順序還有其他filter則由其他filter處理,如果當前filter已經是最后一個filter,則由當前appender處理。
異步日志
配置方式
log4j2的官方文檔建議記錄程序行為日志異步日志,效率更高。因為異步日志使用的是無鎖技術,所以需要引入Disruptor。然后可以通過配置異步的appender或Logger來實現異步日志
<asyncRoot level="DEBUG">
<appender-ref ref="infoLog" />
<appender-ref ref="Console" />
</asyncRoot>
或
<Async name="Async">
<AppenderRef ref="infoLog"/>
</Async>
異步日志工作原理
AsyncAppender采用的是生產者消費者的模型進行異步地將Logging Event送到對應的Appender中。
-
a、 生產者
外部應用了Log4j的系統的實時線程,實時將Logging Event傳送進AsyncAppender里 -
b、 中轉:Buffer和DiscardSummary
-
c、 消費者:Dispatcher線程和appenders
工作原理:
1) Logging Event進入AsyncAppender,AsyncAppender會調用append方法,在append方法中會去把logging Event填入Buffer中,當消費能力不如生產能力時,AsyncAppender會把超出Buffer容量的Logging Event放到DiscardSummary中,作為消費速度一旦跟不上生成速度,中轉buffer的溢出處理的一種方案。
2) AsyncAppender有個線程類Dispatcher,它是一個簡單的線程類,實現了Runnable接口。它是AsyncAppender的后台線程。
Dispatcher所要做的工作是:
① 鎖定Buffer,讓其他要對Buffer進行操作的線程阻塞。
② 看Buffer的容量是否滿了,如果滿了就將Buffer中的Logging Event全部取出,並清空Buffer和DiscardSummary;如果沒滿則等待Buffer填滿Logging Event,然后notify Disaptcher線程。
③ 將取出的所有Logging Event交給對應appender進行后面的日志信息推送。
以上是AsyncAppender類的兩個關鍵點:append方法和Dispatcher類,通過這兩個關鍵點實現了異步推送日志信息的功能,這樣如果大量的Logging Event進入AsyncAppender,就可以游刃有余地處理這些日志信息了。
性能影響
log4j對系統性能的影響程度主要體現在以下幾方面:
- a、輸出的目的地
輸出到控制台的速度比輸出到文件系統的速度要慢。 - b、輸出格式
輸出格式不一樣對性能也會有影響,如簡單輸出布局(SimpleLayout)比格式化輸出布局(PatternLayout)輸出速度要快。可以根據需要盡量采用簡單輸出布局格式輸出日志信息。 - c、日志級別
輸出級別越低輸出的日志內容就越多,對系統系能影響很大。 - d、輸出方式
輸出方式的不同,對系統系能也是有一定影響的,采用異步輸出方式比同步輸出方式性能要高。 - e、輸出事件
每次接收到日志輸出事件就打印一條日志內容比當日志內容達到一定大小時打印系能要低。
緩存設置
設置日志輸出為異步方式
<appender name="DRFOUT" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="logs/brws.log" />
<param name="Append" value="true" />
<param name="DatePattern" value="yyyy_MM_dd'.'" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %l %x - %m%n" />
</layout>
</appender>
<appender name="ASYNCOUT" class="org.apache.log4j.AsyncAppender">
<param name="BufferSize" value="512" />
<appender-ref ref="DRFOUT" />
</appender>
-
同步情況
各線程直接獲得輸出流進行輸出(線程間不需要同步)。 -
異步情況:
1.各線程將日志寫到緩存,繼續執行下面的任務(這里是異步的)
2.日志線程發現需要記日志時獨占緩存(與此同時各線程等待,此時各線程是被阻塞住的),從緩存中取出日志信息,獲得輸出流進行輸出,將緩存解鎖(各線程收到提醒,可以接着寫日志了)- a、將日志記錄到本地文件
同樣都是寫本地文件Log4j本身有一個buffer處理入庫,采用異步方式並不一定能提高性能(主要是如何配置好緩存大小);而線程間的同步開銷則是非常大的!因此在使用本地文件記錄日志時不建議使用異步方式。 - b、將日志記錄到JMS
JMS本身是支持異步消息的,如果不考慮JMS消息創建的開銷,也不建議使用異步方式。 - c、將日子記錄到SOCKET
將日志通過Socket發送,純網絡IO操作不需要反饋,因此也不會耗時 - d、將日志記錄到數據庫
眾所周知JDBC是幾種方式中最耗時的:網絡、磁盤、數據庫事務,都使JDBC操作異常的耗時,在這里采用異步方式入庫倒是一個不錯的選擇。 - e、將日志記錄到SMTP 同JDBC
- a、將日志記錄到本地文件
動態修改日志級別
動態修改日志級別是一個很實用的功能,關於如果動態修改日志級別,請參考美團的一篇文章:日志級別動態調整——小工具解決大問題
[參考]
log4j2 配置簡要分析
Spring Boot系列之Log4j2的配置使用
springboot日志體系---log4j2
log4j日志輸出性能優化-緩存、異步
轉自:https://www.jianshu.com/p/46b530446d20
