• Log4j2 简明教程



    一、概述

    log4j2官方文档内容非常多,要一次性了解全部是不可能的。正确的步骤应当是先了解最常见的配置,当发现原有知识无法解决问题,再重新查看文档看有没有合适的配置。
    下面将从文件结构入手,再到简单的实例,从实例入手分析常见的配置的用途,其中涉及其中包括Appenders, Filters, Layout, Lookups的知识,最后根据学习。

    可以搜索到的关于log4j2的教程非常少,这篇文章更多的是让大家对log4j2有个大体的了解,免得大家看到官方文档那么多就晕了!

    欢迎关注我的github: https://github.com/benson-lin

    如果觉得排版不好,可以访问:http://blog.bensonlin.me/post/log4j2-tutorial

    log4j2.xml文件结构

    <?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>
    

      

    下面是一个比较完整的例子:

    <?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即可。

    下面是其缺省配置:

    <?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代码

    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);
      }
    }
    

      

    如果使用如下配置,也就是缺省配置:

    <?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以上的日志信息

    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以上的日志。可以如下配置:

      <Loggers>
        <Logger name="com.foo.Bar" level="TRACE"/>
        <Root level="ERROR">
          <AppenderRef ref="STDOUT">
        </Root>
      </Loggers>

    结果如下:

    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以上的会打印两次

    <?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>

    结果如下

    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以上日志将保存到文件中,并且不会打印到控制台。

    <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

    <?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如下

    <?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参数为多个

    例子如下:

    <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

    <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)

    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}取出来:

    <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级别的规则。

    <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验证格式是否有效,然后记录当前时间

    <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

    <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中的变量值

    <File name="Application" fileName="application.log">
      <PatternLayout>
        <pattern>%d %p %c{1.} [%t] $${env:USER} %m%n</pattern>
      </PatternLayout>
    </File>
    

      

    System Properties Lookup

    可以获取Java中的系统属性值。

    <Appenders>
      <File name="ApplicationLog" fileName="${sys:logPath}/app.log"/>
    </Appenders>

    和系统属性值有什么区别呢?其实就是System.getProperties();和System.getenv();的区别。下面是一个小例子。

    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());
            }
        }
    }

    输出(摘取部分):

    java.runtime.name=Java(TM) SE Runtime Environment
    sun.boot.library.path=C:Program FilesJavajdk1.8.0_25jrein
    java.vm.version=25.25-b02
    java.vm.vendor=Oracle Corporation
    java.vendor.url=http://java.oracle.com/
    path.separator=;
    ...
    ===================
    JAVA_HOME=C:Program FilesJavajdk1.8.0_25
    TEMP=D:Temp
    ProgramFiles=C:Program Files
    ...

    可以看到其实Environment是获取环境变量,而System Properties获取的更多是与Java相关的值

    转载注明地址:http://blog.bensonlin.me/post/log4j2-tutorial

  • 相关阅读:
    阿里巴巴校招内推简历筛选方案
    SIFT中的尺度空间和传统图像金字塔
    boost的编译
    H264与RTP
    link2001错误无法解析外部符号metaObject
    windows 7下qtcreator里QWT文件的pro配置
    电脑键盘上你所不知道的秘密,学会了很牛气!
    http://blog.csdn.net/chenriwei2/article/details/38047119
    Seaborn中的kdeplot、rugplot、distplot与jointplot
    8-Pandas扩展之Pandas提升性能的方法(eval()、query())
  • 原文地址:https://www.cnblogs.com/lzb1096101803/p/5796849.html
Copyright © 2020-2023  润新知