集成apollo动态日志,“消灭”logback-spring.xml

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 动态调整线上日志级别是一个非常常见的场景,借助apollo这种配置中心组件非常容易实现。作为apollo的官方技术支持,博主经常在技术群看到有使用者询问apollo是否可以托管logback的配置文件,毕竟有了配置中心后,消灭所有的本地配置全部交给apollo管理是我们的最终目标。可是,apollo不具备直接托管logback-spring.xml配置文件能力,但是,我们可以基于spring和logback的装载机制,完全取缔logback-spring.xml配置,以apollo中的配置驱动。而且,改造后,大大提高了日志系统的灵活性和可扩展性。

前言

动态调整线上日志级别是一个非常常见的场景,借助apollo这种配置中心组件非常容易实现。作为apollo的官方技术支持,博主经常在技术群看到有使用者询问apollo是否可以托管logback的配置文件,毕竟有了配置中心后,消灭所有的本地配置全部交给apollo管理是我们的最终目标。可是,apollo不具备直接托管logback-spring.xml配置文件能力,但是,我们可以基于spring和logback的装载机制,完全取缔logback-spring.xml配置,以apollo中的配置驱动。而且,改造后,大大提高了日志系统的灵活性和可扩展性。

apollo动态日志

何为apollo动态日志?直接这样说可能会有歧义,以为是apollo里的日志,其实不然。举个简单的例子,比如,我们项目很多地方使用了log.debug()打印日志,为了方便通过日志信息排查问题,但是一般情况下,生产环境的日志级别会配置成info。只有遇到需要排查线上问题的时候才会临时打开debug级别日志。这个时候只能需改配置文件,将日志级别调整成debug,然后重新打包部署验证。不仅流程繁琐耗时,还会破坏当时的"案发现场的环境",导致判断不准确。如果应用具备了apollo动态日志这种能力,就只需在apollo修改下配置然后提交,就可以热更新日志级别,马上打印debug级别日志。这就是所谓的apollo动态日志。实现这个效果,需要具备两个能力,分别由spring和apollo提供

spring日志系统热更新日志级别

spring应用中,spring适配了主流的日志框架,如logback、log4j2等,在这些日志框架之上,又抽象了自己的日志系统服务,这里我们用到了spring的LoggingSystem,用它来热更新日志级别,这个类在日志系统初始化时就添加到了spring的容器中,所以只要在spring的上下文管理范围内,就可以直接注入,以下为主要使用到的api描述:

    /**
     * 设置给定日志记录器的日志级别.
     * @param loggerName 要设置的日志记录器的名称({@code null}可用于根日志记录器)。
     * @param level 日志级别
     */
    public void setLogLevel(String loggerName, LogLevel level) {
        throw new UnsupportedOperationException("Unable to set log level");
    }

apollo日志配置变更动态下发

apollo作为分布式配置中心,配置集中管理和配置热更新是其最核心的功能,此外,apollo还提供了配置变更下发监听的功能。基于这个配置监听的设计,实现动态日志就变得非常简单了。而且不仅可以实现日志动态热更,基于这个思路,连接池、数据源等都可以轻松实现。apollo实现监听配置变更有多种方式,可以通过Config实例手动添加,如:

    @ApolloConfig
    public Config config;
    
    public void addConfigChangeListener(){
        config.addChangeListener(changeEvent->{
            System.out.println("config change keys" + changeEvent.changedKeys());
        });
    }

也可以通过注解直接驱动

    @ApolloConfigChangeListener
    public void addConfigChangeListener(ConfigChangeEvent changeEvent){
            System.out.println("config change keys" + changeEvent.changedKeys());
    }

实现日志调整热更新

有了上述能力,在结合spring支持的日志加载配置方式,如:

logging.level.org.springframework.web=debug
logging.level.org.hibernate=error

可以实现如下代码完成功能,遇到需要调整日志级别时,修改apollo里的配置,即可实时生效

