李锋镝的博客

  • 首页
  • 时间轴
  • 说说
  • 读书观影
  • 系列
  • AI工具集
  • 论坛
  • 友圈
    • 友链
    • 朋友圈
    • 游客中心
  • 留言
    • 留言
    • 走心评论
  • 关于
    • 关于本站
    • Now
    • 每日心情
    • 网站地图
    • 网站统计
    • 赞助
  • 星球穿梭
  • 🚇开往
静亦定 动亦定
惟坚韧者始能遂其志
  1. 首页
  2. 原创
  3. 正文

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

2021年12月29日 257 0 0
本文最后更新于 2021年12月29日,距今已 1777 天,其中的信息可能已经发生变化,请注意甄别。

背景

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

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

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
分享到

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

也可使用浏览器菜单中的「分享」功能

微信扫一扫分享

标签: Apollo JAVA log logback SpringBoot 分布式
最后更新:2021年12月29日

岁月同一天 11 月 10 日

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

  • AI
    1 年前 2025年11月10日
    一文吃透 AI 核心技术栈:AIGC、RAG、Function Call、Agent、MCP 关系与演进全解析

    AI 领域的热词层出不穷,AIGC、RAG、Function Call、Agent、MCP 常常被混为一谈——很多人知道…

  • 后端
    1 年前 2025年11月10日
    Wails 深度实战指南:用 Go + 前端技术打造轻量桌面应用(从入门到部署)

    提起桌面应用开发,Electron 无疑是最流行的选择,但它“内存占用高、打包体积大”的痛点让很多开发者望而却步。有没有…

相关文章
  • 动态线程池框架DynamicTp使用以及架构设计2025年4月29日
  • Spring WebFlux深度解析:异步非阻塞架构与实战落地指南2025年12月15日
  • 为什么同样是分布式架构的Kafka需要Leader而Redis不需要?2021年3月22日
  • 分布式服务生成唯一不重复ID(24位字符串)2019年7月13日
  • 几款Java开发者必备常用的工具,准点下班不在话下2021年2月19日

李锋镝

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

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

文章评论

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

办公室正在讨论老婆和朋友哪个更重要,一男同事非常义气地大声说道:当然朋友重要,朋友如手足,手足能断吗?女人如衣服,衣服可以换!巧的是,其女友刚好来探班,正好站在门口和他碰个对眼。那男同事立马转向:今时不同往日,如今大街上只会看见断手断脚的,有没有见过没穿衣服的。

听点儿音乐吧 朋友~
文章目录
最新 热点 随机
最新 热点 随机
给服务器加了WAF 又一个逆天改命的人:小小徐 AI的生成能力等于生产力吗? NGINX严重漏洞(CVE-2026-15176、CVE-2026-93479、CVE-2026-97205) Kratos-plus v1.1.27更新说明 php2shell远程执行漏洞修复(CVE-2026-109618 和 CVE-2026-109619)
给主题增加了游客中心功能心气没了,干什么都不得劲给服务器加了WAF我觉得是时候公布我的导航网站了游本昌去世关于主题加载速度优化的一点儿小演进
终于有人把Elasticsearch原理讲透了! 办理居住证困难重重啊! Java数据类型判断工具类DataTypeUtil @Valid 和 @Validated 的区别 解决kubectl exec -it xxxx-service-bfbd45bb9-ktvzj bash -n bit error: exec [POD] [COMMAND] is not supported anymore. Use exec [POD] -- [COMMAND] instead See 'kubectl exec -h' for help and examples 结合Apollo配置中心实现日志级别动态配置
最近评论
李锋镝 发布于 1 天前(11月09日) 我也刷到了,说是恶意引流的
皮皮社长 发布于 1 天前(11月09日) 我刷到的,怎么是说剧本骗流量的?如果真的是用来骗流量,那真恶心。欺骗网友感情。
李锋镝 发布于 1 天前(11月09日) 现在我这个是十个,其实也够用了
老张博客 发布于 1 天前(11月09日) 雷池唯一不好的就是只能免费5个网站。
fairy 发布于 4 天前(11月06日) 机器人快来我这里吧!
标签聚合
AI 多线程 分布式 AI编程 MySQL Spring 数据库 K8s WordPress JVM Theme JAVA SpringBoot SQL IDEA Claude 日常 Redis 架构 ElasticSearch
友情链接
  • Honesty
  • lijie blog
  • 韩情脉脉
  • 瓦匠个人小站
  • 韩小韩博客
  • Mr.Sun的博客
  • 哥斯拉
  • Serendipity
  • 知向前端
  • 搬砖日记
  • 皮皮社
  • 志文工作室
  • 懋和道人
  • 刘永博客
  • sssr7844的博客
  • 老张博客
  • 九仞之行
  • 林羽凡
  • 彬红茶日记

COPYRIGHT © 2016-2026 lifengdi.com. ALL RIGHTS RESERVED.

lifengdi.com 十年之约虫洞

Domain age badge for lifengdi.com

Theme Kratos-plus By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号