集成apollo动态⽇志取缔l配置
⽬录
前⾔
APOLLO动态⽇志
spring⽇志系统热更新⽇志级别
apollo⽇志配置变更动态下发
实现⽇志调整热更新
消灭LOGBACK-SPRING.XML配置
Logback加载原理
javaBean加载SentryAppender
前⾔
动态调整线上⽇志级别是⼀个⾮常常见的场景,借助apollo这种配置中⼼组件⾮常容易实现。作为apollo的官⽅技术⽀持,博主经常在技术看到有使⽤者询问apollo是否可以托管logback的配置⽂件,毕竟有了配置中⼼后,消灭所有的本地配置全部交给apollo管理是我们的最终⽬标。可是,apollo不具备直接托管l配置⽂件能⼒,但是,我们可以基于spring和logback的装载机制,完全取缔l配置,以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->{
log4j2不打印日志System.out.println("config change keys" + changeEvent.changedKeys());
});
}
也可以通过注解直接驱动
@ApolloConfigChangeListener
public void addConfigChangeListener(ConfigChangeEvent changeEvent){
System.out.println("config change keys" + changeEvent.changedKeys());
}
实现⽇志调整热更新
有了上述能⼒,在结合spring⽀持的⽇志加载配置⽅式,如:
springframework.web=debug
hibernate=error
可以实现如下代码完成功能,遇到需要调整⽇志级别时,修改apollo⾥的配置,即可实时⽣效
@Configuration
public class LogbackConfiguration {
private static final Logger logger = Logger(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 (ainsIgnoreCase(key, LOGGER_TAG)) {
String strLevel = Change(key).getNewValue();
LogLevel level = LogLevel.UpperCase());
loggingSystem.place(LOGGER_TAG, ""), level);
logger.info("logging changed: {},oldValue:{},newValue:{}", key, Change(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 (ionMatches(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/l"/>
<include resource="org/springframework/boot/logging/l"/>
<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.taptapmons" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<logger name="com.taptap.server" level="WARN"/>
<logger name="com.taptapmons" level="WARN"/>
</springProfile>
</configuration>
⼀个典型的logback配置⽂件⾥包含了Appender和⽇志级别设置的信息,Appender可以理解为⽇志的输出源。如上贴出的这个配置,添加了两个Appender信息,⼀个是spring中内置的,将⽇志输出到控制台的Appender。⼀个是将error⽇志信息发送到Sentry应⽤监控平台的Appender。其他的配置描述了每个包路径不同的⽇志级别信息。到这⾥,我们很容易想到,上⽂已
经说过,spring已经⽀持以logging.level.包名=info这种配置来设置⽇志系统的⽇志级别。那么剩下的只要解决Appender的配置就ok了。在这⾥,其实只需要解决SentryAppender的加载就⾏,因为consoleAppender spring⾃⼰会处理。有了⽬标和⽅向,就好办了。以l配置的信息,最终都会加载成class对象。就和l配置⼀样。所以研究的⽅向就变成了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中,l的配置也是为了配置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) Singleton().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配置后,l⽂件就可以彻底的删除了
以上就是集成apollo动态⽇志取缔l配置的详细内容,更多关于apollo动取缔l配置的资料请关注其它相关⽂章!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论