log4j2异步日志解读(一)AsyncAppender

log4j、logback、log4j2 历史和关系,我们就在这里不展开讲了。直接上干货,log4j2突出于其他日志的优势,异步日志实现。
看一个东西,首先看官网文档 ,因为前面文章已经讲解了disruptor源码,本文主要展开说说异步日志AsyncAppender和AsyncLogger(基于disruptor实现)。
一、AsyncAppender

我们先来看看AsyncApperder核心,就是logger将数据通过append方法放入到阻塞队列中,随后后台线程从队列中取出数据然后进行后续的操作。
那这样看来,就很简单了,一个append()方法,一个后台线程执行就是我们要看的核心代码了。围绕我们要看的类AsyncAppender,来看看类关系图。

一、放入队列
主要实现就是logger将数据通过append方法放入到阻塞队列中。
//AsyncAppender.java
/**
* Actual writing occurs here.
*
* @param logEvent The LogEvent.
*/
@Override
public void append(final LogEvent logEvent) {
if (!isStarted()) {
throw new IllegalStateException("AsyncAppender " + getName() + " is not active");
}
//创建Log4jLogEvent的对象memento
final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);
InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage());
//transfer(memento)将event放入队列
//默认ArrayBlockingQueueFactory 大小1024
if (!transfer(memento)) {
if (blocking) {
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();
logMessageInCurrentThread(logEvent);
} else {
// delegate to the event router (which may discard, enqueue and block, or log in current thread) final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel());
route.logMessage(this, memento);
}
} else {
error("Appender " + getName() + " is unable to write primary appenders. queue is full");
logToErrorAppenderIfNecessary(false, memento);
}
}
} private boolean transfer(final LogEvent memento) {
return queue instanceof TransferQueue
? ((TransferQueue<LogEvent>) queue).tryTransfer(memento)
: queue.offer(memento);
}

