[TOC]
日志框架
- 日志接口(slf4j)
slf4j是对所有日志框架制定的一种规范、标准、接口,并不是一个框架的具体的实现,因为接口并不能独立使用,需要和具体的日志框架实现配合使用(如log4j、logback) - 日志实现(log4j、logback、log4j2)
- log4j是apache实现的一个开源日志组件
- logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,是slf4j的原生实现
- log4j2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步、等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活。
为什么需要日志接口,直接使用具体的实现不就行了吗?
接口用于定制规范,可以有多个实现,使用时是面向接口的(导入的包都是slf4j的包而不是具体某个日志框架中的包),即直接和接口交互,不直接使用实现,所以可以任意的更换实现而不用更改代码中的日志相关代码。
比如:slf4j定义了一套日志接口,项目中使用的日志框架是logback,开发中调用的所有接口都是slf4j的,不直接使用logback,调用是 自己的工程调用slf4j的接口,slf4j的接口去调用logback的实现,可以看到整个过程应用程序并没有直接使用logback,当项目需要更换更加优秀的日志框架时(如log4j2)只需要引入Log4j2的jar和Log4j2对应的配置文件即可,完全不用更改Java代码中的日志相关的代码logger.info(“xxx”),也不用修改日志相关的类的导入的包(import org.slf4j.Logger; import org.slf4j.LoggerFactory;)
使用日志接口便于更换为其他日志框架
log4j、logback、log4j2都是一种日志具体实现框架,所以既可以单独使用也可以结合slf4j一起搭配使用。
log4j2日志级别
从大到小依次是: off, fatal, error, warn, info, debug, trace, all
由于我们使用的是slf4j
接口包,该接口包中只提供了未标有删除线的日志级别的输出。
log4j2配置文件的优先级
- Log4j will inspect the
log4j.configurationFile
system property and, if set, will attempt to load the configuration using theConfigurationFactory
that matches the file extension. - If no system property is set the properties
ConfigurationFactory
will look forlog4j2-test.properties
in the classpath. - If no such file is found the YAML ConfigurationFactory will look for
log4j2-test.yaml
orlog4j2-test.yml
in the classpath. - If no such file is found the JSON ConfigurationFactory will look for
log4j2-test.json
orlog4j2-test.jsn
in the classpath. - If no such file is found the XML ConfigurationFactory will look for
log4j2-test.xml
in the classpath. - If a test file cannot be located the properties ConfigurationFactory will look for
log4j2.properties
on the classpath. - If a properties file cannot be located the YAML ConfigurationFactory will look for
log4j2.yaml
orlog4j2.yml
on the classpath. - If a YAML file cannot be located the JSON ConfigurationFactory will look for
log4j2.json
orlog4j2.jsn
on the classpath. - If a JSON file cannot be located the XML ConfigurationFactory will try to locate
log4j2.xml
on the classpath. - If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.
对于log4j2配置文件的理解
配置文件结构:
Appdenders
部分Appender
Filter
Layout
Policies
Strategy
Loggers
部分Logger
RootLogger
对于Appender
的理解
简单说Appender就是一个管道,定义了日志内容的去向(保存位置)。
配置一个或者多个Filter
,Filter
的过滤机制和Servlet
的Filter
有些差别,下文会进行说明。
配置Layout
来控制日志信息的输出格式。
配置Policies
以控制日志何时(When)进行滚动。
配置Strategy
以控制日志如何(How)进行滚动。
对于Logger
的理解
简单说Logger就是一个路由器,指定类、包中的日志信息流向哪个管道,以及控制他们的流量(日志级别)
log4j2配置文件框架
配置文件格式
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Appender>
<Filters>
<LevelRangeFilter minLevel="..." maxLevel="..." onMatch="..." onMismatch="..."/>
</Filters>
<PatternLayout pattern="..." charset="..."/>
<Policies>
<CronTriggeringPolicy schedule="..."/>
<SizeBasedTriggeringPolicy size="..."/>
<TimeBasedTriggeringPolicy />
</Policies>
</Appender>
<Appender>
// ...
</Appender>
</Appenders>
<Loggers>
<Logger>
<AppenderRef ref="...">
</Logger>
<Root>
<AppenderRef ref="...">
</Root>
</Loggers>
</Configuration>
Appender标签的实现类
其实这些标签都是类名或者类名去掉后缀。
Appender的常用的实现类有:
- ConsoleAppender(Console)
- FileAppender(File)、RandomAccessFileAppender(RandomAccessFile)
- RollingFileAppender(RollingFile)、RollingRandomAccessFileAppender(RollingRandomAccessFile)
打开这些实现类的源码,你一定会恍然大明白,括号中的是实现类在log4j2.xml
配置文件中的标签名。
ConsoleAppender(Console)
该实现类会把日志输出到控制台中。
它有两种输出方式:
- SYSTEM_OUT(System.out)
- SYSTEM_ERR(System.err)
如果不配置,默认使用SYSTEM_OUT
进行输出。括号中是调用的方法。
简单示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<!-- 格式化日志 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
</Console>
</Appenders>
<Loggers>
<!-- level默认为error -->
<Root level="info">
<!-- 这里引用了Appenders标签中的name值 -->
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
其它属性可以参见官方文档: http://logging.apache.org/log4j/2.x/manual/appenders.html#ConsoleAppender
FileAppender(File)、RandomAccessFileAppender(RandomAccessFile)
相同点:写入日志信息到文件
不同点:使用的I/O实现类不同,前者使用FileOutputStream
,后者使用RandomAccessFile
。
官方文档说是在bufferedIO=true(默认是true)
的情况下后者比前者性能提升20% ~ 200%
,不明觉厉,就用后者吧。
简单示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<RandomAccessFile name="File" fileName="logs/app.log" immediateFlush="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
</RandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
常用属性:
fileName
:来指定文件位置,文件或目录不存在则会自动创建。immediateFlush
:是否每次写入都要立刻刷新到硬盘中。默认true
,如果使用默认值可能会影响性能。
其它属性可以参见官方文档: http://logging.apache.org/log4j/2.x/manual/appenders.html#RandomAccessFileAppender
RollingFileAppender(RollingFile)、RollingRandomAccessFileAppender(RollingRandomAccessFile)
这一对之间的区别与上一对之间的区别是一样的。
上一对的实现类不能进行日志滚动,所谓日志滚动就是当达到设定的条件后,日志文件进行切分。
比如:工程师想让系统中的日志按日进行切分,并且按月归档。
这时候这一对的作用就体现出来了。
简单示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<RollingRandomAccessFile name="File" fileName="logs/app.log" filePattern="logs/$${date:hh-mm}/%d{hh-mm-ss}.app.%i.log" >
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
<Policies>
<!-- 每 5s 翻滚一次 -->
<CronTriggeringPolicy schedule="0/5 * * * * ?" />
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="10" />
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
filePattern
:指定了日志滚动之后的文件命名格式,至于其中的$${date:hh-mm}
表达式下文介绍。DefaultRolloverStrategy
指定了如何(How)进行翻滚,并且指定了最大翻滚次数(影响%i
参数值),超过次数之后会按照相应的规则删除旧日志。Policies
: 这里就是规定了何时进行滚动(When),可以有多个Policy
。CronTriggeringPolicy
设置了每 5s 进行一次翻滚SizeBasedTriggeringPolicy
设置了的话,如果当前文件超过了10MB
,但是文件的名字还没有进行翻滚(建立新文件),那么就会用%i
的方式进行翻滚。
翻滚示例:
app.log
第一次翻滚:app.log app.1.log // app.log -> app.1.log
第二次翻滚:app.log app.1.log app.2.lop // app.log -> app.2.log
第三次翻滚:app.log app.1.log app.2.lop app.3.lop // app.log -> app.3.log
第四次翻滚:app.log app.1.log app.2.lop app.3.lop app.4.lop // app.log -> app.4.log
一直到设定的翻滚次数10之后,会把旧的日志内容覆盖。
app.2.lop -> app.1.lop
app.3.lop -> app.2.lop
…
app.10.lop -> app.9.lop
app.log -> app.10.lop
一直这样循环下去,直到创建新文件。
Filters
Filters决定日志事件能否被输出。过滤条件有三个值:ACCEPT(接受)
,DENY(拒绝)
,NEUTRAL(中立)
。
常用的Filter实现类有:
- LevelRangeFilter
- TimeFilter
- ThresholdFilter
上文中提到log4j2
中的Filter
与Servlet
中的有差别。那么有什么差别呢?
简单说就是log4j2
中的过滤器ACCEPT
和DENY
之后,后续的过滤器就不会执行了,只有在NEUTRAL
的时候才会执行后续的过滤器。
简单示例
测试代码:
public class LogMain {
private static Logger logger = LoggerFactory.getLogger(LogMain.class);
public static void main(String[] args) throws Exception {
logger.trace("trace Msg.");
logger.debug("debug Msg.");
logger.info("info Msg.");
logger.warn("warn Msg.");
logger.error("error Msg.");
}
}
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<Console name="Console">
<!-- 设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器 最后一个过滤器建议设置 onMismatch="DENY", 不然日志就输出了。 -->
<Filters>
<!-- 从大到小:error, warn, info, debug, trace -->
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="NEUTRAL" />
<!-- 只允许在每天的 8点~8点半 之间输出日志 -->
<TimeFilter start="08:00:00" end="08:30:00" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
输出结果:
17:51:53.546 [main] INFO me.master.snail.log.LogMain - info Msg.
17:51:53.548 [main] WARN me.master.snail.log.LogMain - warn Msg.
17:51:53.548 [main] ERROR me.master.snail.log.LogMain - error Msg.
如果当前时间不是 8点~8点半 之间,那么没有日志会输出。
这里的info Msg.
、warn Msg.
和error Msg.
为什么会输出呢?
是因为LevelRangeFilter
对它们进行了ACCEPT
,而剩下的trace Msg.
和debug Msg.
则会经过下一个过滤器,然后依次类推。
PatternLayout
这是常用的日志格式化类,其它日志格式化类很少用。
关于其它日志类,可以打开PatternLayout
类,找到其父类AbstractStringLayout
, 看父类的实现类有哪些。
简单示例:
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
授人以鱼不如授人以渔。关于pattern
的格式点击http://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout
具体的其它属性可以看源码也可以参考官方文档。
Policy & Strategy
上文也说了,Policy
是用来控制日志文件何时(When)进行滚动的;Strategy
是用来控制日志文件如何(How)进行滚动的。
如果配置的是RollingFile
或RollingRandomAccessFile
,则必须配置一个Policy
。
如果想按月归档,按日切分日志,然后
Policy
常用的实现类:
- SizeBasedTriggeringPolicy
- CronTriggeringPolicy
- TimeBasedTriggeringPolicy
SizeBasedTriggeringPolicy
根据日志文件的大小进行滚动。
<SizeBasedTriggeringPolicy size="10MB"/>
单位有:KB,MB,GB
CronTriggeringPolicy
使用Cron
表达式进行日志滚动,很灵活。
<CronTriggeringPolicy schedule="0/5 * * * * ?" />
TimeBasedTriggeringPolicy
这个滚动策略依赖于filePattern
中配置的最具体的时间单位,根据最具体的时间单位进行滚动。
这种方式比较简洁。CronTriggeringPolicy
策略更强大。
简单示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<RollingRandomAccessFile name="File" fileName="logs/app.log" filePattern="logs/$${date:hh-mm}/%d{hh-mm-ss}.app.%i.log" >
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
<Policies>
<!-- 每 5s 翻滚一次 -->
<!--<CronTriggeringPolicy schedule="0/5 * * * * ?" />-->
<!-- filePattern中最具体的时间单位是 秒。 这里用 TimeBasedTriggeringPolicy 替换 CronTriggeringPolicy 注意:modulate属性是指从启动时间开始算5秒,还是从0秒开始算5秒,运行一下就明白了。 modulate: true(默认值) // 会从启动时间开始算 5秒 modulate: false // 从 0秒开始算 -->
<TimeBasedTriggeringPolicy interval="5" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="10" />
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
Strategy
常用的实现类
- DefaultRolloverStrategy
- DirectWriteRolloverStrategy
这两个Strategy
都是控制如何进行日志滚动的,至于他们的区别我还是不太明白,大佬解释一下吧。
平时大部分用DefaultRolloverStrategy
就可以了。
Logger
Logger
部分就比较简单了,分为两个Logger
:
- Root(必须配置)
- Logger
简单示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<Console name="Console">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
<Filters>
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
</Root>
</Loggers>
</Configuration>
注意:Logger
中也可以加过滤器的哟~
比较重要的问题: 日志重复打印
如果Root
中的日志包含了Logger
中的日志信息,并且AppenderRef
是一样的配置,则日志会打印两次。
注意:有两个条件
Root
中的日志包含了Logger
中的日志信息- 且
AppenderRef
是一样的配置
这时候我们需要使用一个Logger
的属性来解决,那就是additivity
,其默认值为true
,需要配置为false
。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<Console name="Console">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Logger name="me.master.snail.log.LogMain" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="trace">
<AppenderRef ref="Console"/>
<Filters>
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
</Root>
</Loggers>
</Configuration>
Lookups
这个组件类似于JSTL的EL表达式,或者类似于Spring的SpEL表达式。
具体的语法很简单,这里就不粘贴复制了。
点击http://logging.apache.org/log4j/2.x/manual/lookups.html查看官方文档。
相信你用半个小时就学会了。
示例
为了大家快速开发(方便懒惰的同学),写一些示例。
输出到控制台
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<!-- 格式化日志 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
</Console>
</Appenders>
<Loggers>
<!-- level默认为error -->
<Root level="info">
<!-- 这里引用了Appenders标签中的name值 -->
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
输出到单个文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<RandomAccessFile name="File" fileName="logs/app.log" immediateFlush="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
</RandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
按月归档日志,按日进行切分,限制单文件大小为 500MB, 一天最多生成20个文件,也就是(20 * 500)MB大小的日志
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<RollingRandomAccessFile name="File" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/%d{yyyy-MM-dd}.app.%i.log" >
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="false"/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<DefaultRolloverStrategy max="20" />
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
限制Spring框架日志的输出级别
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<RollingRandomAccessFile name="File" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/%d{yyyy-MM-dd}.app.%i.log" >
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" charset="UTF-8"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="false"/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<DefaultRolloverStrategy max="20" />
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- 限制Spring框架日志的输出级别,其它框架类似配置 或者使用 AppenderRef 标签,将其输出到指定文件中,记得加上 additivity="false" -->
<logger name="org.springframework" level="INFO"/>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
通过此篇深刻体会到了写博客的好处了,我从对log4j2一知半解,写完之后已经可以达到熟练使用的地步了,建议大家坚持写博客。
如有描述错误,麻烦指出来,别误导了初学者。
最好的学习方法就是敲代码验证。