一 概述
1.1 日志框架
- 日志接口(slf4j)
slf4j是对所有日志框架制定的一种规范、标准、接口,并不是一个框架的具体的实现,因为接口并不能独立使用,需要和具体的日志框架实现配合使用(如log4j、logback) - 日志实现(log4j、logback、log4j2)
- log4j是apache实现的一个开源日志组件
- logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架,是slf4j的原生实现
- log4j2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步、等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活。
关于 Log4j2 的介绍可以参考官网:https://logging.apache.org/log4j/2.x/
1.2 为什么需要日志接口,直接使用具体的实现不就行了吗?
接口用于定制规范,可以有多个实现,使用时是面向接口的(导入的包都是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一起搭配使用。
二 引入 Maven 依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.0</version>
</dependency>
三 log4j 2 日志级别
从大到小依次是: off, fatal, error, warn, info, debug, trace, all
由于我们使用的是slf4j接口包,该接口包中只提供了未标有删除线的日志级别的输出。
四 log4j 2 加载配置文件的顺序
- Log4j will inspect the
log4j.configurationFilesystem property and, if set, will attempt to load the configuration using theConfigurationFactorythat matches the file extension. - If no system property is set the properties
ConfigurationFactorywill look forlog4j2-test.propertiesin the classpath. - If no such file is found the YAML ConfigurationFactory will look for
log4j2-test.yamlorlog4j2-test.ymlin the classpath. - If no such file is found the JSON ConfigurationFactory will look for
log4j2-test.jsonorlog4j2-test.jsnin the classpath. - If no such file is found the XML ConfigurationFactory will look for
log4j2-test.xmlin the classpath. - If a test file cannot be located the properties ConfigurationFactory will look for
log4j2.propertieson the classpath. - If a properties file cannot be located the YAML ConfigurationFactory will look for
log4j2.yamlorlog4j2.ymlon the classpath. - If a YAML file cannot be located the JSON ConfigurationFactory will look for
log4j2.jsonorlog4j2.jsnon the classpath. - If a JSON file cannot be located the XML ConfigurationFactory will try to locate
log4j2.xmlon the classpath. - If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.
五 对于 log4j 2 配置文件的理解
配置文件结构:
Appdenders部分AppenderFilterLayoutPoliciesStrategy
Loggers部分Logger(ROOT)LeveladditivityAppenderRefFilter
5.1 对于 Logger 的理解
简单说 Logger 就是一个路由器,指定包下面的类或者指定某个类的日志信息流向哪个 Appender,以及控制他们的流量(日志级别)。
其中最重要的一个是 Root 这个标签,这个标签是必须定义的!如果没有指定 Logger,则默认会打印到 Root 标签下定义的 Appender 中。
5.2 对于 Appender 的理解
简单说 Appender 就是一个管道,定义了日志内容的去向(控制台、文件、网络等等)。
配置一个或者多个Filter,Filter的过滤机制和Servlet的Filter有些差别,下文会进行说明。
配置Layout来控制日志信息的输出格式。
配置Policies以控制日志何时(When)进行滚动。
配置Strategy以控制日志如何(How)进行滚动。
六 Appender
其实这些标签都是类名去掉 Appender 后缀的形式。
这里我选择几个基础的代表性的 Appender 进行讲解:
- ConsoleAppender(Console)
- FileAppender(File) 和 RandomAccessFileAppender(RandomAccessFile)
- RollingFileAppender(RollingFile) 和 RollingRandomAccessFileAppender(RollingRandomAccessFile)
6.1 ConsoleAppender
该实现类会把日志输出到控制台中。
它有两种输出方式:
- SYSTEM_OUT(System.out)
- SYSTEM_ERR(System.err)
如果不配置,默认使用SYSTEM_OUT进行输出(源码Target DEFAULT_TARGET = Target.SYSTEM_OUT;)。
示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="baseConf" status="warn" monitorInterval="30">
<Appenders>
<!-- target 是 "SYSTEM_OUT" or "SYSTEM_ERR". 默认是 "SYSTEM_OUT". -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<ROOT level="info">
<AppenderRef ref="Console"/>
</ROOT>
</Loggers>
</Configuration>
其它属性可以参见官方文档: http://logging.apache.org/log4j/2.x/manual/appenders.html#ConsoleAppender
提示
如果出现控制台日志乱码,通过查看 ConsoleAppender#Target 的源码你就能解决了。target 属性也可以这么配置:
<Console name="Console">
<Target>SYSTEM_ERR</Target>
</Console>
现在我们看一下 <Appenders> 这个标签,它的源码是 AppendersPlugin,它有一个 createAppenders 的方法,那么现在可以知道这些 Appender 是如何组织在一起的了,而且发现它返回了一个 Map 结构,并且使用 Appender 的 name 作为 key,所以 Appender 必须有 name 属性。
6.2 FileAppender 和 RandomAccessFileAppender
写入日志信息到文件。使用 FileOutputStream#getChannel 进行文件的写入。它是默认有 4M 的缓冲区的。
两个基本相似,除了 RandomAccessFileAppender 的 bufferedIO 属性是不能关闭的,但是官网上说后者比前者提高了 20-200% 的性能(在 bufferedIO=true 的情况下)。
简单示例:
<appenders>
<File name="File">
<fileName>log4j2/app.log</fileName>
<immediateFlush>false</immediateFlush>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
</File>
</appenders>
<loggers>
<root level="info">
<AppenderRef ref="File"/>
</root>
</loggers>
常用属性:
fileName:来指定文件位置,文件或目录不存在则会自动创建。immediateFlush:是否每次写入都要立刻刷新到硬盘中。默认true,如果使用默认值可能会影响性能。
其它属性可以参见官方文档:
- http://logging.apache.org/log4j/2.x/manual/appenders.html#FileAppender
- http://logging.apache.org/log4j/2.x/manual/appenders.html#RandomAccessFileAppender
6.3 RollingFileAppender 和 RollingRandomAccessFileAppender
与 6.2 的 Appender 一样,后者同样比前者提高了 20-200% 的性能。
它们的主要功能也是将日志写入到文件,但是增加了一个功能,就是日志文件可以进行切分。
RollingFileAppender 需要 TriggeringPolicy 和 RolloverStrategy。触发策略确定是否应执行过渡,而RolloverStrategy 定义应如何进行过渡。如果未配置 RolloverStrategy,则 RollingFileAppender 将使用 DirectWriteRolloverStrategy。DirectWriteRolloverStrategy 默认的最大文件数是 7。
演示每 2 秒切分一下日志文件,并且最大的文件数是 7。示例:
<appenders>
<RollingFile name="RollingFile">
<filename>log4j2/rolling_app.log</filename>
<filePattern>log4j2/rolling_app_%i.log</filePattern>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
<!-- 每 2 秒切分一次日志 -->
<CronTriggeringPolicy schedule="0/2 * * * * ?" />
</RollingFile>
</appenders>
<loggers>
<root level="info">
<AppenderRef ref="RollingFile"/>
</root>
</loggers>
filePattern:指定了日志滚动之后的文件命名规则。DirectWriteRolloverStrategy:指定了如何(How)进行翻滚,并且指定了最大翻滚次数(%i参数值),超过次数之后会按照相应的规则删除旧日志。Policy: 这里就是规定了何时进行滚动(When)。
日志切分覆盖原则
第一次翻滚: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
一直到设定的翻滚次数 7 之后,会把旧的日志内容覆盖。
app.2.lop -> app.1.lop
app.3.lop -> app.2.lop
...
app.7.lop -> app.6.lop
app.log -> app.7.lop
一直这样循环下去。
Policy是用来控制日志文件何时(When)进行滚动的;Strategy是用来控制日志文件如何(How)进行滚动的。
更详细的 Policy & Strategy 介绍在第九节和第十节介绍。
七 Filter
Filters决定日志事件能否被输出。过滤条件有三个值:ACCEPT(接受),DENY(拒绝),NEUTRAL(中立)。
常用的Filter实现类:
- BurstFilter:提供了一种机制来控制处理打印日志的速率,超过之后将无声的丢弃日志,一般不用。
- LevelRangeFilter:过滤日志的级别。
- TimeFilter: 提供基于时间段内的日志过滤。
- ThresholdFilter:对日志级别进行进一步的限制。
- ThreadContextMapFilter:验证 ThreadContentMap 中是否含有某个 key-value 值。
ThresholdFilter 示例:
<appenders>
<Console name="Console">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</Console>
</appenders>
<loggers>
<root level="info">
<AppenderRef ref="Console"/>
</root>
</loggers>
虽然 root 标签配置的日志级别是 info, 但是对应的 appender 中配置的 ThresholdFilter#level 是 warn,所以所有 info 级别的日志是打印不出来的。
ThreadContextMapFilter 示例:
<appenders>
<Console name="Console">
<ThreadContextMapFilter onMatch="ACCEPT" onMismatch="DENY" operator="or">
<KeyValuePair key="name" value="Mike" />
<KeyValuePair key="age" value="20" />
</ThreadContextMapFilter>
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</Console>
</appenders>
<loggers>
<root level="info">
<AppenderRef ref="Console"/>
</root>
</loggers>
operator="or":表示有一个匹配到即可通过过滤器,KeyValuePair 中的元素必须在 org.apache.logging.log4j.ThreadContext.put("name", "Mike") 中存在。否则会匹配 onMismatch。如果没有配置 operator,默认匹配所有的 KeyValuePair。
八 Layout
这个组件是将 log 日志格式化为各种想要的格式,进行输出的。在 AbstractStringLayout 中默认的编码是 UTF-8。
它有很多实现类,最常用的就是 PatternLayout,它还可以将日志格式化为 xml、json、yml、html等等,感兴趣的可以找一下 AbstractStringLayout 的实现类。
这里只说一下 PatternLayout。
简单示例:
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
授人以鱼不如授人以渔。关于 pattern 的格式化方式点击 http://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout 查看官方的详细介绍。
九 Policy
Policy是用来控制日志文件何时(When)进行滚动的。
如果配置的是RollingFile或RollingRandomAccessFile,则必须配置一个Policy。
Policy常用的实现类:
- SizeBasedTriggeringPolicy
- CronTriggeringPolicy
- TimeBasedTriggeringPolicy
- CompositeTriggeringPolicy
9.1 SizeBasedTriggeringPolicy
根据日志文件的大小进行滚动,当文件大小达到指定的大小时,就会进行文件的切分。
最多产生 7 个日志文件:
<RollingFile name="RollingFile">
<filename>log4j2/rolling_app.log</filename>
<filePattern>log4j2/rolling_app_%i.log</filePattern>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
<SizeBasedTriggeringPolicy size="1K"/>
</RollingFile>
日志文件满 1K 大小,就要进行切分。
size 的单位有:K,M,G(参见源码org.apache.logging.log4j.core.appender.rolling.FileSize#parse)。
为什么最多产生 7 个日志文件呢?因为这里有一个默认的 Stragety,那就是 DefaultRolloverStrategy,为什么是 1~7,与两个参数有关 MIN_WINDOW_SIZE 和 DEFAULT_WINDOW_SIZE。
<filePattern>log4j2/rolling_app_%i.log</filePattern> 也可以写为 <filePattern>log4j2/rolling_app_%i.log.tar.gz</filePattern> 来启动日志压缩。
注意
这里如果配置了 <filename> 属性,就是用默认的 DefaultRolloverStrategy,如果没有配置的话就使用 DirectWriteRolloverStrategy,关于 DirectWriteRolloverStrategy 的使用,我没细研究,一般情况下使用 DefaultRolloverStrategy 即可。
9.2 CronTriggeringPolicy
使用 Cron 表达式进行日志滚动,很灵活。
最多产生 7 个日志文件:
<RollingFile name="RollingFile">
<filename>log4j2/rolling_app.log</filename>
<filePattern>log4j2/rolling_app_%i.log</filePattern>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
<CronTriggeringPolicy schedule="0/5 * * * * ?" />
</RollingFile>
没有配置 Stragety,就使用默认的 DefaultRolloverStrategy。
9.3 TimeBasedTriggeringPolicy
这个滚动策略依赖于 filePattern 中配置的最具体的时间单位,根据最具体的时间单位进行滚动。
这种方式比较简洁, 但是 CronTriggeringPolicy 策略更强大,也更易读。
最多产生 7 个日志文件:
<RollingFile name="RollingFile">
<filename>log4j2/rolling_app.log</filename>
<filePattern>log4j2/rolling_app_%d{hh-mm-ss}.log</filePattern>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
<TimeBasedTriggeringPolicy interval="5"/>
</RollingFile>
没有配置 Stragety,就使用默认的 DefaultRolloverStrategy。
这个滚动策略是根据 filePattern 中配置的具体时间粒度来滚动的。比如 interval 是 5,而 filePattern 中配置的时间粒度是秒,所有是每 5 秒滚动一次。
9.4 CompositeTriggeringPolicy
它可以配置多个 Policy。
比如我想每 5 秒生成一个新文件,但同时每个文件的大小不超过 1KB,最大 5 秒内不能超过 20 个文件,就是 5秒内打印的日志不超过 20 K,就需要这样配置:
<RollingFile name="RollingFile">
<filename>log4j2/rolling_app.log</filename>
<filePattern>log4j2/rolling_app_%d{hh-mm-ss}_%i.log.tar.gz</filePattern>
<PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
<Policies>
<CronTriggeringPolicy schedule="0/5 * * * * ?" />
<SizeBasedTriggeringPolicy size="1K"/>
</Policies>
<DefaultRolloverStrategy max="20"/>
</RollingFile>
DefaultRolloverStrategy 默认的 min 是 1,查看源码可知,这里配置 20 之后,会产生下标 1~20 的文件,如果修改了 min 为 5 则就是 5~20。
十 Strategy
Strategy 都是控制如何(How)进行日志滚动的。
Strategy常用的实现类:DefaultRolloverStrategy,这个也是默认的实现类。
常用的属性:
- min:最小文件下标。
- max:最大文件下标。
- compressionLevel:日志压缩级别,0-9,越大压缩越厉害。
十一 Logger
Logger 部分就比较简单了,分为两个 Logger :
- Root(必须配置)
- Logger
11.1 Root
简单示例:
<?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"/>
<LevelRangeFilter minLevel="error" maxLevel="info" onMatch="ACCEPT" onMismatch="DENY" />
</Root>
</Loggers>
</Configuration>
注意:Logger中也可以加过滤器的哟~
11.2 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="com.snailwu.log" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
这样就不会打印重复的日志了。
十二 ThreadContent
这个与 logback 的 MDC 类似。
log4j2.xml 文件:
<appenders>
<Console name="Console">
<PatternLayout>
<Pattern>用户: %X{name} %d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</Console>
</appenders>
<loggers>
<root level="info">
<AppenderRef ref="Console"/>
</root>
</loggers>
Java 代码:
import org.apache.logging.log4j.CloseableThreadContext;
import org.apache.logging.log4j.ThreadContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) throws InterruptedException {
ThreadContext.put("name", "Mike");
log.info("Hello Info");
new Thread((() -> {
log.info("无 Name");
})).start();
Map<String, String> stringMap = ThreadContext.getImmutableContext();
List<String> list = ThreadContext.getImmutableStack().asList();
new Thread((() -> {
CloseableThreadContext.putAll(stringMap).pushAll(list);
log.info("有 Name");
})).start();
}
}
这个目前广泛使用的就是将 http 请求串起来。
疫情在家里重新整理了一遍,也加深了一下。