如流程图所示,首先会判断用户是否设置了blocking选项,默认是true,如果设置为false,则Appender直接会ToErrorAppender,如果用户没有配置或者配置为true,则会按照一定的策略来处理这些消息。策略可以分为2种,他们分别为:
1、DefaultAsyncQueueFullPolicy---等待队列,转为同步操作策略
public class DefaultAsyncQueueFullPolicy implements AsyncQueueFullPolicy {
@Override
public EventRoute getRoute(final long backgroundThreadId, final Level level) {
// LOG4J2-471: prevent deadlock when RingBuffer is full and object
// being logged calls Logger.log() from its toString() method
if (Thread.currentThread().getId() == backgroundThreadId) {
return EventRoute.SYNCHRONOUS;
}
return EventRoute.ENQUEUE;
}
2、DiscardingAsyncQueueFullPolicy---按照日志等级抛弃日志策略
//DiscardingAsyncQueueFullPolicy.java
@Override
public EventRoute getRoute(final long backgroundThreadId, final Level level) {
if (level.isLessSpecificThan(thresholdLevel)) {
if (discardCount.getAndIncrement() == 0) {
LOGGER.warn("Async queue is full, discarding event with level {}. " +
"This message will only appear once; future events from {} " +
"are silently discarded until queue capacity becomes available.",
level, thresholdLevel);
}
return EventRoute.DISCARD;
}
return super.getRoute(backgroundThreadId, level);
}
二、后台线程执行后续操作。
主要就是后台线程从队列中取出数据然后进行后续的操作。
//AsyncAppender.java
private class AsyncThread extends Log4jThread { private volatile boolean shutdown = false;
private final List<AppenderControl> appenders;
private final BlockingQueue<LogEvent> queue; public AsyncThread(final List<AppenderControl> appenders, final BlockingQueue<LogEvent> queue) {
super("AsyncAppender-" + THREAD_SEQUENCE.getAndIncrement());
this.appenders = appenders;
this.queue = queue;
setDaemon(true);
} @Override
public void run() {
while (!shutdown) {
LogEvent event;
try {
event = queue.take();
if (event == SHUTDOWN_LOG_EVENT) {
shutdown = true;
continue;
}
} catch (final InterruptedException ex) {
break; // LOG4J2-830
}
event.setEndOfBatch(queue.isEmpty());
final boolean success = callAppenders(event);
if (!success && errorAppender != null) {
try {
errorAppender.callAppender(event);
} catch (final Exception ex) {
// Silently accept the error.
}
}
}
// Process any remaining items in the queue.
LOGGER.trace("AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.",
queue.size());
int count = 0;
int ignored = 0;
while (!queue.isEmpty()) {
try {
final LogEvent event = queue.take();
if (event instanceof Log4jLogEvent) {
final Log4jLogEvent logEvent = (Log4jLogEvent) event;
logEvent.setEndOfBatch(queue.isEmpty());
callAppenders(logEvent);
count++;
} else {
ignored++;
LOGGER.trace("Ignoring event of class {}", event.getClass().getName());
}
} catch (final InterruptedException ex) {
// May have been interrupted to shut down.
// Here we ignore interrupts and try to process all remaining events.
}
}
LOGGER.trace("AsyncAppender.AsyncThread stopped. Queue has {} events remaining. "
+ "Processed {} and ignored {} events since shutdown started.", queue.size(), count, ignored);
} ...
}
该线程会一直尝试从阻塞队列中获取LogEvent,如果获取成功,调用AppenderRef所引用Appender的append方法。我们也可以看到,AsyncAppender实际上主要是类似于中转,日志异步化,当消息放入阻塞队列,返回成功,这样能够大幅提高日志记录的吞吐。用户可以在权衡性能与日志收集质量上进行权衡配置策略(设置blocking选项),当然也可以设置不同类型的阻塞队列已到达更好的日志记录吞吐。
AsyncAppender配置参数
https://logging.apache.org/log4j/2.x/manual/appenders.html#AsyncAppender

log4j2异步日志解读(一)AsyncAppender的更多相关文章
- log4j2异步日志解读(二)AsyncLogger
前文已经讲了log4j2的AsyncAppender的实现[log4j2异步日志解读(一)AsyncAppender],今天我们看看AsyncLogger的实现. 看了这个图,应该很清楚AsyncLo ...
- 一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位
一次鞭辟入里的 Log4j2 日志输出阻塞问题的定位 问题现象 线上某个应用的某个实例突然出现某些次请求服务响应极慢的情况,有几次请求超过 60s 才返回,并且通过日志发现,服务线程并没有做什么很重的 ...
- 如何监控 Log4j2 异步日志遇到写入瓶颈
如何监控 Log4j2 异步日志遇到写入瓶颈 在之前的一篇文章中(一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位),我们详细分析了一个经典的 Log4j2 异步日志阻塞问题的定位,主要原因还 ...
- 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 云环境改进异步日志等待策略
最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...
- log4j2笔记 #02# 启用异步日志
索引 参考 Making All Loggers Asynchronous 第一步,添加相应的disruptor库 第二步,设置系统属性log4j2.contextSelector 第三步,检验! 参 ...
- log4j2用Log4jContextSelector启动参数配置全局异步日志是如何使用disruptor
与 log4j2用asyncRoot配置异步日志是如何使用disruptor差异有几个: 给disruptor实例的EventFactory不同 此处EventFactory采用的是RingBuffe ...
随机推荐
- [bzoj1031][JSOI2007]字符加密Cipher_后缀数组
字符加密Cipher bzoj-1031 JSOI-2007 题目大意:题目链接. 注释:略. 想法: 后缀数组裸题啊. 后缀数组其实背下来板子之后有几个数组记住就可以了. $sa_i$表示排名为$i ...
- hdfs是什么?
参考:https://www.cnblogs.com/shijiaoyun/p/5778025.html hadoop分布式文件系统 1.hdfs是一个分布式文件系统,简单理解就是多台机器组成的一个文 ...
- 【python自制】让大白成为你的个人助手!
我做这个软件就是要让卖萌进行究竟! 官方站点:http://www.jackeriss.com/companions.htm GitHub:https://github.com/Jackeriss/C ...
- 查找olr备份路径
使用:ocrdump -local <olr_dump_name> more <olr_dump_name> 来查找 [SYSTEM.OLR.BACKUP.LOC] 的相应键值 ...
- Android GMS无法通过网络定位
前言 欢迎大家我分享和推荐好用的代码段~~ 声明 欢迎转载.但请保留文章原始出处: CSDN:http://www.csdn.net ...
- onenstack 简介
一.云计算的前世今生 所有的新事物都不是突然冒出来的,都有前世和今生.云计算也是IT技术不断发展的产物. 要理解云计算,需要对IT系统架构的发展过程有所认识. 请看下 IT系统架构的发展到目前为止大致 ...
- JsonArray和JsonObject的使用
import net.sf.json.JSONArray; import net.sf.json.JSONObject; public class JsonTest { public static v ...
- Azure Pack演示样例缩放部署架构
- MyEclipse,Eclipse注释规范模板
注释的作用:myeclipse中java文件头注释格式设置,最终可用于javadoc.exe生成API文档,同时提高了代码的可读性 设置方法: windows->preferences-> ...
- 容器HashSet原理(学习)
一.概述 使用HashMap存储,非线程安全: 二.实现 HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调 ...