【Java】log4j2 使用

现在的应用已经越来越离不开日志的支持,此前log4j已经提供了良好的日志实现,但是随着时间的推移,log4j已经在2015年停止了维护,取而代之的是log4j2。下面介绍下log4j2。

 

优点

这里只列举一些:

1.支持异步logger,效率可以提升10倍;

2.gc压力几乎为0;

3.插件式实现,易于扩展;

4.支持动态修改配置,不丢事件;

 

配置

位置:默认找classpath下log4j2.xml、log4j2.yaml、log4j2.yml或者log4j2.properties这些文件。如果没有找到认为没有配置文件。默认会只输出error日志到console。

ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console)
18:36:14.849 [main] ERROR com.liyao.Test - hello

上面这个默认的配置相当于以下显式的配置:

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

下面是一个比较完整的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" name="XMLConfigTest" packages="org.apache.logging.log4j.test">
  <Properties>
    <Property name="filename">target/test.log</Property>
  </Properties>
  <ThresholdFilter level="trace"/>
 
  <Appenders>
    <Console name="STDOUT">
      <PatternLayout pattern="%m MDC%X%n"/>
    </Console>
    <Console name="FLOW">
      <!-- this pattern outputs class name and line number -->
      <PatternLayout pattern="%C{1}.%M %m %ex%n"/>
      <filters>
        <MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
        <MarkerFilter marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
      </filters>
    </Console>
    <File name="File" fileName="${filename}">
      <PatternLayout>
        <pattern>%d %p %C{1.} [%t] %m%n</pattern>
      </PatternLayout>
    </File>
  </Appenders>
 
  <Loggers>
    <Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
      <ThreadContextMapFilter>
        <KeyValuePair key="test" value="123"/>
      </ThreadContextMapFilter>
      <AppenderRef ref="STDOUT"/>
    </Logger>
 
    <Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
      <Property name="user">${sys:user.name}</Property>
      <AppenderRef ref="File">
        <ThreadContextMapFilter>
          <KeyValuePair key="test" value="123"/>
        </ThreadContextMapFilter>
      </AppenderRef>
      <AppenderRef ref="STDOUT" level="error"/>
    </Logger>
 
    <Root level="trace">
      <AppenderRef ref="STDOUT"/>
    </Root>
  </Loggers>
 
</Configuration>

configuration配置

常用属性:

monitorInterval:单位是s,log4j2支持动态检测配置文件的变化,这个参数用于标识检测的周期;

name:配置文件的名字;

packages:用于指定搜索插件的包名路径;

status:是log4j2内部的日志的等级,比如trace,info等等;

 

logger配置

每一份配置都会包含多个logger,每一个logger又对应一个loggerConfig。logger和loggerconfig有命名层级体系。

Named Hierarchy

A LoggerConfig is said to be an ancestor of another LoggerConfig if its name followed by a dot is a prefix of the descendant logger name. A LoggerConfig is said to be a parent of a child LoggerConfig if there are no ancestors between itself and the descendant LoggerConfig.

logger(或者loggerconfig)命名是用点好分割的字符串,这个与包名一致。点号之前的部分是后面部分的祖先。比如com就是com.foo的祖先而com.foo就是com的孩子。logger和loggerconfig都遵循这个规则。

所有的logger都有一个根祖先,也就是root。

程序获取logger时,需要传入logger的名字,log4j2在做匹配时,实际是做一个最长匹配,意味着如果无法精确匹配,就最长匹配。另外,如果匹配到名字A,那么所有的A的祖先也会被匹配到(包括root)。当然这个传递性可以通过配置additivity属性来取消。

logger的命名可以不遵循包名,但是点号分割的继承关系是内在实现的。这个恰好和包名完美契合,所以用包名命名logger是一个很好的选择。

获取root:

Logger logger = LogManager.getRootLogger();

理解命名层级体系对logger检索很重要。

下面是几个例子,LoggerName是程序传入的名字,Assigned是目前已经配置的logger。

Lookup

很重要的功能,lookup为我们提供了在配置文件的宏替换的能力。这种替换可以是运行时替换也可以是非运行时替换。

非运行时替换发生在加载配置文件时,而运行时替换发生在程序执行时。宏定义一般是${var}形式。log4j2加载配置文件时,会使用apache common的一个类库做替换,规则就是匹配${}符号,如果是双$$符号,那么第一个$被认定为转义符,表示第二个$不替换,直接输出。所以$${var}不会被Apache common库替换,而是直接输出${var},这个变量会在实现运行时被替换。明白了替换规则和时机,如果我们想非运行时替换,只要一个$,如果是运行时替换最好两个$(防止在加载配置文件时就替换)。

下面看下可以替换的类型。

首先是最典型的加载配置文件替换,这种一般就是在配置文件里定义一个property,然后后面的配置直接使用这个property。

