共计 3645 个字符,预计需要花费 10 分钟才能阅读完成。
引言
上篇文章 性能调优——小小的 log 大大的坑 已将具体的介绍了高并发下,不正确的应用日志姿态,可能会导致服务性能急剧下降问题。文末也给各位留下了解决方案——日志级别动静调整。
本文将具体介绍“动静日志”的实现原理及源码,心愿各位能在今后的生产环境中应答日志问题能“得心应手”!
背景
日志的重要性显而易见,是咱们排查问题,解决 BUG 的重要伎俩之一,然而在高并发环境下,又会存在悖论:
大量打印日志,耗费 I/O,导致 CPU 占用率高;缩小日志,性能是下来了,然而排查问题的链路断掉了。
痛点:一方面须要借助日志可疾速排查问题,另一方面要兼顾性能,二者是否得兼?
那么本文的动静日志调整实现就是为了能解决这个痛点所构思开发的。
性能个性
- 低侵入,疾速接入:以二方包(jar)的模式染指,只须要配置启用,对业务无感
- 及时响应,随调随改:应答研发不小心在大流量入口链路打印了大量 INFO 日志,能及时调整日志级别
- 阶梯配置反对:默认全局设置兜底,又能够反对部分 Logger 放 / 限流
- 人性化操作:与操作界面,不便批改
<!– more –>
技术实现
如下,我将以 log4j2 为实例作解说,其它日志实现大同小异,参照实现即可。
如下是 log 染指的配置文件示例:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info">
<Properties>
// 全局参数信息
</Properties>
<appenders>
// appender 具体配置
</appenders>
<loggers>
// 配置 appender 指向
</loggers>
</configuration>
以往咱们调整我的项目的日志时,要么是删除代码中的废日志,要么是批改下面的 xml 配置,针对某个包下或者类作日志级别限度,再从新打包部署失效。此时的效率是非常低的,不符个咱们的诉求。
那么如何实现动静调整呢,首先想到的是 xml 调整日志级别后是如何失效的?xml 自身就是一些配置信息,log 的实现类读取 xml 信息动静批改日志级别,有没有可能咱们在程序中间接去调用 log4j 外部的封装办法,绕过 xml 不就好了?
动静调整日志级别
源码查看:具体源码我已放在 github dynamic-logger-util,可自行查看。
顺着思路,查看 log4j 源码后,发现的确可行,如下即是调整日志办法的实现代码:
// 获取日志上下文
LoggerContext logContext = LoggerContext.getContext(false);
Configuration configuration = logContext.getConfiguration();
LoggerConfig loggerConfig = configuration.getRootLogger();
loggerConfig.setLevel(level);
// 失效
logContext.updateLoggers();
获取以后的 LoggerContext 后,再获取 configuration,以后的配置即是 xml 内的配置转换过去的,再获取 root logger, 即对应 xml 中的配置如下:
<Root level="info">
<AppenderRef ref="..."/>
<AppenderRef ref="..."/>
</Root>
其中 level 即是咱们须要更改的日志级别,可供选择的日志级别如下(参照 org.apache.logging.log4j.Level):
OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL;
如上咱们曾经能够更改全局日志级别,那么比方我想只 更改某个类内的日志级别 如何实现呢?
LoggerContext logContext = LoggerContext.getContext(false);
if (logContext.hasLogger(name)) {
// 准确匹配
Logger logger = logContext.getLogger(name);
logger.setLevel(newLevel);
flag = true;
} else {
// 正则匹配
Collection<Logger> loggers = logContext.getLoggers();
for (Logger logger : loggers) {if (Pattern.matches(name, logger.getName())) {logger.setLevel(newLevel);
flag = true;
}
}
}
通过获取的 logContext 获取相应的 logger 即可设置以后的类对应的日志级别,对应的程序代码如下:
// name = com.jifuwei.dynamic.logger.DynamicLoggerConfiguration
private static final org.slf4j.Logger = LoggerFactory.getLogger(DynamicLoggerConfiguration.class);
如上,曾经晓得了如何动静批改日志 api,那么如何去动静触发批改呢?
配置触发
触发更新的机制很多,咱们梳理如下:
如上能满足咱们需要的,最最简答不便的就是配置核心,当初都是微服务,大部分都是通过核心配置去告诉各个系统信息变更,配置核心都具备欠缺的界面和性能,可满足咱们实时变更下发告诉,又能灰度部署,缩小出错,几乎是动静配置的最佳搭档。
配置核心的选型十分多,我将以 Apollo 为例,演示如何触发日志级别变更。我将配置 Key 设计如下:
// 全局管制日志级别
key: log_level val=OFF/FATAL/ERROR/WARN/INFO/DEBUG/TRACE/ALL
// 部分管制日志级别
key: log_level_detail
val:
{
"com.jifuwei.demo.Test1": "ERROR", // 每个 logger 都可配置本人专属的日志级别
"com.jifuwei.demo.Test2": "OFF",
"com.jifuwei.demo.Test3": "INFO",
}
要害实现如下:
public void init() {
// 初始化风控监听 action 配置
String level = apolloConfig.getProperty(LOGGER_LEVEL, Level.ERROR.name());
setRootLoggerLevel(Level.valueOf(level));
// 注册监听
apolloConfig.addChangeListener(this);
}
public void onChange(ConfigChangeEvent changeEvent) {if (changeEvent.changedKeys().contains(LOGGER_LEVEL)) {String newValue = changeEvent.getChange(LOGGER_LEVEL).getNewValue();
try {setRootLoggerLevel(Level.valueOf(newValue));
} catch (Exception e) {log.error("loggerLevel onChange error", e);
}
}
if (changeEvent.changedKeys().contains(LOGGER_LEVEL_DETAIL)) {String newValue = changeEvent.getChange(LOGGER_LEVEL_DETAIL).getNewValue();
try {parseLoggerConfig(newValue);
} catch (Exception e) {log.error("loggerLevel detail onChange error", e);
}
}
}
初始化时即从 apollo config 获取以后全局日志级别及部分日志级别,其次在注册监听器,此时只须要在 apollo 配置界面设置如上 key,则程序会立刻收到更新并从新设置相应的日志级别。
本文所有源码都放在了 github 仓库:https://github.com/jifuwei/dynamic-logger-util,可随时查看 / 索取 / 应用, 有问题随时发问。
总结
通过 xml 批改日志级别去追究 api 办法,找到可用的办法后再去设计如何触发办法调用。依照这一思路,就解决了动静调整日志级别的问题。在生产产生大量异样,可对日志进行降级,不至于 I/O 升高导致 CPU 爆满,从而导致用户体验卡顿问题。
如果你感觉自己分享的内容够“干”,麻烦点赞、关注、转发,这是对我最大激励,感激反对!
心愿我分享的文章可能给每一位读者带来帮忙!
往期精彩
集体技术博客:https://jifuwei.github.io/
- 性能调优——小小的 log 大大的坑
- 性能优化必备——火焰图
- Flink 在风控场景实时特色落地实战