动态刷新日志级别

本文介绍了如何借助Apollo配置中心,在不重启线上环境的情况下,动态调整SpringBoot应用的日志级别。当线上出现问题时,无需修改配置文件和重新部署,而是直接在Apollo中更改日志级别,以便于快速定位和解决问题。文章详细阐述了日志级别、SpringBoot的日志系统以及如何实现监听Apollo配置变化来更新日志级别。
摘要由CSDN通过智能技术生成

概述

日志模块是每个项目中必须的,用来记录程序运行中的相关信息。一般在开发环境下使用DEBUG级别的日志输出,为了方便查看问题,而在线上一般都使用INFO级别的日志,主要记录业务操作的日志。那么问题来了,当线上环境出现问题希望输出DEBUG日志信息辅助排查的时候怎么办呢?修改配置文件,重新打包然后上传重启线上环境,但是这么做不优雅 而且可能会破坏现场。

本文介绍一种实现方案:通过Apollo配置中心来实现 动态调整线上日志级别。

日志级别

不同的日志框架支持不同的日志级别,其中比较常见的就是Log4j和Logback。

在Log4j中支持8种日志级别,优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。

Logback中支持7种日志级别,优先级从高到低分别是:OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL。

可以看到常见的ERROR、WARN、INFO、DEBUG,这两者都是支持的。

所谓设置日志的输出级别表示的是输出的日志的最低级别,也就是说,如果我们把级别设置成INFO,那么包括INFO在内以及比INFO优先级高的级别的日志都可以输出。

Spring Boot对日志的支持

Spring Boot 对log做了统一封装,代码在 org.springframework.boot.logging 包中,结构如下:
在这里插入图片描述

其中 org.springframework.boot.logging.LoggingSystem 是SpringBoot对日志系统的抽象,是一个顶层的抽象类,有很多具体的实现:
在这里插入图片描述

通过上图,我们可以发现目前SpringBoot目前支持3种类型的日志,分别是

  1. JDK内置的Log(JavaLoggingSystem)
  2. Log4j2(Log4J2LoggingSystem)
  3. Logback(LogbackLoggingSystem)。
static {
		Map<String, String> systems = new LinkedHashMap<>();
		systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
		systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
				"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
		systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
		SYSTEMS = Collections.unmodifiableMap(systems);
	}

LoggingSystem是个抽象类,内部有这几个方法:

在这里插入图片描述

  1. beforeInitialize方法:日志系统初始化之前需要处理的事情。抽象方法,不同的日志架构进行不同的处理
  2. initialize方法:初始化日志系统。默认不进行任何处理,需子类进行初始化工作
  3. cleanUp方法:日志系统的清除工作。默认不进行任何处理,需子类进行清除工作
  4. getShutdownHandler方法:返回一个Runnable用于当jvm退出的时候处理日志系统关闭后需要进行的操作,默认返回null,也就是什么都不做
  5. getSupportedLogLevels: 返回日志系统实际支持的一组 LogLevel。
  6. setLogLevel方法:抽象方法,用于设置对应logger的级别
  7. get方法:检测并返回正在使用的日志系统。支持 Logback 和 Java 日志记录。

代码

我们可以将日志级别配置保存在Apollo配置中心中, 当日志级别发生变更时,我们需要通过监听该配置的变更,设置应用中的 Logger 的日志级别,从而后续的日志打印可以根据新的日志级别

@Slf4j
@Component
public class LoggingSystemAdjustListener {

    /**
     * 日志配置项的前缀
     */
    private static final String LOGGER_PREFIX = "logging.level.";

    @Resource
    private LoggingSystem loggingSystem;

    // By default only read config in "application"
    @ApolloConfigChangeListener
    public void onChange(ConfigChangeEvent changeEvent) throws Exception {
        // <Y> 遍历配置集的每个配置项,判断是否是 logging.level 配置项
        for (String key : changeEvent.changedKeys()) {
            // 如果是 logging.level 配置项,则设置其对应的日志级别
            if (key.startsWith(LOGGER_PREFIX)) {
                String loggerName = key.replace(LOGGER_PREFIX, "");
                //
                LoggerConfiguration cfg = loggingSystem.getLoggerConfiguration(loggerName);
                if (cfg == null) {
                    if (log.isErrorEnabled()) {
                        log.error("no loggerConfiguration with loggerName:{}", loggerName);
                    }
                    continue;
                }

                // 获得日志级别
                ConfigChange change = changeEvent.getChange(key);
                // the newLevel could be null if the config is deleted from apollo
                // in this case we update it same as "root" level
                String newLevel = change.getNewValue();

                LogLevel level = null;
                // config is deleted or kept as empty string
                if (newLevel == null || newLevel.isEmpty()) {
                    level = getFallbackLogLevel(ROOT_LOGGER_NAME);
                } else {
                    try {
                        level = LogLevel.valueOf(newLevel.toUpperCase());
                    } catch (IllegalArgumentException e) {
                        // do nothing
                    }
                }

                if (level == null) {
                    if (log.isErrorEnabled()) {
                        log.error("logger:[{}]current LogLevel is invalid:{}", loggerName, newLevel);
                    }
                    continue;
                }

                if (!isSupportLevel(level)) {
                    if (log.isErrorEnabled()) {
                        log.error("LoggingSystem:[] not support current LogLevel:{}",
                            loggingSystem.getClass().getName(), newLevel);
                    }
                    continue;
                }

                if (log.isInfoEnabled()) {
                    log.info("logger:[{}] current effective level:{}, to be changed to level:{}", loggerName,
                        cfg.getEffectiveLevel(), newLevel);
                }

                // 基于springboot的日志抽象类,设置日志级别到 LoggingSystem 中
                loggingSystem.setLogLevel(loggerName, level);
            }
        }
    }

    private boolean isSupportLevel(LogLevel level) {
        for (LogLevel ll : loggingSystem.getSupportedLogLevels()) {
            if (ll == level) {
                return true;
            }
        }
        return false;
    }

    public LogLevel getFallbackLogLevel(String loggerName) {
        LoggerConfiguration cfg = loggingSystem.getLoggerConfiguration(loggerName);
        if (cfg == null) {
            if (log.isErrorEnabled()) {
                log.error("no loggerConfiguration with loggerName:{}", loggerName);
            }
            // use WARN as unexpected case
            return LogLevel.WARN;
        }

        return cfg.getEffectiveLevel();
    }

}

基于spring的日志支持,我们还可以在logback中通过logger标签对某一个包甚至类单独配置日志级别

<logger name="com.ethan.demo.log.controller" level="INFO" additivity="true">
    <appender-ref ref="${CONSOLE_APPENDER}"/>
  </logger>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值