Log4j2 簡明教程(轉載)
一、概述
log4j2官方文件內容非常多,要一次性瞭解全部是不可能的。正確的步驟應當是先了解最常見的配置,當發現原有知識無法解決問題,再重新檢視文件看有沒有合適的配置。
下面將從檔案結構入手,再到簡單的例項,從例項入手分析常見的配置的用途,其中涉及其中包括Appenders, Filters, Layout, Lookups的知識,最後根據學習。
可以搜尋到的關於log4j2的教程非常少,這篇文章更多的是讓大家對log4j2有個大體的瞭解,免得大家看到官方文件那麼多就暈了!
歡迎關注我的github:https://github.com/benson-lin
如果覺得排版不好,可以訪問:http://blog.bensonlin.me/post/log4j2-tutorial
log4j2.xml檔案結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version= "1.0" encoding= "UTF-8" ?>;
<Configuration>
<Properties>
<Property name= "name1" >value</property>
<Property name= "name2" value= "value2" />
</Properties>
<Filter type= "type" ... />
<Appenders>
<Appender type= "type" name= "name" >
<Filter type= "type" ... />
</Appender>
...
</Appenders>
<Loggers>
<Logger name= "name1" >
<Filter type= "type" ... />
</Logger>
...
<Root level= "level" >
<AppenderRef ref= "name" />
</Root>
</Loggers>
</Configuration>
|
下面是一個比較完整的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<?xml version= "1.0" encoding= "UTF-8" ?>
<!-- 設定log4j2的自身log級別為warn -->
<!-- OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<configuration status= "WARN" monitorInterval= "30" >
<appenders>
<console name= "Console" target= "SYSTEM_OUT" >
<PatternLayout pattern= "[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
</console>
<RollingFile name= "RollingFileInfo" fileName= "${sys:user.home}/logs/info.log"
filePattern= "${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log" >
<!--控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)-->
<Filters>
<ThresholdFilter level= "INFO" />
<ThresholdFilter level= "WARN" onMatch= "DENY" onMismatch= "NEUTRAL" />
</Filters>
<PatternLayout pattern= "[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size= "100 MB" />
</Policies>
</RollingFile>
<RollingFile name= "RollingFileWarn" fileName= "${sys:user.home}/logs/warn.log"
filePattern= "${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log" >
<Filters>
<ThresholdFilter level= "WARN" />
<ThresholdFilter level= "ERROR" onMatch= "DENY" onMismatch= "NEUTRAL" />
</Filters>
<PatternLayout pattern= "[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size= "100 MB" />
</Policies>
</RollingFile>
<RollingFile name= "RollingFileError" fileName= "${sys:user.home}/logs/error.log"
filePattern= "${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log" >
<ThresholdFilter level= "ERROR" />
<PatternLayout pattern= "[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size= "100 MB" />
</Policies>
</RollingFile>
</appenders>
<loggers>
<!--過濾掉spring和mybatis的一些無用的DEBUG資訊-->
<logger name= "org.springframework" level= "INFO" ></logger>
<logger name= "org.mybatis" level= "INFO" ></logger>
<root level= "all" >
<appender-ref ref= "Console" />
<appender-ref ref= "RollingFileInfo" />
<appender-ref ref= "RollingFileWarn" />
<appender-ref ref= "RollingFileError" />
</root>
</loggers>
</configuration>
|
log4j2有預設的配置,如果要替換配置,只需要在classpath根目錄下放置log4j2.xml。
log4j 2.0與以往的1.x有一個明顯的不同,其配置檔案只能採用.xml, .json或者 .jsn。在預設情況下,系統選擇configuration檔案的優先順序如下:(classpath為src資料夾)
- classpath下名為 log4j-test.json 或者log4j-test.jsn檔案
- classpath下名為 log4j2-test.xml
- classpath下名為 log4j.json 或者log4j.jsn檔案
- classpath下名為 log4j2.xml
如果本地要測試,可以把log4j2-test.xml放到classpath,而正式環境使用log4j2.xml,則在打包部署的時候不要打包log4j2-test.xml即可。
下面是其預設配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version= "1.0" encoding= "UTF-8" ?>
<Configuration status= "WARN" >
<Appenders>
<Console name= "Console" target= "SYSTEM_OUT" >
<PatternLayout pattern= "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level= "error" >
<AppenderRef ref= "Console" />
</Root>
</Loggers>
</Configuration>
|
下面將對上面的配置檔案進行一一講解。
二、示例Java程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package com.foo;
// Import log4j classes.
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class MyApp {
// Define a static logger variable so that it references the
// Logger instance named "MyApp".
private static final Logger logger = LogManager.getLogger(MyApp. class );
public static void main( final String... args) {
// Set up a simple configuration that logs on the console.
logger.trace( "Entering application." );
Bar bar = new Bar();
if (!bar.doIt()) {
logger.error( "Didn't do it." );
}
logger.trace( "Exiting application." );
}
}
package com.foo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Bar {
static final Logger logger = LogManager.getLogger(Bar. class .getName());
public boolean doIt() {
logger.entry();
logger.error( "Did it again!" );
return logger.exit( false );
}
}
|
如果使用如下配置,也就是預設配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version= "1.0" encoding= "UTF-8" ?>
<Configuration status= "WARN" >
<Appenders>
<Console name= "Console" target= "SYSTEM_OUT" >
<PatternLayout pattern= "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level= "error" >
<AppenderRef ref= "Console" />
</Root>
</Loggers>
</Configuration>
|
輸出如下:只輸出error以上的日誌資訊
1 2 |
17 : 13 : 01.540 [main] ERROR com.foo.Bar - Did it again!
17 : 13 : 01.540 [main] ERROR MyApp - Didn't do it.
|
如果我們希望除了com.foo.Bar類下輸出TRACE以上到控制檯外,其他停止TRACE的輸出到控制檯,只輸出ERROR以上的日誌。可以如下配置:
1 2 3 4 5 6 |
<Loggers>
<Logger name= "com.foo.Bar" level= "TRACE" />
<Root level= "ERROR" >
<AppenderRef ref= "STDOUT" >
</Root>
</Loggers>
|
結果如下:
1 2 3 4 |
14 : 14 : 17.176 [main] TRACE com.foo.Bar - Enter
14 : 14 : 17.182 [main] ERROR com.foo.Bar - Did it again!
14 : 14 : 17.182 [main] TRACE com.foo.Bar - Exit with( false )
14 : 14 : 17.182 [main] ERROR com.foo.MyApp - Didn't do it.
|
因為com.foo.Bar沒有自己的Appender,所以會使用ROOT的Appender,如果自己也配置了在控制檯列印,就要注意可加性:如下配置,會ERROR以上的會列印兩次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version= "1.0" encoding= "UTF-8" ?>
<Configuration status= "WARN" >
<Appenders>
<Console name= "Console" target= "SYSTEM_OUT" >
<PatternLayout pattern= "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Logger name= "com.foo.Bar" level= "trace" >
<AppenderRef ref= "Console" />
</Logger>
<Root level= "error" >
<AppenderRef ref= "Console" />
</Root>
</Loggers>
</Configuration>
|
結果如下
1 2 3 4 5 6 7 |
14 : 11 : 27.103 [main] TRACE com.foo.Bar - Enter
14 : 11 : 27.103 [main] TRACE com.foo.Bar - Enter
14 : 11 : 27.106 [main] ERROR com.foo.Bar - Did it again!
14 : 11 : 27.106 [main] ERROR com.foo.Bar - Did it again!
14 : 11 : 27.107 [main] TRACE com.foo.Bar - Exit with( false )
14 : 11 : 27.107 [main] TRACE com.foo.Bar - Exit with( false )
14 : 11 : 27.107 [main] ERROR com.foo.MyApp - Didn't do it.
|
如果我們確實有這種需求(不想遵循父類的Appender),可以加上additivity="false"
引數。如下配置,com.foo.Bar的trace以上日誌將儲存到檔案中,並且不會列印到控制檯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<Configuration status= "WARN" >
<Appenders>
<Console name= "Console" target= "SYSTEM_OUT" >
<PatternLayout pattern= "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<RollingFile name= "RollingFile" fileName= "${sys:user.home}/logs/trace.log"
filePattern= "${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log" >
...
</RollingFile>
</Appenders>
<Loggers>
<Logger name= "com.foo.Bar" level= "trace" additivity= "false" >
<AppenderRef ref= "RollingFile" />
</Logger>
<Root level= "error" >
<AppenderRef ref= "Console" />
</Root>
</Loggers>
</Configuration>
|
log4j2支援自動重新配置,如果配置了monitorInterval,那麼log4j2每隔一段時間就會檢查一遍這個檔案是否修改。最小是5s
1 2 3 4 |
<?xml version= "1.0" encoding= "UTF-8" ?>
<Configuration monitorInterval= "30" >
...
</Configuration>
|
三、Appenders
ConsoleAppender
將使用 System.out 或 System.err輸出到控制檯。
可以有如下引數
- name:Appender的名字
- target:SYSTEM_OUT 或 SYSTEM_ERR,預設是SYSTEM_OUT
- layout:如何格式化,如果沒有預設是%m%n
典型的ConsoleAppender如下
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version= "1.0" encoding= "UTF-8" ?>
<Configuration status= "warn" name= "MyApp" packages= "" >
<Appenders>
<Console name= "STDOUT" target= "SYSTEM_OUT" >
<PatternLayout pattern= "%m%n" />
</Console>
</Appenders>
<Loggers>
<Root level= "error" >
<AppenderRef ref= "STDOUT" />
</Root>
</Loggers>
</Configuration>
|
RollingFileAppender
顧名思義,日誌檔案回滾,也就是刪除最舊的日誌檔案,預設是3個檔案。可以通過DefaultRolloverStrategy設定max引數為多個
例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<Appenders>
<RollingFile name= "RollingFile" fileName= "logs/app.log"
filePattern= "logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz" >
<PatternLayout>
<Pattern>%d %p %c{ 1 .} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size= "250 MB" />
</Policies>
<DefaultRolloverStrategy max= "20" />
</RollingFile>
</Appenders>
|
現在說說TimeBasedTriggeringPolicy和SizeBasedTriggeringPolicy的作用。
第一個是基於時間的rollover,第二個是基於大小的rollover。第二個很容易理解,如果大小大於某個閾值,上面是50MB的時候就會滾動。
TimeBasedTriggeringPolicy中有其中一個引數是interval,表示多久滾動一次。預設是1 hour。modulate=true用來調整時間:比如現在是早上3am,interval是4,那麼第一次滾動是在4am,接著是8am,12am...而不是7am
四、Layouts
這裡只描述最常見的PatternLayout!更多看官方文件Layouts
1 2 3 4 5 6 7 8 9 10 |
<RollingFile name= "RollingFileError" fileName= "${sys:user.home}/logs/error.log"
filePattern= "${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log" >
<ThresholdFilter level= "ERROR" />
<PatternLayout pattern= "[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size= "50 MB" />
</Policies>
<DefaultRolloverStrategy max= "20" />
</RollingFile>
|
上面的%是什麼含義,還有哪些呢?其實最主要的引數還是%d, %p, %l, %m, %n, %X。下面的圖是摘取網上的。
%X用來獲取MDC記錄,這些記錄從哪來的?我們可以使用org.apache.logging.log4j.ThreadContext將需要記錄的值put進去。(我發現slf的MDC.java的put方法對log4j2不可用,因為底層依賴的是log4j1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.bensonlin.service.web.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.ThreadContext;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MDCInterceptor implements HandlerInterceptor {
public final static String USER_KEY = "user_id" ;
public final static String REQUEST_REQUEST_URI = "request_uri" ;
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg2)
throws Exception {
ThreadContext.put(REQUEST_REQUEST_URI, httpServletRequest.getRequestURI());
return true ;
}
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg2,
ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object arg2, Exception exception) throws Exception {
ThreadContext.remove(USER_KEY);
ThreadContext.remove(REQUEST_REQUEST_URI);
}
public static void setUserKeyForMDC(String userId) {
ThreadContext.put(USER_KEY, userId);
}
}
|
xml中使用%X{aaa}取出來:
1 2 3 |
<console name= "Console" target= "SYSTEM_OUT" >
<PatternLayout pattern= "%X{user_id} %X{request_uri} [%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
</console>
|
對應ThreadContext的文件在這裡
五、Filters
Filters決定日誌事件能否被輸出。過濾條件有三個值:ACCEPT(接受), DENY(拒絕) or NEUTRAL(中立).
ACCEP和DENY比較好理解就是接受和拒絕的意思,在使用單個過濾器的時候,一般就是使用這兩個值。但是在組合過濾器中,如果用接受ACCEPT的話,日誌資訊就會直接寫入日誌檔案,後續的過濾器不再進行過濾。所以,在組合過濾器中,接受使用NEUTRAL(中立),被第一個過濾器接受的日誌資訊,會繼續用後面的過濾器進行過濾,只有符合所有過濾器條件的日誌資訊,才會被最終寫入日誌檔案。
ThresholdFilter
有幾個引數:
- level:將被過濾的級別。
- onMatch:預設值是NEUTRAL
- onMismatch:預設是DENY
如果LogEvent 中的 Log Level 大於 ThresholdFilter 中配置的 Log Level,那麼返回 onMatch 的值, 否則返回 onMismatch 的值,例如 : 如果ThresholdFilter 配置的 Log Level 是 ERROR , LogEvent 的Log Level 是 DEBUG。 那麼 onMismatch 的值將被返回, 因為 ERROR 小於DEBUG。如果是Accept,將自己被接受,而不經過下一個過濾器
下面的例子可以這樣理解:如果是INFO級別及其以上,將經過通過第一個過濾,進入第二個,否則是onMismatch:拒絕進入。對於第二個,如果是大於等於WARN(WARN/ERROR/ERROR),那麼將返回onMatch,也就是拒絕,如果是其他情況(也就是INFO),將是中立情況,因為後面沒有其他過濾器,則被接受。最後的結果就只剩下INFO級別的日誌。也就符合了RollingFileInfo只記錄Info級別的規則。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<RollingFile name= "RollingFileInfo" fileName= "${sys:user.home}/logs/info.log"
filePattern= "${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log" >
<!--控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)-->
<Filters>
<ThresholdFilter level= "INFO" />
<ThresholdFilter level= "WARN" onMatch= "DENY" onMismatch= "NEUTRAL" />
</Filters>
<PatternLayout pattern= "[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval= "24" modulate= "true" />
<SizeBasedTriggeringPolicy size= "50 MB" />
</Policies>
<DefaultRolloverStrategy max= "20" />
</RollingFile>
|
六、Lookups
提供另外一種方式新增某些特殊的值到日誌中。
Date Lookup
與其他lookup不同,它不是通過key去查詢值,而是通過SimpleDateFormat驗證格式是否有效,然後記錄當前時間
1 2 3 4 5 6 |
<RollingFile name= "Rolling-${map:type}" fileName= "${filename}" filePattern= "target/rolling1/test1-$${date:MM-dd-yyyy}.%i.log.gz" >
<PatternLayout>
<pattern>%d %p %c{ 1 .} [%t] %m%n</pattern>
</PatternLayout>
<SizeBasedTriggeringPolicy size= "500" />
</RollingFile>
|
Context Map Lookup: 如記錄loginId
1 2 3 4 5 |
<File name= "Application" fileName= "application.log" >
<PatternLayout>
<pattern>%d %p %c{ 1 .} [%t] $${ctx:loginId} %m%n</pattern>
</PatternLayout>
</File>
|
這個的結果和前面的MDC是一樣的,即 %X{loginId}
Environment Lookup:記錄系統環境變數
比如可以獲取如/etc/profile
中的變數值
1 2 3 4 5 |
<File name= "Application" fileName= "application.log" >
<PatternLayout>
<pattern>%d %p %c{ 1 .} [%t] $${env:USER} %m%n</pattern>
</PatternLayout>
</File>
|
System Properties Lookup
可以獲取Java中的系統屬性值。
1 2 3 |
<Appenders>
<File name= "ApplicationLog" fileName= "${sys:logPath}/app.log" />
</Appenders>
|
和系統屬性值有什麼區別呢?其實就是System.getProperties();和System.getenv();的區別。下面是一個小例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package com.bensonlin.service.common;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
public class Main {
public static void main(String[] args) {
Properties properties = System.getProperties();
Iterator i = properties.entrySet().iterator();
while (i.hasNext()) {
Map.Entry entry = (Map.Entry) i.next();
Object key = entry.getKey();
Object value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println( "===================" );
Map map = System.getenv();
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
System.out.print(entry.getKey() + "=" );
System.out.println(entry.getValue());
}
}
}
|
輸出(摘取部分):
1 2 3 4 5 6 7 8 9 10 11 |
java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path=C:\Program Files\Java\jdk1. 8 .0_25\jre\bin
java.vm.version= 25.25 -b02
java.vm.vendor=Oracle Corporation
java.vendor.url=http: //java.oracle.com/
path.separator=;
...
===================
JAVA_HOME=C:\Program Files\Java\jdk1. 8 .0_25
TEMP=D:\Temp
ProgramFiles=C:\Program Files
|
...
可以看到其實Environment是獲取環境變數,而System Properties獲取的更多是與Java相關的值
轉載註明地址:http://blog.bensonlin.me/post/log4j2-tutorial