李锋镝的博客 - LiFengdi.Com

  • 首页
  • 时间轴
  • 留言
  • 左邻右舍
  • 我的日常
  • 关于我
青衿之志 履践致远
霁月光风 不萦于怀
  1. 首页
  2. 原创
  3. 正文

结合Apollo配置中心实现日志级别动态配置

2021年12月29日 7407点热度 0人点赞 0条评论

背景

目前常用的实现动态配置日志级别的应该非SpringBoot的spring-boot-starter-actuator莫属了。

不过通过spring-boot-starter-actuator配置的日志级别,服务一旦重启就会恢复原状。且只能通过访问指定的接口来修改单个实例的日志级别(SpringBootAdmin也是一样,只能修改单个实例的)。如果是想修改某个服务所有实例的日志级别,只能修改配置文件,然后重启服务,可以说局限性稍微大点儿。

由于重启服务太费劲儿,所以想到了利用Apollo配置中心来动态修改日志级别。

实现

大体思路是通过Apollo的监听机制,结合Spring的事件监听,来刷新日志级别。

具体代码如下:
LogLevelRefreshEvent:自定义Spring事件。

import lombok.ToString;
import org.springframework.context.ApplicationEvent;

/**
 * @author lifengdi
 * @date 2021/12/24 11:29
 */
@ToString
public class LogLevelRefreshEvent extends ApplicationEvent {

    /**
     * key
     */
    private final String key;

    /**
     * 旧值
     */
    private final Object oldValue;

    /**
     * 新值
     */
    private final Object newValue;

    public LogLevelRefreshEvent(String key, Object oldValue, Object newValue) {
        super(key);
        this.key = key;
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    public String getKey() {
        return key;
    }

    public Object getOldValue() {
        return oldValue;
    }

    public Object getNewValue() {
        return newValue;
    }
}

LogConfig:日志级别配置文件,用于获取Apollo配置以及转换日志级别。

import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import lombok.Getter;
import org.springframework.boot.logging.LogLevel;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author lifengdi
 * @date 2021/12/24 14:29
 */
@Configuration
@Getter
public class LogConfig {

    /**
     * 日志级别配置key
     */
    public static final String LOG_LEVEL_CONFIG_KEY = "logLevel.list";

    /**
     * 日志级别配置,格式:
     * <code>
     * {
     *     "com.cowell.conveyor": "warn",
     *     "com.cowell.tools": "warn"
     * }
     * </code>
     */
    @ApolloJsonValue("${logLevel.list:{}}")
    private Map<String, String> logLevelConfig;

    /**
     * 日志级别映射
     */
    private final Map<String, LogLevel> levelMap = new HashMap<String, LogLevel>() {
        {
            put("trace", LogLevel.TRACE);
            put("debug", LogLevel.DEBUG);
            put("info", LogLevel.INFO);
            put("warn", LogLevel.WARN);
            put("error", LogLevel.ERROR);
            put("fatal", LogLevel.FATAL);
            put("off", LogLevel.OFF);
        }
    };

    public Map<String, LogLevel> getLevelMap() {
        return levelMap;
    }

    /**
     * 根据名称获取日志级别,默认返回debug级别。
     * @param logLevel 级别名称
     * @return 日志级别 {@link LogLevel}
     */
    public LogLevel getFromLevelMap(String logLevel) {
        return levelMap.getOrDefault(logLevel, LogLevel.DEBUG);
    }
}

LogLevelUtils:工具类

import com.lifengdi.config.LogConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.logging.LoggerConfiguration;
import org.springframework.boot.logging.LoggingSystem;

import java.util.List;
import java.util.Map;

/**
 * @author lifengdi
 * @date 2021/12/27 17:44
 */
public class LogLevelUtils {

    private static final Logger logger = LoggerFactory.getLogger(LogLevelUtils.class);

    /**
     * 动态刷新日志级别
     * @param loggingSystem LoggingSystem
     * @param logConfig LogConfig
     */
    public static void refreshLogLevel(LoggingSystem loggingSystem, LogConfig logConfig) {
        Map<String, String> logLevelConfig = logConfig.getLogLevelConfig();
        List<LoggerConfiguration> loggerConfigurations = loggingSystem.getLoggerConfigurations();
        if (!logLevelConfig.isEmpty()) {
            logLevelConfig.forEach((loggerName, value) -> {
                String logLevel = value.toLowerCase();
                for (LoggerConfiguration loggerConfiguration : loggerConfigurations) {
                    if (loggerConfiguration.getName().startsWith(loggerName)) {
                        loggingSystem.setLogLevel(loggerName, logConfig.getFromLevelMap(logLevel));
                        logger.debug("LogLevelUtils|RefreshLogLevel|SUCCESS|loggerName:{},logLevel:{}", loggerName, logLevel);
                    }
                }
            });
        }
    }
}

日志级别刷新Listener:

import com.lifengdi.config.LogConfig;
import com.lifengdi.util.LogLevelUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 日志级别刷新Listener
 *
 * @author lifengdi
 * @date 2021/12/24 11:34
 */
@Component
@Slf4j
public class LogLevelRefreshListener implements ApplicationListener<LogLevelRefreshEvent> {