@Configuration
public class LogbackConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(LoggerConfiguration.class);
    private static final String LOGGER_TAG = "logging.level.";
    private final LoggingSystem loggingSystem;
    public LogbackConfiguration(LoggingSystem loggingSystem) {
        this.loggingSystem = loggingSystem;
    }

    @ApolloConfigChangeListener
    private void onChange(ConfigChangeEvent changeEvent) {
        for (String key : changeEvent.changedKeys()) {
            if (this.containsIgnoreCase(key, LOGGER_TAG)) {
                String strLevel = changeEvent.getChange(key).getNewValue();
                LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
                loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level);
                logger.info("logging changed: {},oldValue:{},newValue:{}", key, changeEvent.getChange(key).getOldValue(), strLevel);
            }
        }
    }
    
    private boolean containsIgnoreCase(String str, String searchStr) {
        if (str == null || searchStr == null) {
            return false;
        }
        int len = searchStr.length();
        int max = str.length() - len;
        for (int i = 0; i <= max; i++) {
            if (str.regionMatches(true, i, searchStr, 0, len)) {
                return true;
            }
        }
        return false;
    }
}

消灭logback-spring.xml配置

在"消灭"logback-xml配置之前,先看下这个配置文件有哪些配置信息,起到了哪些作用,下面贴出一个典型的配置文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
  <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
  <appender name="Sentry" class="io.sentry.logback.SentryAppender">
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
      <level>ERROR</level>
    </filter>
  </appender>
  <root level="INFO">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="Sentry"/>
  </root>
  <logger name="org.apache.ibatis.session" level="WARN"/>
  <springProfile name="dev">
    <logger name="com.taptap.server" level="DEBUG"/>
    <logger name="com.taptap.commons" level="DEBUG"/>
  </springProfile>
  <springProfile name="prod">
    <logger name="com.taptap.server" level="WARN"/>
    <logger name="com.taptap.commons" level="WARN"/>
  </springProfile>
</configuration>

一个典型的logback配置文件里包含了Appender和日志级别设置的信息,Appender可以理解为日志的输出源。如上贴出的这个配置,添加了两个Appender信息,一个是spring中内置的,将日志输出到控制台的Appender。一个是将error日志信息发送到Sentry应用监控平台的Appender。其他的配置描述了每个包路径不同的日志级别信息。到这里,我们很容易想到,上文已经说过,spring已经支持以logging.level.包名=info这种配置来设置日志系统的日志级别。那么剩下的只要解决Appender的配置就ok了。在这里,其实只需要解决SentryAppender的加载就行,因为consoleAppender spring自己会处理。有了目标和方向,就好办了。以logback-spring.xml配置的信息,最终都会加载成class对象。就和spring.xml配置一样。所以研究的方向就变成了Logback的加载原理的问题。

Logback加载原理

在java的日志生态里,除了响当当的logback、log4j2、apache common log外,还有一个日志框架不得不提,就是sl4j。正因为java生态强大,日志框架层出不穷,所以sl4j出来了,不干实事,专门定义日志标准、规范定义接口。而且,在我们平时的编码过程中,也建议使用sl4j的api,这样,无论底层日志框架实现怎么切换,都不会影响。主流的日志框架都有实现sl4j的接口,spring中日志系统的加载也是面向的sl4j,而不是直接面向日志实现,加载过程是一个自动化的过程,系统会自动扫描实现了sl4j的接口实现,如:

public interface ILoggerFactory {
    public Logger getLogger(String name);
}

每个日志框架都会实现这个接口,如Logback中的LoggerContext。Logback所有的功能都集成在了这个Context中,logback-spring.xml的配置也是为了配置LoggerContext中的属性信息,所有我们只要拿到了LoggerContext实例,问题就解决了一大半。这涉及到sl4j的另一个接口,获取ILoggerFactory实例的接口:

public interface LoggerFactoryBinder {

    public ILoggerFactory getLoggerFactory();

    public String getLoggerFactoryClassStr();
}

Logback的实现类为StaticLoggerBinder,也就是说,我们可以通过StaticLoggerBinder的getLoggerFactory方法拿到LoggerContext实例了。

javaBean加载SentryAppender

拿到Logback的LoggerContext后,就好办了,见代码:

@Configuration
public class LogbackConfiguration {

    private final LoggerContext ctx = (LoggerContext) StaticLoggerBinder.getSingleton().getLoggerFactory();

