目录
日志框架出现的历史顺序:Log4j → JUL → JCL → slf4j → logback → log4j2
Log4j2的使用
Log4j的升级版
Apache的Log4j2 参考了logback一些优秀的设计,并修复了一些问题,带来了重大提升,主要有:
- 异常处理:在logback中,Appender中的异常不会被应用感知到,但在log4j2中,提供了一些异常处理机制
- 性能提升:log4j2相较于log4j 和 logback都具有很明显的性能提升,后面会有官方测试的数据
- 自动重载配置:参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用
- 无垃圾机制:log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的 jvm gc
无垃圾机制设计思想
针对对象的重用和缓冲区的使用,对象一直在复用的话,就很少产生新对象了,就间接的降低了GC的访问压力了
Log4j2入门
目前市面上最主流的日志门面就是SLF4J,虽然log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。大家一般将log4j2看作是日志的实现,Slf4j + log4j2 应该是未来的大势所趋。
▎快速入门案例:log4j2
1. 引入maven依赖(为了方便测试,同步也引入junit)
<dependencies>
<!-- junit 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<!-- log4j2 日志门面技术 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.0</version>
</dependency>
<!-- log4j2 日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
2. 编写测试用例
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class Demo {
// 获取日志管理器对象
public static final Logger logger = LogManager.getLogger(Demo.class);
// 入门案例
@Test
public void testQuick(){
logger.fatal("hello fatal");
logger.error("hello error"); // 默认日志级别
logger.warn("hello warn");
logger.info("hello info");
logger.debug("hello debug");
logger.trace("hello trace");
}
}
3. 运行测试用例
▎快速入门案例:Slf4j + log4j2
log4j2也是日志门面,其功能非常强大,性能优越。大家一般将log4j2看作是日志的实现,企业中使用一般都是Slf4j + log4j2
既要导入slf4j-api的日志门面+log4j2的适配器,也要导入log4j2-api日志门面+log4j2具体实现
1. 在原有log4j2日志门面和日志实现的依赖上,增加slf4j的日志门面技术 和 log4j2的适配器
<dependencies>
<!-- junit 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<!-- slf4j 日志门面技术 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!-- 使用log4j2 的适配器进行绑定 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.13.3</version>
</dependency>
<!-- log4j2 日志门面技术 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.0</version>
</dependency>
<!-- log4j2 日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
2. 测试用例
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jDemo {
public static final Logger logger = LoggerFactory.getLogger(Slf4jDemo.class);
@Test
public void testQuick(){
logger.error("hello error"); // 默认日志级别
logger.warn("hello warn");
logger.info("hello info");
logger.debug("hello debug");
logger.trace("hello trace");
}
}
3. 执行结果
➳ 结论:增加了slf4j日志门面技术和log4j2的适配器后,两个入门案例中的代码都能够运行,并且不会冲突,既可以使用log4j2 也可以使用 slf4j+log4j2
Log4j2日志级别
log4j2 日志级别总共有8种,除去两个特殊的级别:off、all,其级别严格可以划分6种
日志级别(降序) | 使用 |
---|---|
OFF | 关闭所有级别的日志记录(不捕获任何内容) |
FATAL | 严重错误,一般会造成系统奔溃并终止运行 |
ERROR | 错误信息,不会影响系统运行 |
WARN | 警告信息,可能会发生问题 |
INFO | 运行信息,数据连接、网络连接、IO 操作等 |
DEBUG(默认) | 调试信息,一般在开发中使用,记录程序变量参数传递信息等 |
TRACE | 追踪信息,记录程序所有的流程信息 |
ALL | 打开所有级别的日志记录(捕获所有内容) |
➳ 结论:级别依次从高到低,其中OFF可用来关闭日志记录,ALL启用所有消息的日志记录
Log4j2 配置文件
log4j2 默认加载classpath下的log4j2.xml配置文件
log4j2是参考logback设计的,因此它俩的配置文件配置方式大差不差
★ log4j2.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!-- status:日志框架本身的输出日志级别
monitorInterval:自动加载配置文件的间隔时间,不低于5秒(生产环境修改了配置文件也无需重启服务)
-->
<Configuration status="debug" monitorInterval="5">
<!-- 集中配置管理属性:使用时通过${name} -->
<properties>
<property name="log_dir">/Users/wpf011/log</property>
</properties>
<!-- 日志处理 -->
<Appenders>
<!-- 输出到控制台的appender 该appender的名称为console 输出流类型为:SYSTEM.OUT 普通黑色字体输出在控制台 -->
<Console name="console" target="SYSTEM_OUT">
<!-- 消息输出格式的表达式 -->
<PatternLayout pattern="%d{HH:mm:ss} [%t] [%-5level] %c{36}:%L --------%m%n" />
</Console>
<!--输出到文件的appender 该appender的名称为file fileName:指定文件名和保存路径-->
<File name="file" fileName="${log_dir}/myLog4j2.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %l %c{36} --------%m%n" />
</File>
<!-- 随机读写流的日志文件输出appender 性能提高很多 -->
<RandomAccessFile name="randomFile" fileName="${log_dir}/myAcc.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %l %c{36} --------%m%n" />
</RandomAccessFile>
<!-- 指定拆分规则的 appender -->
<!-- filePattern:细粒度日志拆分条件
——$${data:yyyy-MM-dd}:以天为单位生成一个日志文件夹
——myRoll-%d{yyyy-MM-HH-mm}:当天内按照分钟为单位生成一个日志文件
——%i:如果日志文件达到了指定大小,再按照大小进行拆分,文件名以自增序号自动创建新的文件
注意:还可以进行文件的归档压缩处理,在.log后面增加具体压缩格式,例如 xxx.log.zip
-->
<RollingFile name="rollFile" fileName="${log_dir}/myRoll.log"
filePattern="${log_dir}/$${data:yyyy-MM-dd}/myRoll-%d{yyyy-MM-HH-mm}-%i.log">
<!-- 日志级别的过滤器,低于debug的日志级别会被拦截 -->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<!-- 消息输出格式的表达式 -->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %l %c{36} --------%m%n" />
<!-- 设置具体拆分规则 -->
<Policies>
<!-- 在系统启动时,触发拆分规则,生产一个新的日志文件-->
<OnStartupTriggeringPolicy/>
<!-- 按照文件大小拆分:10MB 文件超出大小则会拆分-->
<SizeBasedTriggeringPolicy size="10 MB"/>
<!-- 按照时间节点拆分,规则根据filePattern定义的-->
<TimeBasedTriggeringPolicy/>
</Policies>
<!-- 在同一个目录下,文件的个数限定为30个,超过进行覆盖-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<!--logger 定义 -->
<loggers>
<!-- 使用RootLogger配置日志级别 -->
<Root level="trace">
<!-- 指定日志使用的appender处理器 -->
<Appender-Ref ref="console" />
</Root>
</loggers>
</Configuration>
1. 将该配置文件放到classpath路径下
2. 测试用例代码如下
public class Log4j2Demo {
public static final Logger logger = LogManager.getLogger(Log4j2Demo.class);
@Test
public void testQuick(){
logger.fatal("hello fatal");
logger.error("hello error"); // 默认日志级别
logger.warn("hello warn");
logger.info("hello info");
logger.debug("hello debug");
logger.trace("hello trace");
}
}
3. 运行结果
➳ 说明:配置文件中设置的日志级别是trace,因此所有日志都会输出,红框外的消息是日志框架本身的日志信息, 级别为debug,在<Configuration status="debug" >标签中status属性进行设置
Log4j2异步日志
log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益
● 同步日志
执行一条日志打印,其内部需执行这么多流程,当全部流程都结束后,主线程才能往下去执行
✸ 问题:随着日志文件记录的信息比较完善,越来越多的话,性能会出现很大的问题
● 异步日志
执行一条日志打印,它也需要创建logger对象,生成一个logEvent事件处理,注意,在生成完这个事件对象以后,它并不会去appender进行日志的具体处理,而是把它传递给ArrayBlockingQueue阻塞队列,传递成功之后,主线程的任务就完成了,可继续处理其它业务代码,等待时间大大缩短,间接的提升了程序的运行性能
传递给阻塞队列后,log4j2就会开启一个新的线程,通过这个新的线程来执行logEvent对象,调用具体的Appender进行处理,最终输出到我们的指定位置
异步日志组件
log4j2提高了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应前面我们所说的Appender组件和Logger组件
异步的appender一般开发中很少使用,因为它性能并没有提升多少,一般是配置异步的Logger对象(AsyncLogger),也可以单独配置某个Logger为异步(混合异步),或者全部Logger设置异步(全局异步)
!! 注意:配置异步日志需要添加依赖
<!-- log4j2的异步日志依赖 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
▎AsyncAppender(异步的Appender)
增加完异步日志的依赖后,只需要在appender标签块内增加<Async>标签,logger对象再引用即可
<Configuration status="debug" monitorInterval="5">
<!-- 集中配置管理属性:使用时通过${name} -->
<properties>
<property name="log_dir">/Users/wpf011/log</property>
</properties>
<!-- 日志处理 -->
<Appenders>
<!--输出到文件的appender -->
<File name="file" fileName="${log_dir}/myLog4j2.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %l %c{36} --------%m%n" />
</File>
<!-- 异步appender:增加<Async>标签,logger对象再引用该异步appender-->
<Async name="asyncFile">
<!-- 指向具体处理日志的appender -->
<Appender-Ref ref="file" />
</Async>
</Appenders>
<!--logger 定义 -->
<loggers>
<!-- 使用RootLogger配置日志级别 -->
<Root level="trace">
<!-- 引用异步的appender -->
<Appender-Ref ref="asyncFile" />
</Root>
</loggers>
</Configuration>
1. 执行测试用例
2. 执行结果
➳ 结论:上述生成的日志文件是通过异步的方式来生成的,这种效率并不高,与logback相差无几,因此开发中很少使用异步的appender生成日志,一般是使用异步的Logger
▎AsyncLogger(异步的Logger)
AsyncLogger才是log4j2的重头戏,也是官方推荐的异步方式,它可以使得调用Logger.log返回的更快。
你可以有两种选择:全局异步 和 混合异步。
✈ 全局异步:
全部的日志将异步进行记录,配置文件无需改动,只需添加一个log4j2.component.properties配置文件,并设置如下参数即可
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
1. 在classpath下增加log4j2.component.properties配置文件
2. log4j2.xml 配置文件如下
<?xml version="1.0" encoding="UTF-8" ?>
<!-- status:日志框架本身的输出日志级别
monitorInterval:自动加载配置文件的间隔时间,不低于5秒(生产环境修改了配置文件也无需重启服务)
-->
<Configuration status="debug" monitorInterval="5">
<!-- 集中配置管理属性:使用时通过${name} -->
<properties>
<property name="log_dir">/Users/wpf01137679/log</property>
</properties>
<!-- 日志处理 -->
<Appenders>
<!-- 输出到控制台的appender 该appender的名称为console 输出流类型为:SYSTEM.OUT 普通黑色字体输出在控制台 -->
<Console name="console" target="SYSTEM_OUT">
<!-- 消息输出格式的表达式 -->
<PatternLayout pattern="%d{HH:mm:ss} [%t] [%-5level] %c{36}:%L --------%m%n" />
</Console>
<!--输出到文件的appender-->
<File name="file" fileName="${log_dir}/myLog4j2.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] [%-5level] %l %c{36} --------%m%n" />
</File>
</Appenders>
<!--logger 定义 -->
<loggers>
<!-- 使用RootLogger配置日志级别 -->
<Root level="trace">
<!-- 指定日志输出到控制台的appender -->
<Appender-Ref ref="console" />
<!-- 指定输出到文件的appender -->
<Appender-Ref ref="file" />
</Root>
</loggers>
</Configuration>
3. 执行结果
➳ 结论:设置了全局异步后,不管是输出到控制台的日志内容还是输出到文件,都会采用异步输出方式,而不是同步,实际上日志框架本身的输出采用的就是异步方式的
✈ 混合异步
你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活
异步appender是配置<Async>标签,配置异步logger同理,配置<AsyncLogger>标签
<!--logger 定义 -->
<loggers>
<!-- 1.配置RootLogger父对象属性 -->
<Root level="trace">
<!-- 指定日志使用的appender处理器 -->
<Appender-Ref ref="console" />
</Root>
<!-- 2.配置名为“Test01”的logger对象为异步logger
includeLocation="false":关闭日志记录的行号信息
additivity="false":不再继承RootLogger父元素的属性配置
-->
<AsyncLogger name="Test01" level="trace" includeLocation="false" additivity="false" >
<Appender-Ref ref="console" />
</AsyncLogger>
</loggers>
❥ 使用异步日志注意
- 如果使用异步日志,AsyncAppender、AsyncLogger局部日志 和 全局日志,不要同时出现,性能会和AsyncAppender一致,降至最低
- 如设置includeLocation=true,打印行号信息可能会影响到异步日志的记录性,造成性能比同步日志记录还要差,通常会把行号信息关闭,即设置false
混合日志AsyncLogger 和 全局日志二选一使用,AsyncAppender建议关闭,因为性能最低!
无垃圾记录
2.6版本默认开启。垃圾收集暂停是延迟峰值的常见原因,对于许多系统而言,花费大量精力来控制这些暂停。
日志的生成会产生很多临时对象,许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串,字符数组,字节数组等。这会对垃圾收集器造成压力并增加GC暂停发生的频率。
从版本2.6开始,默认情况下Log4j2以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal字段。
Log4j2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现.
有两个单独的系统属性可用于手动控制Log4j用于避免创建临时对象的机制
♦ log4j2.enableThreadlocals
如果设置true(非web应用程序的默认值)对象存储在ThreadLocal字段中并重新使用,否则将为每个日志事件创建新对象
♦ log4j2.enableDirectEncoders
如果设置true(默认)日志事件转换文本,则将此文本转换为字节而不创建临时对象。注意:由于共享缓冲区上的同步,在此模式下多线程应用程序的同步日志记录性能可能更差。如果您的应用程序是多线程的并且日志记录性能很重要,请考虑使用异步记录器