    @Autowired
    private LoggingSystem loggingSystem;

    @Autowired
    private LogConfig logConfig;

    @Override
    public void onApplicationEvent(LogLevelRefreshEvent logLevelRefreshEvent) {
        if (LogConfig.LOG_LEVEL_CONFIG_KEY.equals(logLevelRefreshEvent.getKey())) {
            log.info("LogLevelRefreshListener|onApplicationEvent|refreshLogLevel|configRefreshEvent:{}", logLevelRefreshEvent);
            LogLevelUtils.refreshLogLevel(loggingSystem, logConfig);
            log.info("LogLevelRefreshListener|onApplicationEvent|refreshLogLevel|SUCCESS|configRefreshEvent:{}", logLevelRefreshEvent);
        }
    }

}

服务启动时日志级别刷新Listener:

import com.lifengdi.config.LogConfig;
import com.lifengdi.util.LogLevelUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 服务启动时日志级别刷新Listener
 *
 * @author lifengdi
 * @date 2021/12/24 13:59
 */
@Component
public class LogLevelApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    private LoggingSystem loggingSystem;

    @Autowired
    private LogConfig logConfig;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {

        LogLevelUtils.refreshLogLevel(loggingSystem, logConfig);

    }
}

增加事件发布:

@Component
public class ApolloAutoRefreshBean {

    private final Logger logger = LoggerFactory.getLogger(ApolloAutoRefreshBean.class);

    @Autowired
    private ApplicationContext applicationContext;

    @ApolloConfigChangeListener
    public void onChange(ConfigChangeEvent changeEvent) {
        logger.info("ApolloAutoRefreshBean|onChange|apollo触发变更|param:namespace={}", changeEvent.getNamespace());
        for (String key : changeEvent.changedKeys()) {
            ConfigChange change = changeEvent.getChange(key);
            logger.info("ApolloAutoRefreshBean|onChange|apollo数据key-value发生变更|key: {}, oldValue: {}, newValue: {}, changeType: {}",
                    change.getPropertyName(), change.getOldValue(), change.getNewValue(),
                    change.getChangeType());
            applicationContext.publishEvent(new LogLevelRefreshEvent(key, change.getOldValue(), change.getNewValue()));
        }
        applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
    }
}

配置中心配置的参数格式如下:

{
    "com.lifengdi.util": "info",
    "com.lifengdi.tools": "warn"
}

至此,动态配置日志级别算是搞定了。

除非注明,否则均为李锋镝的博客 - LiFengdi.Com原创文章,转载必须以链接形式标明本文链接
本文链接:https://www.lifengdi.com/archives/article/3777
本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: Apollo JAVA log logback SpringBoot 分布式
最后更新:2021年12月29日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >
guest
您的姓名(必填)
您的邮箱(必填)
您的站点
guest
您的姓名(必填)
您的邮箱(必填)
您的站点
0 评论
Inline Feedbacks
查看所有评论
支付宝红包

露从今夜白,月是故乡明。

最新 热点 随机
最新 热点 随机
回忆是一条没有尽头的路 这样的日子什么时候才是个头 MySQL 中的 distinct 和 group by 哪个效率更高? 开工啦~ 今晚,回家过年! 图数据库选型:Neo4j、Janus、HugeGraph
看病难~取药难~~阳了...开工啦~RocketMQ的push消费方式实现详解国庆节过的也很累~~MybatisCodeHelperPro激活
博客有logo啦 好多小阳人呀 关于Elasticsearch文档的描述以及如何操作文档的详细总结 九种常用的UML图总结 我是如何失去团队掌控的? 北京的秋天
最近评论
李锋镝 发布于 2 周前(03月10日) 已添加~欢迎回访喔
博客录(boke.lu) 发布于 2 周前(03月10日) 已添加贵站0.0 名称:博客录(boke.lu) 简介:boke.lu · 博客收录展示平台~ 链接...
李锋镝 发布于 3 周前(03月05日) 系统版本是win11吗?
HJQ 发布于 4 周前(02月28日) 同问,注册表都没有楼主说的值。
林羽凡 发布于 1 个月前(02月16日) 开工大吉。
有情链接
  • 志文工作室
  • 临窗旋墨
  • 旧时繁华
  • 城南旧事
  • 强仔博客
  • 林三随笔
  • 徐艺扬的博客
  • 云辰博客
  • 韩小韩博客
  • 知向前端
  • 阿誉的博客
  • 林羽凡
  • 情侣头像
  • 哥斯拉
  • 博客录

COPYRIGHT © 2022 lifengdi.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

豫ICP备16004681号-2