比如:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <properties>
        <property name="prefix">haha</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${prefix}%d{HH:mm:ss.SSS}[%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>
log.info("hello1");

将会输出:haha12:11:57.086[main] INFO  com.liyao.App - hello1

 

context map替换,这是一种运行时替换。我们可在程序内使用log4j2提供的contextmap存储键值对,然后在配置文件中使用,配置文件中使用需要加上ctx前缀。比如:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="$${ctx:name} %d{HH:mm:ss.SSS}[%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>
ThreadContext.put("name", "liyao6");
log.info("hello1");

最后输出:liyao6 12:25:47.047[main] INFO  com.liyao.App - hello1

注意这里使用了双$符号。

 

java系统变量,也是一种运行时替换,需要使用java前缀。

例子:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="$${java:version} %d{HH:mm:ss.SSS}[%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>
log.info("hello1");

最后输出:Java version 1.8.0_101 12:30:16.632[main] INFO  com.liyao.App - hello1

 

date,也是运行时替换,区别在于仍然需要提供前缀date,但是不需要使用任何key,而是需要提供一个simpledateformat版本来格式化日期,比如:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="$${date:yyyy-MM-dd HH:mm:ss.SSS}[%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

log.info("hello1");

最后将输出:2018-12-22 12:34:31.051[main] INFO  com.liyao.App - hello1

这与之前%d格式指定日期效果相同,%d是另一种方式,会在之后的layout处介绍。用在layout格式处,二者等效。但是date lookup还可以用在其他地方,比如定义property时指定文件名等。

 

system属性,也是运行时绑定,需要sys前缀。比如:

        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="$${sys:sys} %d{HH:mm:ss.SSS}[%t] %-5level %logger{36} - %msg%n"/>
        </Console>
System.setProperty("sys", "p");
log.info("hello1");

最后将输出:p 12:41:48.053[main] INFO  com.liyao.App - hello1

 

layout配置

这里重点记录下patternlayout的用法。patternlayout负责将一个日志事件格式化输出出来。一句日志可以由多个格式化符组成,每一个格式化符都可以输出一定的信息。

格式化符由3部分组成:

%;

可选的格式参数;

格式化标记;

其中%是必须的开始符号。

可选的格式参数表示输出的格式,其格式为:

“符号 数字 . 符号 数字”

点号左侧表示最少输出多少字符,如果不够用空格补充。如果没有符号或者是正号,表示默认右对齐。如果是负号,表示左对齐,一般会选择左对齐吧。

点号右侧表示最长输出多少字符,如果多了就截断。如果没有符号或者是正号,表示从开始截断,如果是负号,表示从末尾截断。

下面是一个打印类名的例子:

最后是格式化标记,表明要输出什么。比如日志内容就用m,输出日志的类名就用c等等。常用的有:

C或者class:类名

c或者logger:logger名字

L或者line:行号

m后者msg或者meesage:日志内容

M或者method:方法名

p或者level:日志级别

t或者thread:线程名

n:换行

d或者date:日期,日期的格式可以被指定,在d后面的{}内部指定。如果不指定,相当于%d{DEFAULT},其格式后面会贴。其实默认的格式已经可以满足很多场景了。

常见的格式如下:

最终在使用layout时,需要在appender标签下,加一个PetternLayout标签,其中的属性pattern用于指定日志格式化格式。

 

Appender配置

Appender表示日志目的地。log4j2支持的appender种类很多,这里只记录console和rollingfile两种。

console:顾名思义,打印至控制台。几个常用的属性:

name:appender的name;

target:只能填写SYSTEN_OUT或者SYSTEM_ERR。

 

rollingfileappender:

常用属性:

append:是否追加,默认为true。

name:appender的name;

fileName:日志文件的文件名,其实是全路径,如果没有,将会自动创建;

filePattern:这个属性非常非常重,字面上表示,当发生rolling时,该如何命名旧的日志文件名;但是更深层次地,它暗示了是否能进行基于date或者递增数字的rolling,和下面的TriggeringPolicy以及RolloverStrategy必须一一对应。如果想要用基于date的rolling模式,那么必须在pattern中指定date的模式,比如$${date:yyyy-MM-dd HH:mm:ss}.log这种(或者%d格式);如果想要用基于递增数字的rollling模式,那么必须在pattern中指定%i。如果在pattern中没有指定date模式,但是却在后面配置了基于date的triggering,那么会报错;反之,如果没有指定基于数字的模式,但在后面配置了基于文件大小的triggering,也不行。例子:

        <RollingFile name="RollingFile" fileName="~/logs/app.log"
                     filePattern="~/logs/app-%i.log">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="1KB"/>
            </Policies>
        </RollingFile>

这里pattern里只有%i,最后配置了timebasedtrigger。报错:

2018-12-22 20:04:54,088 main ERROR Could not create plugin of type class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile: java.lang.IllegalStateException: Pattern does not contain a date java.lang.IllegalStateException: Pattern does not contain a date

所以,pattern一定要和后面的policy保持一致!!!!!

 

下面看具体配置:

TriggeringPolicy:配置触发一次rolling的时机,与pattern要一致;

RolloverStrategy:表明发生rolling时,如何重命名之前的文件;

下面看一下triggerpolicy:

Cron Triggering Policy:基于一个linux的cron表达式。其中使用属性schedule标签指定表达式,比如:

<CronTriggeringPolicy schedule="0 0 * * * ?"/>

TimeBased Triggering Policy:基于时间,即超过多少时间触发一次rolling,用interval指定时间,单位是小时,默认是1。

<TimeBasedTriggeringPolicy interval="6"/>

以上两个是基于date的rolling时机,必须在pattern中指定date模式;

SizeBased Triggering Policy:基于文件大小,使用后缀指明大小,比如MB或者KB。例子:

SizeBasedTriggeringPolicy size="250 MB"/>

Composite Triggering Policy:组合多种policy,定义时只需要提供一个<Policies>标签包含其他的policy即可。比如:

<Policies>
  <OnStartupTriggeringPolicy />
  <SizeBasedTriggeringPolicy size="20 MB" />
  <TimeBasedTriggeringPolicy />
</Policies>

rollingpolicy:

rollingpolicy一般可以不指定,因为log4j2会为我们默认提供一个default的rollingpolicy。这个default支持接收一个date模式的参数或者一个自增的数值模式来rolling。具体如下:

默认的rollover strategy接受一个日期/时间模式和一个整数,其中这个整数,是RollingFileAppender本身指定的filePattern属性。如果date/time模式存在的话,它将会替换当前日期和时间的值。如果这个模式包含整数的话,它将会在每次发生rollover时,进行递增。如果模式同时包含date/time和整数,那么在模式中,整数会递增直到结果中的data/time模式发生改变。如果文件模式是以".gz", ".zip", ".bz2", ".deflate", ".pack200", or ".xz"结尾的,将会与后缀相匹配的压缩方案进行压缩文件。

通常这个默认的rolling(不配置)已经满足了大多数常规需求。

当然如果我们想配置,default的rollling支持下面的参数:

fileIndex:如果是“max”,表示高的比低的更新;反之,“min”表示低的比高的更新;

min:计数器的最小值,默认为1;

max:计数器的最大值,当达到最大值以后,旧的将会被删除;

另外还有一个DirectWrite Rollover Strategy,这里就不介绍了。

一些例子:

①下面是使用RollingFileAppender的一个示例配置,并且是基于时间和大小的触发策略。其将会根据当前年和月份在未来7天内创建7个压缩包,并且使用gzip进行压缩。

<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>
    </RollingFile>

②第二个例子显示一个rollover策略,最多保留20个文件:

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

③下面是使用RollingFileAppender的一个示例配置,并且是基于时间和大小的触发策略。其将会根据当前年和月份在未来7天内创建7个压缩包,并且每6小时即被6整除时,会使用gzip进行压缩每个文件。

<Appenders>
    <RollingFile name="RollingFile" fileName="logs/app.log"
                 filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <Policies>
        <!--这行区别-->
        <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
        <SizeBasedTriggeringPolicy size="250 MB"/>
      </Policies>
    </RollingFile>
  </Appenders>

 

以上介绍了log4j2绝大多数常用的使用方式。

最后补充一下依赖:

要使用log4j2,需要一个api包和一个core包

如果时直接使用log4j2,maven只要导入一个core的jar包即可,比如:

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.11.1</version>
    </dependency>

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ log4j_test ---
[INFO] com.liyao:log4j_test:jar:1.0-SNAPSHOT
[INFO] +- org.apache.logging.log4j:log4j-core:jar:2.11.1:compile
[INFO] |  \- org.apache.logging.log4j:log4j-api:jar:2.11.1:compile

如上,两个jar包都被导入了。

 

那如果想结合slf4j,除了需要以上两个log4j2的jar以外,还需要导入一个log4j-slf4j-impl的桥接依赖和slf4j的api的jar包。

以上4个jar可以通过一个maven依赖导入:

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.11.1</version>
    </dependency>

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ log4j_test ---
[INFO] com.liyao:log4j_test:jar:1.0-SNAPSHOT
[INFO] +- org.apache.logging.log4j:log4j-slf4j-impl:jar:2.11.1:compile
[INFO] |  +- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] |  +- org.apache.logging.log4j:log4j-api:jar:2.11.1:compile
[INFO] |  \- org.apache.logging.log4j:log4j-core:jar:2.11.1:runtime

这是log4j2与log4j在结合slf4j的不同。log4j结合slf4j需要导入slf4j-log4j-xxx的依赖。

 

以上关于log4j2的介绍就结束了,整理了一周才写完,学无止境!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值