log4j2异步日志解读(二)AsyncLogger
前文已经讲了log4j2的AsyncAppender的实现【log4j2异步日志解读(一)AsyncAppender】,今天我们看看AsyncLogger的实现。
看了这个图,应该很清楚AsyncLogger调用Disruptor,然后直接返回。至于高性能队列 这里已经展开讲了是如何实现的。
AsyncLogger的调用流程
我们来看看AsyncLogger的调用流程,log.info()首先会调用抽象类AbstractLogger,然后调用了Logger的logMessage。
//Logger.java
@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
final Throwable t) {
final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message;
final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
strategy.log(this, getName(), fqcn, marker, level, msg, t);
}
strategy.log是调用了ReliabilityStrategy接口,日志事件传递到适当的appender的对象的接口,然后调用了LoggerConfig.log()方法,来创建有关记录消息的上下文信息。
//LoggerConfig.java
@PerformanceSensitive("allocation")
public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,
final Message data, final Throwable t) {
List<Property> props = null;
if (!propertiesRequireLookup) {
props = properties;
} else {
if (properties != null) {
props = new ArrayList<>(properties.size());
final LogEvent event = Log4jLogEvent.newBuilder()
.setMessage(data)
.setMarker(marker)
.setLevel(level)
.setLoggerName(loggerName)
.setLoggerFqcn(fqcn)
.setThrown(t)
.build();
for (int i = 0; i < properties.size(); i++) {
final Property prop = properties.get(i);
final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
? config.getStrSubstitutor().replace(event, prop.getValue()) //
: prop.getValue();
props.add(Property.createProperty(prop.getName(), value));
}
}
}
final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
try {
log(logEvent, LoggerConfigPredicate.ALL);
} finally {
// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
ReusableLogEventFactory.release(logEvent);
}
}
接着我们来看AsyncLoggerConfig.logToAsyncDelegate()方法,首先会调用Disruptor,放入环形队列。如果环形队列阻塞,则执行等待策略。
//AsyncLoggerConfig.java
private void logToAsyncDelegate(LogEvent event) {
if (!isFiltered(event)) {
// Passes on the event to a separate thread that will call
// asyncCallAppenders(LogEvent).
populateLazilyInitializedFields(event);
if (!delegate.tryEnqueue(event, this)) {
//如果获取Disruptor队列需要等待则执行等待策略,这里类似AsyncAppender等待策略
handleQueueFull(event);
}
}
} private void handleQueueFull(final LogEvent event) {
if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031
// If queue is full AND we are in a recursive call, call appender directly to prevent deadlock
AsyncQueueFullMessageUtil.logWarningToStatusLogger();
logToAsyncLoggerConfigsOnCurrentThread(event);
} else {
// otherwise, we leave it to the user preference
final EventRoute eventRoute = delegate.getEventRoute(event.getLevel());
// 1、DefaultAsyncQueueFullPolicy---等待队列,转为同步操作策略
// 2、DiscardingAsyncQueueFullPolicy---按照日志等级抛弃日志策略
eventRoute.logMessage(this, event);
}
}
然后再来看看Disruptor写入 的过程。LogEvent是记录消息的上下文信息的接口,然后调用tryPublishEvent去获取环形队列的位置,然后发布数据到环形队列上。这一块具体可以看笔者前文Disruptor源码分析,这里就不展开讨论。
//AsyncLoggerConfigDisruptor.java
@Override
public boolean tryEnqueue(final LogEvent event, final AsyncLoggerConfig asyncLoggerConfig) {
final LogEvent logEvent = prepareEvent(event);
return disruptor.getRingBuffer().tryPublishEvent(translator, logEvent, asyncLoggerConfig);
}
日志的消费过程,定义RingBufferLogEventHandler类实现Disruptor的SequenceReportingEventHandler的onEvent方法,从ringbuffer读取事件进行处理。最后会调用该logger绑定的默认appender输出。
最后提供下笔者测试demo
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Appenders>
<RollingRandomAccessFile name="applicationAppender" fileName="./log/application.log"
filePattern="./log/$${date:yyyy-MM}/common-%d{yyyy-MM-dd}.log.gz"
append="false">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile> <Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] %t - %l - %m%n"/>
</Console> <!-- AsyncAppender配置 -->
<!--<Async name="asyncTest" blocking="true">-->
<!--<AppenderRef ref="applicationAppender"/>-->
<!--</Async>--> </Appenders> <Loggers>
<!-- AsyncLogger配置 -->
<AsyncLogger name="log4j2" >
<AppenderRef ref="applicationAppender"/>
</AsyncLogger> <Root level="info">
<!--<AppenderRef ref="CONSOLE"/>-->
<AppenderRef ref="applicationAppender"/>
</Root> <!--<Logger name="log4j2" level="debug" additivity="false" >-->
<!--<AppenderRef ref="CONSOLE"/>-->
<!--<AppenderRef ref="applicationAppender"/>-->
<!--</Logger>--> </Loggers>
</Configuration>
总结
1、Log4j 2的异步记录日志在一定程度上提供更好的吞吐量,但是一旦队列已满,appender线程需要等待,这个时候就需要设置等待策略,AsyncAppender是依赖于消费者最序列最后的消费者,会持续等待。至于异步性能图可以看下官方提供的吞吐量比较图,差异很明显。
2、因为AsyncAppender是采用Disruptor,通过环形队列无阻塞队列作为缓冲,多生产者多线程的竞争是通过CAS实现,无锁化实现,可以降低极端大的日志量时候的延迟尖峰,Disruptor 可是号称一个线程里每秒处理600万订单的高性能队列。
log4j2异步日志解读(二)AsyncLogger的更多相关文章
- log4j2异步日志解读(一)AsyncAppender
log4j.logback.log4j2 历史和关系,我们就在这里不展开讲了.直接上干货,log4j2突出于其他日志的优势,异步日志实现. 看一个东西,首先看官网文档 ,因为前面文章已经讲解了disr ...
- 如何监控 Log4j2 异步日志遇到写入瓶颈
如何监控 Log4j2 异步日志遇到写入瓶颈 在之前的一篇文章中(一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位),我们详细分析了一个经典的 Log4j2 异步日志阻塞问题的定位,主要原因还 ...
- 一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位
一次鞭辟入里的 Log4j2 日志输出阻塞问题的定位 问题现象 线上某个应用的某个实例突然出现某些次请求服务响应极慢的情况,有几次请求超过 60s 才返回,并且通过日志发现,服务线程并没有做什么很重的 ...
- log4j2异步日志配置及官方文档的问题澄清
配置及demo 方法一全部打开 加启动参数 -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextS ...
- Log4j2中的同步日志与异步日志
1.背景 Log4j 2中记录日志的方式有同步日志和异步日志两种方式,其中异步日志又可分为使用AsyncAppender和使用AsyncLogger两种方式. 2.Log4j2中的同步日志 所谓同步日 ...
- log4j 异步日志问题分析
1. 常用的DailyRollingFileAppender与RollingFileAppender是否同步? 1.1 代码分析 2. log4j 1.2.x提供了异步appender是什么?Asyn ...
- 近期业务大量突增微服务性能优化总结-3.针对 x86 云环境改进异步日志等待策略
最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...
- spring boot:配置druid数据库连接池(开启sql防火墙/使用log4j2做异步日志/spring boot 2.3.2)
一,druid数据库连接池的功能? 1,Druid是阿里巴巴开发的号称为监控而生的数据库连接池 它的优点包括: 可以监控数据库访问性能 SQL执行日志 SQL防火墙 2,druid的官方站: http ...
- spring boot:使用log4j2做异步日志打印(spring boot 2.3.1)
一,为什么要使用log4j2? log4j2是log4j的升级版, 升级后更有优势: 性能更强/吞吐量大/支持异步 功能扩展/支持插件/支持自定义级别等 这些优 ...
随机推荐
- cogs——73. 找最佳通路
73. 找最佳通路 ★☆ 输入文件:city.in 输出文件:city.out 简单对比时间限制:1 s 内存限制:128 MB 问题描述有 n 个 城市,它们之间的交通情况已知.现在 ...
- TensorFlow-GPU环境配置之一——安装Ubuntu双系统
本机已经安装过Windows系统,准备安装Ubuntu双系统进行TensorFlow相关工作,需要在windows中将磁盘分出一定空间供Ubuntu使用 1.首先下载Ubuntu17.04版本ISO ...
- pycharm内存不足时如何修改设置?
Help->Find Action->(type "VM Options")->(Click)"Edit Custom VM Options" ...
- jenkinsapi出现HTTPConnectionPool Max retires exceeded异常
python项目通过使用jenkinsapi远程控制jenkins jenkinsapi使用的远程连接方法是requests包,requests包又使用了urllib3,urllib3又引用了http ...
- javascript statically scope
在javascript 里面, 函数中使用的未定义的变量,会默认变为全局的变量. 而通过 var 这个关键字定义的变量,就是局部变量. As far as the output is concerne ...
- Unity3D研究之多语言用中文做KEY
做多语言的时候用中文做KEY绝对是有100%的优点,假设用英文表示那么代码里面给文字赋值的地方全都是英文.写的代码多了以后维护起来就没有人能看懂了,或者看起来非常费劲. 对PoolManager ...
- C++入门学习——模板
为什么须要模板? 我们已经学过重载(Overloading),对重载函数而言,C++ 通过函数參数(參数个数.參数类型)的正确匹配来调用重载函数.比如.为求两个数的最大值,我们定义 max () 函数 ...
- iOS开发——常见BUG——window决定程序的状态栏管理问题
Xcode7升级之后遇到的问题 问题一: 老项目在Xcode6上运行没有任何问题,但在Xcode7上运行直接崩了! 经过一波分析: 发现是因为我顶部状态栏处添加了topWindow,用于处理Tab ...
- STL 之 list源码自行实现(iterator)
(0)文件夹 STL 之 vector源码实现(云算法<< [] = 重载, new delete,throw catch) STLc++中string类的源码 堆(stack) 之 c ...
- 关于移动平台的viewport
viewport是用来设置移动平台上的网页宽度,写device-width比较好,不然会和设备上不一样 在使用device-width之后,图片资源最好使用百分比布局,进行自动缩放. 文字大小是一样的 ...