李锋镝的博客

  • 首页
  • 时间轴
  • 评论区显眼包🔥
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
    • 赞助
  • 留言
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 原创
  3. 正文

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

2021年12月29日 253点热度 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"
}

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

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/article/3777

相关文章

  • SpringBoot 实现接口防刷的 5 种实现方案
  • SpringBoot基于redis的分布式锁的实现(源码)
  • SpringBoot常用注解
  • CompletableFuture使用详解
  • SpringBoot 中内置的 49 个常用工具类
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: Apollo JAVA log logback SpringBoot 分布式
最后更新:2021年12月29日

李锋镝

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

打赏 点赞
< 上一篇
下一篇 >

文章评论

1 2 3 4 5 6 7 8 9 11 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 46 47 48 49 50 51 52 53 54 55 57 58 60 61 62 63 64 65 66 67 69 72 74 76 77 78 79 80 81 82 85 86 87 90 92 93 94 95 96 97 98 99
取消回复
…

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

那年今日(06月16日)

  • 2012年:神舟九号飞船发射
  • 1977年:火箭专家冯·布劳恩去世
  • 1934年:资本资产定价模型的奠基者威廉·夏普出生
  • 1903年:亨利·福特创立福特汽车公司
  • 1858年:瑞典国王古斯塔夫五世出生
  • 更多历史事件
最新 热点 随机
最新 热点 随机
Claude-HUD 使用文档 Kratos+ —— Kratos 主题二次开发记录 译文:如何将单体应用拆解为微服务 codebase-memory-mcp 极简完整使用指南 Claude Haiku 4.5、Claude Sonnet 4.6、Claude Opus 4.7 区别以及各自的新特性 SchedulingConfigurer详解
AI时代,个人技术博客的出路在哪里?这个域名注册整整十年了,十年时间,真快啊WordPress实现用户评论等级排行榜插件WordPress网站换了个字体,差点儿把样式换崩了做了一个WordPress文章热力图插件千万级大表新增字段实战指南:告别锁表与业务中断
codebase-memory-mcp 极简完整使用指南 因在公司上不正经网站,我没过试用期… 深度解析 Kafka Rebalance:从原理到实战,彻底解决消息积压、重复与丢失 一篇文章帮你彻底搞清楚“I/O多路复用”和“异步I/O”的前世今生 OSI模型及代表协议详解 AI原生数据库新标杆:seekdb深度解析,轻量架构与混合搜索的双重革命
最近评论
博客集市 发布于 3 周前(05月26日) 博客组织申请友链 网站名称: 博客集市 网站地址: https://cnb.cool/Blog_...
李锋镝 发布于 4 周前(05月18日) 沙发-。-
皮皮社长 发布于 4 周前(05月17日) 不懂技术,打个水卡。破了这个0评论。 :18:
林羽凡 发布于 2 个月前(04月27日) 没毛病,主要是禁用描述,和可以做什么的描述,哪些命令能用,哪些不能用,在什么情况下问我之类的。
老张博客 发布于 2 个月前(04月02日) 这个真的是保姆级教程了。
标签聚合
MCP Spring 数据库 IDEA MySQL ElasticSearch JVM JAVA SpringBoot Redis K8s 分布式 AI编程 架构 SQL 设计模式 日常 WordPress AI 多线程
友情链接
  • Blogs·CN
  • Honesty
  • Mr.Sun的博客
  • 临窗旋墨
  • 哥斯拉
  • 彬红茶日记
  • 志文工作室
  • 懋和道人
  • 拾趣博客导航
  • 搬砖日记
  • 旧时繁华
  • 林羽凡
  • 瓦匠个人小站
  • 皮皮社
  • 知向前端
  • 蜗牛工作室
  • 韩小韩博客
  • 风渡言

COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

域名年龄

Theme Kratos+ By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号