包阅导读总结
1.
关键词:异步日志、Log4j2、性能优化、日志脱敏、内存泄漏
2.
总结:文章讲述余利宝业务因日志脱敏导致接口性能问题,通过去掉日志打印解决。介绍了 Log4j2 框架的优势,包括性能、配置灵活性等,阐述其工作原理、组件和日志输出流程。还提到日志脱敏的实现方式及异步日志的使用、潜在问题与解决方案,强调日志的重要性和打印策略。
3.
– 余利宝业务赎回接口因日志脱敏耗时激增,去掉打印日志后优化性能。
– 接口超时报警,排查发现新增日志耗时,去掉后耗时从 2s 优化到 600ms。
– 分析原因为日志脱敏导致同步调用耗时。
– Log4j2 框架优势
– 性能高,减少日志操作对应用性能影响。
– 配置灵活,支持多种配置方式和动态重新配置。
– 插件架构,组件可插拔,易于扩展和自定义。
– 内存和资源管理高效,减少内存泄漏风险。
– 可靠性强,有故障恢复机制和条件日志记录。
– 与 SLF4J 集成良好。
– Log4j2 框架原理
– 包括 Logger、LoggerContext 等组件。
– 日志输出经过过滤、可靠性策略处理、写入目的地和格式化。
– 日志脱敏
– 借助 PatternConverter 定制脱敏组件。
– 异步日志
– 原理基于 Disruptor 的环形缓冲区。
– 开启方式包括全局和混合异步日志。
– 存在日志丢失、顺序问题等潜在问题及解决方案。
– 强调日志重要性和打印策略
– 如动静分离、合理分割、合理设置日志级别。
思维导图:
文章地址:https://mp.weixin.qq.com/s/yO2lfvA2Jvw_HxP5LKwJsQ
文章来源:mp.weixin.qq.com
作者:柏淳
发布时间:2024/7/9 8:26
语言:中文
总字数:5607字
预计阅读时间:23分钟
评分:90分
标签:日志优化,Log4j2,异步日志,性能提升,数据脱敏
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
这是2024年的第48篇文章
( 本文阅读时间:15分钟 )
-
支持条件日志记录(Conditionals),可以根据运行时条件决定是否记录日志。
-
AwaitCompletionReliabilityStrategy: 等待日志接收完成策略。这种策略主要是在应用关闭时,尽可能要等应用日志接收完成后再结束Appender的生命周期(这种策略只是说尽可能所有日志等待调用Appender.append方法完成,但在异步日志场景下,Appender.append其实是落了ringbuffer或者其他队列里,实际上未持久化。因此该策略是尽可能保证接收完成而非处理完成)
-
AwaitUnconditionallyReliabilityStrategy: 无条件等待策略。这种策略会在rootLogger关闭时无条件等待一段时间,具体等待时间可以配置log4j2.component.properties文件的log4j.waitMillisBeforeStopOldConfig属性。
-
DefaultReliabilityStrategy: 默认策略。该策略不做任何等待。 -
LockingReliabilityStrategy: 锁等待策略。该策略当正在写入日志时,则会等待;否则即会停止等待。
import java.util.Arrays;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.message.FormattedMessage;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFormatMessage;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.message.StringFormattedMessage;
import org.apache.logging.log4j.util.PerformanceSensitive;
@Plugin(
name = "ShieldPatternConverter",
category = "Converter"
)
@ConverterKeys({"shield", "sd", "shieldMessage", "sm"})
@PerformanceSensitive({"allocation"})
public final class ShieldMessagePatternConverter extends LogEventPatternConverter {
private final String[] options;
private ShieldMessagePatternConverter(String[] options) {
super("Shield", "shield");
this.options = options == null ? null : (String[])Arrays.copyOf(options, options.length);
}
public static ShieldMessagePatternConverter newInstance(String[] options) {
return new ShieldMessagePatternConverter(options);
}
@Override
public void format(LogEvent logEvent, StringBuilder output) {
Message message = logEvent.getMessage();
String format = message.getFormat();
if (isFormatMessage(message)) {
String msgInfo = ShieldUtils.format(format, message.getParameters());
output.append(msgInfo);
} else {
output.append(message.getFormattedMessage());
}
}
private boolean isFormatMessage(Message message) {
return message instanceof ParameterizedMessage || message instanceof StringFormattedMessage
|| message instanceof FormattedMessage || message instanceof MessageFormatMessage;
}
}
<RollingFile name="TEST_APPENDER" fileName="test.log"
filePattern="test.log.%d{yyyy-MM-dd}"
append="true">
<PatternLayout pattern="%d %sm%n" charset="UTF-8"/>
<TimeBasedTriggeringPolicy/>
<DefaultRolloverStrategy/>
</RollingFile>
异步日志原理概述

