动态调整日志级别思路&实现
引言
上篇文章 性能调优——小小的 log 大大的坑 已将详细的介绍了高并发下,不正确的使用日志姿势,可能会导致服务性能急剧下降问题。文末也给各位留下了解决方案——日志级别动态调整。
本文将详细介绍“动态日志”的实现原理及源码,希望各位能在今后的生产环境中应对日志问题能“得心应手”!
背景
日志的重要性不言而喻,是我们排查问题,解决 BUG 的重要手段之一,但是在高并发环境下,又会存在悖论:
大量打印日志,消耗 I/O,导致 CPU 占用率高;减少日志,性能是下来了,但是排查问题的链路断掉了。
痛点:一方面需要借助日志可快速排查问题,另一方面要兼顾性能,二者能否得兼?
那么本文的动态日志调整实现就是为了能解决这个痛点所构思开发的。
功能特性
- 低侵入,快速接入:以二方包(jar)的形式介入,只需要配置启用,对业务无感
- 及时响应,随调随改:应对研发不小心在大流量入口链路打印了大量 INFO 日志,能及时调整日志级别
- 阶梯配置支持:默认全局设置兜底,又可以支持局部 Logger 放/限流
- 人性化操作:与操作界面,方便修改
技术实现
如下,我将以 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/
公众号:是咕咕鸡
动态调整日志级别思路&实现的更多相关文章
- Log4cpp配置文件及动态调整日志级别的方法
一.log4cpp概述 Log4cpp是一个开源的C++类库,它提供了C++程序中使用日志和跟踪调试的功能,它的优点如下: 提供应用程序运行上下文,方便跟踪调试: 可扩展的.多种方式记录日志,包括命令 ...
- [C#] 将NLog输出到RichTextBox,并在运行时动态修改日志级别过滤
作者: zyl910 一.缘由 NLog是一个很好用的日志类库.利用它,可以很方便的将日志输出到 调试器.文件 等目标,还支持输出到窗体界面中的RichTextBox等目标. 而且它还支持在运行时修改 ...
- springboot动态修改日志级别+权限认证
1. springboot动态修改日志级别+权限认证 1.1. 需求 网上找到的动态修改日志级别的方式,基本都是没有权限验证的,或者特地关闭权限验证,但也没给出加上验证的解决方式 修改日志等级也是一个 ...
- SpringBoot系列十一:SpringBoot整合Restful架构(使用 RestTemplate 模版实现 Rest 服务调用、Swagger 集成、动态修改日志级别)
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot整合Restful架构 2.背景 Spring 与 Restful 整合才是微架构的核心,虽然在整 ...
- log4j2动态修改日志级别及拓展性使用
一.供参考的完整日志配置 <?xml version="1.0" encoding="UTF-8"?> <!-- 配置LoggerConfig ...
- Hadoop2动态调整Log级别-以datanode的heartbeat log为例
在Hadoop中,有些log信息在正常情况下是不打印出来的.比如datanode发送heartbeat的日志. 代码位于BPServiceActor#sendHeartBeat方法中,如下图: 由于默 ...
- springboot 1.5.x中的动态切换日志级别
logback是一套日志框架,由log4j的优化版,由同一个作者开发,在速度和性能上都超过其他日志框架,再结合slf4j,已成为当前最流行的日志框架. 一.springboot中使用logback s ...
- log4j2和logback动态修改日志级别工具类
工作中,在排查线上问题时,有以下场景在不重新部署或重启服务的情况下,需要动态调整线上日志级别 1.线上有些日志打印过多干扰有用的日志,需要动态修改线上日志记录器的打印日志级别,调高一些日志级别,打印出 ...
- Spring Boot动态修改日志级别
1. pom中引入 org.springframework.boot spring-boot-starter-actuator 2. 发送POST请求: 地址: http://[服务地址] ...
随机推荐
- WPF第三方控件,只能输入数字型数据
话不多说,根据最近项目需求,为了减少输入验证等相关代码量,需要此控件 先上效果图 默认样式是这样,自己可以根据需求修改外形,但我更喜欢它自带的简洁版 有人可能会问怎么实现的呢?其实很简单,我们设置它的 ...
- Linux远程连接工具和运行级别
常用的Linux远程连接工具: xshell MobaXterm windows的命令行工具 Linux的运行级别 linux有七个运行级别 0----所有的服务都不开启,代表的式关机 1---代表的 ...
- 一文看完vue3的变化之处
在通读了vue的官网文档后,我记录下了如下这些相对于2.x的变化之处. 1.创建应用实例的变化 之前一般是这样: let app = new Vue({ // ...一些选项 template: '' ...
- LVGL库入门教程 - 动画
动画可以说是 LVGL 中的特色之一,不过在使用动画前,请确保单片机具有足够的性能来维持足够的帧率. transition:过渡动画 当一个控件的状态发生改变时,可以让样式也发生变化以提醒用户.通过过 ...
- java运算符(超详细!!!)
java运算符 一.算数运算符 符号 含义 + 加法 - 减法 * 乘法 / 除法 % 余数 ++ 自增 -- 自减 这些是常用的算数运算符,在java基础阶段,掌握这些就可 加减乘除运算符 代码实例 ...
- P2575 高手过招 题解
题目描述 我们考虑如何把问题转换成博弈论来求解. 我们对于每一行之前都加上一个空格. 设原来这一行的空格个数是 \(C\) ,那么此时空格个数变成 \(C + 1\) . 然后按照从左到右的顺序给每一 ...
- labview从入门到出家9(进阶篇)--串口通讯
Labview在工控领域,如产线,实验室等环境用得较多,其中与仪器通讯控制的方式有串口(RS232,RS485,TTL),GPIB,网口,CAN等,其中串口在仪器还有单片机控制中用来调试居多.(很 ...
- ApiDay001 __02 Java_StringBuilder
Java 核心API StringBuilder String 类型的连接性能不好,Java提供了StringBuilder解决字符串连接性能问题. 简单理解 StringBuilder性能好!(重点 ...
- 零基础学Java(9)在mac上运行命令行提示"找不到或无法加载主类"
天坑 遇到的问题:使用命令行执行命令:java EightSample,会报以下错误 错误: 找不到或无法加载主类 EightSample 运行环境 mac系统 IntelliJ IDEA编译器 Ja ...
- Github隐藏使用技巧(超详解)
目录 github使用说明 查看别人的主页和项目 上传自己的项目 使用git下载github上的文件 使用git实现代码管理 使用git恢复被修改的文件 更多关于git使用小技巧 github使用说明 ...