配置
Log4j 2的配置可以通过4种方式中的1种完成:
1、通过使用XML,JSON,YAML或属性格式编写的配置文件。
2、以编程方式,通过创建一个ConfigurationFactory和配置实现。
3、以编程方式,通过调用配置界面中公开的API将组件添加到默认配置。
4、通过编程方式,通过调用内部Logger类的方法。
我主要是讲解配置文件的方式
编程的方式可以参考: Extending Log4j 2和Programmatic Log4j Configuration.
自动配置
Log4j能够在初始化期间自动配置自身。
当Log4j启动时,将找到所有ConfigurationFactory插件,并按照从最高到最低的加权顺序进行排列。
交付时,Log4j包含四个ConfigurationFactory实现:一个用于JSON,一个用于YAML,一个用于properties,一个用于XML。
1、Log4j将检查log4j.configurationFile
系统属性,如果设置,将尝试使用与文件扩展名匹配的ConfigurationFactory加载配置。
2、如果没有设置系统属性,则properties ConfigurationFactory
将在类路径中查找log4j2-test.properties
。
3、如果没有找到这样的文件,YAML ConfigurationFactory
将在类路径中查找log4j2-test.yaml
或log4j2-test.yml
。
4、如果没有找到这样的文件,JSON ConfigurationFactory
将在类路径中查找log4j2-test.json
或log4j2-test.jsn
。
5、如果没有找到这样的文件,XML ConfigurationFactory
将在类路径中查找log4j2-test.xml
。
6、如果找不到测试文件,则properties ConfigurationFactory
将在类路径中查找log4j2.properties
。
7、如果无法找到属性文件,则YAML ConfigurationFactory
将在类路径上查找log4j2.yaml
或log4j2.yml
。
8、如果无法找到YAML
文件,则JSON ConfigurationFactory
将在类路径上查找log4j2.json
或log4j2.jsn
。
9、如果无法找到JSON
文件,则XML ConfigurationFactory
将尝试在类路径上找到log4j2.xml
。
10、如果没有找到配置文件,将使用DefaultConfiguration
。这将导致日志输出转到控制台。
日志级别
log4j规定了默认的几个级别:trace<debug<info<warn<error<fatal
等。这里要说明一下:
①级别之间是包含的关系,意思是如果你设置日志级别是trace,则大于等于这个级别的日志都会输出。
②基本上默认的级别没多大区别,就是一个默认的设定。你可以通过它的API自己定义级别。你也可以随意调用这些方法,不过你要在配置文件里面好好处理了,否则就起不到日志的作用了,而且也不易读,相当于一个规范,你要完全定义一套也可以,不用没多大必要。
③这不同的级别的含义大家都很容易理解,这里就简单介绍一下:
level | 描述 |
---|---|
trace | 是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出。 |
debug | 调试么,我一般就只用这个作为最低级别,trace压根不用。是在没办法就用eclipse或者idea的debug功能就好了么。 |
info | 输出一下你感兴趣的或者重要的信息,这个用的最多了。 |
warn | 有些信息不是错误信息,但是也要给程序员的一些提示,类似于eclipse中代码的验证不是有error 和warn(不算错误但是也请注意,比如以下depressed的方法)。 |
error | 错误信息。用的也比较多。 |
fatal | 级别比较高了。重大错误,这种级别你可以直接停止程序了,是不应该出现的错误么!不用那么紧张,其实就是一个程度的问题。 |
日志使用
紧接上篇博文
例子1:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</appenders>
<loggers>
<!--我们只让这个logger输出trace信息,其他的都是error级别-->
<!--
additivity开启的话,由于这个logger也是满足root的,所以会被打印两遍。
-->
<logger name="cn.lsw.base.log4j2.Hello" level="trace" additivity="false">
<appender-ref ref="Console"/>
</logger>
<root level="error">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
先简单介绍一下下面这个配置文件。
1)根节点configuration,然后有两个子节点:appenders和loggers(都是复数,意思就是可以定义很多个appender和logger了)(如果想详细的看一下这个xml的结构,可以去jar包下面去找xsd文件和dtd文件)
2)appenders:这个下面定义的是各个appender,就是输出了,有好多类别,这里也不多说(容易造成理解和解释上的压力,一开始也未必能听懂,等于白讲),先看这个例子,只有一个Console,这些节点可不是随便命名的,Console就是输出控制台的意思。然后就针对这个输出设置一些属性,这里设置了PatternLayout就是输出格式了,基本上是前面时间,线程,级别,logger名称,log信息等,差不多,可以自己去查他们的语法规则。
3)loggers下面会定义许多个logger,这些logger通过name进行区分,来对不同的logger配置不同的输出,方法是通过引用上面定义的logger,注意,appender-ref引用的值是上面每个appender的name,而不是节点名称。
这个例子为了说明什么呢?我们要说说这个logger的name(名称)了(前面有提到)。
name机制
可以参考: http://logging.apache.org/log4j/2.x/manual/architecture.html
我们看到配置文件中的那个name
是非常重要的。这个name
要用好的,就不能随便乱起。
(随便用的话,那就随便取名字)。这个机制很简单,就类似于java package
一样。
上篇中创建logger
对象的时候,名称是通过Hello.class
或者Hello.class.getName()
这样的方法。为什么要这样做呢?很重要的原因就是有所谓的继承问题。比如 如果你给com.Hello
定义了一个logger
,那么它也适用于com.Hello.base
这个logger
。名称的继承是通过(.)点号分隔的。然后你会返现上面的loggers
里面有个子节点不是logger
而是root
,而且这个root
没有name
属性。
这个root
相当于根节点。你所有的logger
都适用于这个logger
,所以,即使你在很多类里面通过类名.class.getName()
或者类名.class
得到很多的logger
,而且你也没有在配置文件中进行任何配置,它们也能够都输出,因为他们都继承了root
的log
配置。
这种继承的说法官网的解释叫做logger 层次结构(Hierarchy)
官网的例子:
例如:Logger配置中name为com.foo是name为com.foo.Bar的父类。类似的有,java是java.util的父类,是java.util.Vector的祖先。
上面的那个配置文件里面还定义了一个logger
,他的名称是cn.lsw.base.log4j2.Hello
,这个名称其实就是通过前面的Hello.class.getName()
或者Hello.class
得到的。上面那个配置文件,我们为了给它做单独配置。意思是:只有cn.lsw.base.log4j2.Hello
这个logger
输出trace
信息。也就是它的日志级别为trace
,其他的logger
则继承root
的日志配置,日志级别为error
,只能打印出error
及以上级别的日志。
那么有人会问,你单独配置的那个logger
不也是继承了root
的配置么,那这样的话,岂不是会打印两遍日志? 这个问题确实是存在的。当然如果你设置了additivity=false
,就不会输出两遍。
我们再写一个测试类:
(我们先对上面的配置文件做下修改,一个是logger的name改为:test.Hello,第二把additivity=false
去掉或改为true)
package test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Logger2Test {
private static Logger logger = LogManager.getLogger(Logger2Test.class.getName());
public static void main(String[] args){
logger.trace("start programe");
Hello hello = new Hello();
hello.getHello();
logger.trace("end programe");
}
}
结果就是:
2017-05-17 11:51:40.783 [main] TRACE test.Hello - Enter
2017-05-17 11:51:40.783 [main] TRACE test.Hello - Enter
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 我是trace
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 我是trace
2017-05-17 11:51:40.787 [main] INFO test.Hello - 我是info信息
2017-05-17 11:51:40.787 [main] INFO test.Hello - 我是info信息
2017-05-17 11:51:40.787 [main] ERROR test.Hello - 我是error
2017-05-17 11:51:40.787 [main] ERROR test.Hello - 我是error
2017-05-17 11:51:40.787 [main] FATAL test.Hello - 我是fatal
2017-05-17 11:51:40.787 [main] FATAL test.Hello - 我是fatal
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 退出程序.
2017-05-17 11:51:40.787 [main] TRACE test.Hello - 退出程序.
2017-05-17 11:51:40.787 [main] TRACE test.Hello - Exit
2017-05-17 11:51:40.787 [main] TRACE test.Hello - Exit
我们可以看出主程序Logger2Test
并没有trace
日志输出,因为它继承了root
的日志配置(error级别及以上)。而Hello
输出了trace及以上级别的日志,但是呢,每个都输出了两遍。为什么会这样呢?前面也说过默认所有的logger
都继承root
的配置的。此时的Hello
既有自己单独的配置,也有从root
那里继承下来的配置,所以会打印两次。这样的特性,在其name
的层次结构中也是同样适用的,比如:
我创建三个logger
名称为test
、test.Hello
和test.Hello.Hello2
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</appenders>
<loggers>
<logger name="test.Hello" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<logger name="test.Hello" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<logger name="test.foo.Hello2" level="info" additivity="true">
<appender-ref ref="Console"/>
</logger>
<root level="error">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>
打印结果:
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info信息
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info信息
2017-05-17 14:18:09.388 [main] INFO test.Hello - 我是info信息
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] ERROR test.Hello - 我是error
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.392 [main] FATAL test.Hello - 我是fatal
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info信息
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info信息
2017-05-17 14:18:09.393 [main] INFO test.foo.Hello2 - 我是info信息
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] ERROR test.foo.Hello2 - 我是error
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
2017-05-17 14:18:09.393 [main] FATAL test.foo.Hello2 - 我是fatal
可以看出打印三篇。
在实际使用过程中,我们其实就是需要一个就行了,这时你可以设置:additivity=false
。
它会把父类全部屏蔽掉。官方说法就是把追加性关闭。
现在我们看一个稍微复杂的例子:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="error">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!--这个都知道是输出日志的格式-->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--建立一个默认的root的logger-->
<root level="trace">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>