如何使用异步日志
-
通过JVM启动参数来全局启用异步日志功能。在启动应用程序时,向JVM传递以下系统属性:
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
-
在类路径(classpath)中添加一个名为log4j2.component.properties的文件,并包含以下内容(这个文件会在Log4j2初始化时被读取):
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
这两种方式下,所有Logger都会自动使用异步处理。
在log4j2.xml配置文件中,可以手动指定特定的Logger使用异步处理,通过将<Root>或<Logger>元素替换为<AsyncRoot>或<AsyncLogger>。例如:
<Configuration status='WARN'>
<Appenders>
... <!-- your appenders here -->
</Appenders>
<Loggers>
<AsyncRoot level='info' includeLocation='false'>
<AppenderRef ref='yourAppenderName'/>
</AsyncRoot>
<!-- 或者为特定logger配置 -->
<AsyncLogger name='com.example.MyClass' level='debug'>
<AppenderRef ref='yourAppenderName'/>
</AsyncLogger>
</Loggers>
</Configuration>
异步日志的潜在问题及解决方案
日志丢失问题:如果机器发生意外重启、发布、掉电导致的jvm进程停止,停留在队列的未来得及输出到目的地的LogEvent可能会丢失 日志顺序问题:由于日志事件是在不同的线程中异步处理的,因此日志条目可能不会严格按照它们产生的顺序出现在日志文件中,这对于需要严格按时间顺序追踪日志的应用可能是个问题。 其他问题:如增加资源损耗、配置复杂度和调试复杂度等问题
-
原生Log4j2有完整的生命周期管理,并监听了jvm关闭的事件。当jvm关闭时,Log4j2会监听Disrupter队列中的RingbufferLogEvent数量,直到日志打印完(或超时)才释放关闭Log4j2,jvm才得以正常关闭。但是自然灾害或者机房掉电等不可抗力因素,无法避免丢失问题。 -
我们基于Log4j2定制的AsyncAbleRollingFileAppender,其中有独立的Disrupter,且不在Log4j2生命周期管理当中,存在日志丢失风险。可以采用类似方案解决:
try {
LoggerContextFactory factory = LogManager.getFactory();
if (!(factory instanceof Log4jContextFactory)) {
return;
}
Log4jContextFactory log4jContextFactory = (Log4jContextFactory) factory;
ShutdownCallbackRegistry registry = log4jContextFactory.getShutdownCallbackRegistry();
if (!(registry instanceof DefaultShutdownCallbackRegistry)) {
return;
}
DefaultShutdownCallbackRegistry defaultShutdownCallbackRegistry = (DefaultShutdownCallbackRegistry) registry;
Field hooksField = DefaultShutdownCallbackRegistry.class.getDeclaredField("hooks");
hooksField.setAccessible(true);
Collection<Cancellable> hooks = (Collection<Cancellable>) hooksField.get(defaultShutdownCallbackRegistry);
Collection<Cancellable> newHooks = new CopyOnWriteArrayList<>();
newHooks.add(new Log4j2Cancellable(() -> {
new AppenderUnInstaller(register).run();
}));
newHooks.addAll(hooks);
hooksField.set(defaultShutdownCallbackRegistry, newHooks);
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
-
AsyncAbleRollingFileAppender使用独立的disrupter,且RingBufferLogEvent未及时清理对象,容易导致内存泄漏,异步日志场景请慎用。
-
异步线程池大小设置为1,但是会影响日志打印的速度(现在的普遍做法)。
4月份的这一问题发生后,我们从原理出发,对理财的核心应用做了升级和优化,整体服务耗时上取得了不错的性能优化效果。