四、异步日志
4.1、介绍异步日志
简单介绍
官网(Log4j2异步日志):https://logging.apache.org/log4j/2.x/manual/async.html
log4j2最大的特点就是异步日志,通过使用异步日志能够大大提升性能!
log4j2中提供了两种方式使用异步日志:AsyncAppender方式以及AsyncLogger方式。
其中AsyncLogger可以使用全局异步也可以是混合异步。
同步与异步区别
看下同步日志与异步日志之间的区别:
同步日志:
异步日志:
区别介绍:
对于同步日志操作,先进行Logger中的操作并传输到LogEvent对象中接着再执行Appender操作进行日志输出,主线程会将这些步骤都执行完才算作一条日志结束。
对于异步日志操作,我们可以看到主线程当执行完Logger操作产生出LogEvent对象会放置到一个队列后就结束返回,对应在队列中的LogEvent会交由线程2操作,大大提升了效率。
性能比对
我们再看下官网提供的一个性能图比对图:
主要看64threads的性能表现对比特别突出:
第一个是使用AsyncLogger方式的全局异步,性能最高。
第二个是使用AsyncLogger方式的混合异步,性能会稍差全局异步。
第三个是使用AsyncAppender方式性能与Log4j、Logback都差不多了。
4.2、依赖jar包
引入对应依赖jar包即可使用log4j2的异步日志:
<!-- log4j2并发编程依赖包 --> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.4</version> </dependency>
若是日志使用的是slf4j+log4j2,并使用log4j2的异步日志,直接引入如下依赖
<!--使用slf4j作为日志的门面,使用log4j2来记录日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!--为slf4j绑定日志实现 log4j2的适配器 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.10.0</version> </dependency> <!-- Log4j2 门面API --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.13.3</version> </dependency> <!-- Log4j2的日志实现 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.11.1</version> </dependency> <!-- log4j2并发编程依赖包 --> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.4</version> </dependency> </dependencies>
4.3、异步日志的实际使用(3种)
①AsyncAppender方式
使用AsyncAppender是设置指定的一个appender为异步方式,如下:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="debug" monitorInterval="5"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" /> </Console> <!-- ①AsyncAppender方式 引用配置的appender --> <Async name="Async"> <AppenderRef ref="Console"/> </Async> </Appenders> <Loggers> <Root level="trace"> <!-- ②引用name为Async的AsyncAppender方式 --> <AppenderRef ref="Async" /> </Root> </Loggers> </Configuration>
在Appenders标签中使用Async标签(引用想要进行异步日志的appender),最后将下面对应的RootLogger引用该异步Appender即可。
这种方式不太建议使用,因为其效率与logback差不多,并且日志输出时不显示行号。
②AsyncLogger方式
介绍
AsyncLogger才是log4j2的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的 更快。你可以有两种选择:全局异步和混合异步。
全局异步则是让所有的日志都是异步的操作。
混合异步的话能够指定某个Appender进行异步,某个进行同步,更加灵活。
全局异步
全局异步就是所有的日志都是异步的操作,如何才能让所有的日志进行异步操作呢?我们只需要额外添加一个配置文件log4j2.component.properties并将其放置到resource目录下(Maven项目),配置内容如下:
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
之后我们运行项目默认所有的日志都是异步的操作,需要注意打印的日志消息没有行号!!!
混合异步(含自定义logger)
在原本log4j2.xml配置文件中自定义logger对象设置为异步(不需要添加全局异步的配置文件)
log4j2.xml: <?xml version="1.0" encoding="UTF-8"?> <!--日志框架本身日志等级debug 可以打印配置信息--> <Configuration status="debug" monitorInterval="5"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" /> </Console> </Appenders> <Loggers> <!--***************************************************--> <!-- 自定义Logger对象(异步) includeLocation="false" 表示关闭日志记录的行号信息 additivity="false" 不继承rootLogger对象 --> <AsyncLogger name="xyz.changlu" level="trace" includeLocation="false" additivity="false"> <AppenderRef ref="file"/> </AsyncLogger> <!--***************************************************--> <Root level="trace"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
在<Loggers>标签中定义了一个AsyncLogger标签表示异步Logger,并且指定引用Appender。
这里配置RootLogger是同步的,自定义Logger是异步。
测试程序:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Log4j2Test { //获取Logger实例 public static final Logger LOGGER = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args) { System.out.println(LOGGER.getName());//xyz.changlu.LogTest //1、打印日志记录 LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug"); LOGGER.trace("trace"); //2、占位符输出 String name = "changlu"; int age = 20; LOGGER.info("报错,name:{},age:{}",name,age); //3、打印堆栈信息 try { int i = 5/0; }catch (Exception e){ LOGGER.error("报错",e); } } }
使用这种方式的日志打印没有行号输出!!!
4.4、注意点
使用异步日志时的注意点如下:
使用异步日志时,打印的日志信息不会显示行号!
如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。否则性能会和 AsyncAppender一致,降至最低。
设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。
五、Log4j2的无垃圾模式介绍
垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,则需要花费更多大量精力来控制这些暂停。
许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串, 字符数组,字节数组等。
这会对垃圾收集器造成压力并增加GC暂停发生的频率。
从版本2.6开始,默认情况下Log4j以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal字段。
Log4j 2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。
看下面两个版本的测试比较
使用Log4j 2.5:内存分配速率809 MB /秒,141个无效集合:
Log4j 2.6没有分配临时对象:0(零)垃圾回收:
通过使用无垃圾模式,我们可以看到GC收集数为0,并且每秒分配率为1.58MB/s。
避免创建临时对象机制的两个属性
有两个单独的系统属性可用于手动控制Log4j用于避免创建临时对象的机制:
log4j2.enableThreadlocals - 如果“true”(非Web应用程序的默认值)对象存储在ThreadLocal字段中并重新使用,否则将为每个日志事件创建新对象。
log4j2.enableDirectEncoders - 如果将“true”(默认)日志事件转换为文本,则将此文本转换 为字节而不创建临时对象。注意: 由于共享缓冲区上的同步,在此模式下多线程应用程序的同步日志记录性能可能更差。如果您的应用程序是多线程的并且日志记录性能很重要,请考虑使用异步记录器。
总结
1、Log4j2有6个日志等级,默认日志等级为error。—见第1部分
2、可设置定义配置文件log4j2.xml,包含了各个常使用的Appender以及RootLogger配置。—见第2部分
3、Log4j2也有日志门面,不过建议使用slf4j日志门面来对日志实现框架进行同一管理。—见第3部分
4、Log4j2也推出异步日志,其包含AsyncAppender方式、AsyncLogger方式((全局异步、混合异步))两种方式配置,其中全局异步日志性能最好大约是Logback的18倍,其次就是混合异步,性能最差的是AsyncAppender方式与Logback性能几乎差不多。使用异步日志需要注意的是其无法输出方法中的行号信息!—见4部分
5、在Log4j2的2.6版本之后使用无垃圾模式,通过重用ThreadLocal字段中的对象以及文本转换为字节时重用缓冲区来大大减少内存的使用。—见第5部分