李锋镝的博客

  • 首页
  • 时间轴
  • 说说
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 网站地图
    • 网站统计
    • 另一个网站
    • 我的导航站
    • 赞助
  • 留言
  • 走心评论
  • 系列文章
  • Now
  • 每日心情
  • 论坛
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 原创
  3. 正文

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

2021年12月29日 约 866 字3 分钟 33点热度 0人点赞 0条评论
本文最后更新于 2021年12月29日,距今已 1687 天,其中的信息可能已经发生变化,请注意甄别。

背景

目前常用的实现动态配置日志级别的应该非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日

岁月同一天 8 月 12 日

回望过去的今天,你在写什么

  • 6 年前 2020年8月12日
    jstat命令使用(JDK1.8)

    概述 jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下: jstat [-命令选项] [vm…

相关文章
  • 跨平台版本管理神器,开发者的环境配置救星:vfox2025年10月27日
  • SpringBoot框架自动配置之spring.factories和AutoConfiguration.imports2025年6月12日
  • Spring Boot 配置加载优先级总结2026年8月6日
  • Spring中的Aware接口2025年5月17日
  • Java中ArrayList为什么比LinkedList查询速度快?2020年5月16日

李锋镝

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

打赏 点赞
< 上一篇
下一篇 >
1234567891112131415161718192021222324252627282930313233343536373839404142434446474849505152535455575859606162636465666769727476777879808182858687909293949596979899
取消回复
…

文章评论

还没有评论,快来抢沙发吧~

不将沉重累坠的银元装在怀中,来自讨无谓的苦吃。

听点儿音乐吧 朋友~
文章目录
最新 热点 随机
最新 热点 随机
Kratos+ v1.1.16版本更新说明 WorkBuddy介绍 Kratos+ v1.1.14版本更新说明 Spring Boot 指定外部配置文件的方式 Spring Boot 配置加载优先级总结 Claude Fable 5(claude-fable-5)深度详解
给主题增加了Now、每日心情、年度回顾、岁月同一天、随机漫步等功能AI时代,个人技术博客的出路在哪里?增加了两套复古皮肤-牛皮纸、千禧网页这个域名注册整整十年了,十年时间,真快啊Kratos+ v1.1.14版本更新说明WordPress实现用户评论等级排行榜插件
使用shell脚本统一修改maven项目的版本 图数据库选型:Neo4j、Janus、HugeGraph 今晚,回家过年! ElasticSearch入门-基本概念介绍以及安装 SpringBoot 实现 RSA+AES 自动接口解密 Kafka常见面试题(一)
最近评论
李锋镝 发布于 2 天前(08月10日) 等我搞一个数据转换的插件~
李锋镝 发布于 2 天前(08月10日) 精美可担不起~😂
李锋镝 发布于 2 天前(08月10日) PHP是世界上最伟大的语言=。=
Hary 发布于 3 天前(08月09日) 我都想用了,但是是ty,转换有点麻烦
老张博客 发布于 4 天前(08月08日) 做的越来越精美了,好看。
标签聚合
Spring JAVA K8s SQL Claude Redis JVM 设计模式 分布式 MySQL IDEA 架构 ElasticSearch 日常 SpringBoot AI编程 AI 数据库 WordPress 多线程
友情链接
  • Serendipity
  • 老张博客
  • 韩小韩博客
  • Mr.Sun的博客
  • 九仞之行
  • 志文工作室
  • 风渡言
  • 知向前端
  • 拾趣博客导航
  • Honesty
  • 瓦匠个人小站
  • 旧时繁华
  • 彬红茶日记
  • 懋和道人
  • 临窗旋墨
  • 搬砖日记
  • 林羽凡
  • 哥斯拉
  • 韩情脉脉
  • 皮皮社

COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

正在博友圈履约中

域名年龄

Theme Kratos+ By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号