动态调整日志级别思路&实现

标签: 日志 | 发表时间:2022-09-08 17:32 | 作者:是咕咕鸡
出处:https://juejin.cn/backend

引言

上篇文章 性能调优——小小的 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/

公众号:是咕咕鸡

相关 [日志] 推荐:

日志管理

- - CSDN博客系统运维推荐文章
#很关键 [root@client01 ~]# ls /var/log/ anaconda.ifcfg.log. tallylog #关键日志,大部分记录在里面 [root@client01 ~]# ls /var/log/messages /var/log/messages. [root@client01 ~]# ps -ef|grep log #系统日志服务 root.

日志优化

- - 互联网 - ITeye博客
在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索. 绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢. 今天就让我们来聊聊Java日志性能那些事. 说到Java日志,大家肯定都会说要选择合理的日志级别、合理控制日志内容,但是这仅是万里长征第一步……哪怕一些 DEBUG级别的日志在生产环境中不会输出到文件中,也可能带来不小的开销.

nginx日志切割

- - haohtml's blog
nginx的日志文件没有rotate功能. 如果你不处理,日志文件将变得越来越大,还好我们可以写一个nginx日志切割脚本来自动切割日志文件. 第一步就是重命名日志文件,不用担心重命名后nginx找不到日志文件而丢失日志. 在你未重新打开原名字的日志文件前,nginx还是会向你重命名的文件写日志,linux是靠文件描述符而不是文件名定位文件.

flume日志采集

- - CSDN博客推荐文章
1.1.2.  Client端Log4j配置文件. (黄色文字为需要配置的内容). //日志Appender修改为flume提供的Log4jAppender. //日志需要发送到的端口号,该端口要有ARVO类型的source在监听. //日志需要发送到的主机ip,该主机运行着ARVO类型的source.

Flume日志收集

- - 企业架构 - ITeye博客
转: http://www.cnblogs.com/oubo/archive/2012/05/25/2517751.html. Flume是一个分布式、可靠、和高可用的海量日志聚合的系统,支持在系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力.

GC 日志分析

- - 码蜂笔记
不同的JVM及其选项会输出不同的日志. 生成下面日志使用的选项: -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:d:/GClogs/tomcat6-gc.log. 最前面的数字 4.231 和 4.445 代表虚拟机启动以来的秒数.

Nginx 日志滚动

- - Linux - 操作系统 - ITeye博客
Nginx 日志滚动配置. 在linux下配置日志滚动一般都用系统自带的logrotate,但是在之前的使用中发现,如果一个daemon只打开一个日志文件写日志,在logroate的配置文件中使用copytruncate,会有少部分日志丢失. 在对日志要求不是特别严格的情况下这是可行的,但有时候这丢失的几行日志很重要,就需要一个比较严格的日志滚动方法.

分布式日志

- - Java - 编程语言 - ITeye博客
最近完成一个简单的日志管理系统,拿出来跟大家分享一下. 3、支持文件输出、habse输出、mongodb输出. 基于以上三点功能,我们下面详细说明. 说道支持这个功能,有个同事认为没有这个必要,他的观点是log4j的配置不需要经常变动,不需要支持这样的功能;本人的观点是“配置可以进行统一管理、而且正式机跟测试机的log4j的配置肯定会有一些差异的”,因此这个功能是必须的.

nginx cronolog日志分割

- 伟伟 - 高进波博客 - 零起点一步配置你的linux服务器,linux博客,linux教程,web架构 [expanded by feedex.net]
八月 3rd, 2011 Posted in Nginx | 阅读次数: 8 次. nginx cronolog日志分割配置文档,根据下面方法,每小时分割一次NGINX访问日志. cronolog必须在nginx启动前启动,如果是ubuntu,可以将cronolog配置那行写到/etc/init.d/nginx start函数那.

jdbc日志,Log4jbdc-remix使用

- - 企业架构 - ITeye博客
   做项目的时候给jdbc做日志,只打印出代. 由是看看有没有给jdbc做日志的框架. 找到了log4jdbc,但是觉得还不是很理想,由是又看了看,还有一个护展插件log4jdbc-remix. 先不扯这么多,先来教大家怎么使用吧. 使用这个插件依赖4个类:log4j,slf4j-log4j,slf4j-api,log4j-remix.