    @Bean
    @Profile(PROD_ENV)
    public void initSenTry() {
        SentryAppender sentryAppender = new SentryAppender();
        sentryAppender.setContext(ctx);
        ThresholdFilter filter = new ThresholdFilter();
        filter.setLevel(Level.ERROR.levelStr);
        filter.start();
        sentryAppender.addFilter(filter);
        sentryAppender.start();
        ctx.addTurboFilter(new TurboFilter() {
            @Override
            public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, Level level, String format, Object[] params, Throwable t) {
                logger.addAppender(sentryAppender);
                return FilterReply.NEUTRAL;
            }
        });
    }
}

看到这种代码就非常有感觉了,配置文件中的xml其实就是描述了日志组成对象以及对象的属性。在使用java bean的方式配置时需要注意,Logback的设计里,每个日志系统组成实例都有一个start状态属性,上面的start()方法其实不是动作,只是标记了这个属性为true。而在xml里这个属性只要配置了就自动激活为true了,这里必须显示的start()一下。解决了日志级别配置和Appender配置后,Logback-spring.xml文件就可以彻底的删除了

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
3月前
|
消息中间件 存储 Java
手动实现 Spring Boot 日志链路追踪:提升调试效率的利器
【8月更文挑战第8天】在复杂的分布式系统中,日志是诊断问题、追踪系统行为的重要工具。然而,随着微服务架构的普及,服务间的调用链路错综复杂,传统的日志记录方式往往难以快速定位问题源头。今天,我们将探讨如何在不依赖外部组件(如Zipkin、Sleuth等)的情况下,手动实现Spring Boot应用的日志链路追踪,让日志定位更加便捷高效。
178 1
|
4月前
|
存储 SQL Java
Spring Boot使用slf4j进行日志记录
本节课主要对 slf4j 做了一个简单的介绍,并且对 Spring Boot 中如何使用 slf4j 输出日志做了详细的说明,着重分析了 logback.xml 文件中对日志相关信息的配置,包括日志的不同级别...
|
24天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
92 1
|
4月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
14943 29
|
3月前
|
数据采集 DataWorks 监控
DataWorks产品使用合集之数据集成任务日志中显示wait,是什么原因
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
3月前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
|
3月前
|
XML Java 数据库
"揭秘!Spring Boot日志链路追踪大法,让你的调试之路畅通无阻,效率飙升,问题无所遁形!"
【8月更文挑战第11天】在微服务架构中,请求可能跨越多个服务与组件,传统日志记录难以全局追踪问题。本文以电商系统为例,介绍如何手动实现Spring Boot应用的日志链路追踪。通过为每个请求生成唯一追踪ID并贯穿全链路,在服务间传递该ID,并在日志中记录,即使日志分散也能通过ID串联。提供了实现这一机制所需的关键代码片段,包括使用过滤器设置追踪ID、业务代码中的日志记录及Logback配置。此方案显著提升了问题定位的效率,适用于基于Spring Boot构建的微服务环境。
91 4
|
3月前
|
人工智能 Java API
JeecgBoot 低代码平台快速集成 Spring AI
Spring 通过 Spring AI 项目正式启用了 AI(人工智能)生成提示功能。本文将带你了解如何在 Jeecg Boot 应用中集成生成式 AI,以及 Spring AI 如何与模型互动,包含 RAG 功能。
125 3
|
3月前
|
人工智能 Java Spring
Spring框架下,如何让你的日志管理像‘AI’一样智能,提升开发效率的秘密武器!
【8月更文挑战第31天】日志管理在软件开发中至关重要,不仅能帮助开发者追踪问题和调试程序,还是系统监控和运维的重要工具。在Spring框架下,通过合理配置Logback等日志框架,可大幅提升日志管理效率。本文将介绍如何引入日志框架、配置日志级别、在代码中使用Logger,以及利用ELK等工具进行日志聚合和分析,帮助你构建高效、可靠的日志管理系统,为开发和运维提供支持。
66 0
|
4月前
|
JSON 缓存 Java
Spring Boot集成 Swagger2 展现在线接口文档
本节课详细分析了 Swagger 的优点,以及 Spring Boot 如何集成 Swagger2,包括配置,相关注解的讲解,涉及到了实体类和接口类,以及如何使用。最后通过页面测试,体验了 Swagger 的强大之处,基本上是每个项目组中必备的工具之一,所以要掌握该工具的使用,也不难。