集成apollo動(dòng)態(tài)日志取締logback-spring.xml配置
前言
動(dòng)態(tài)調(diào)整線上日志級(jí)別是一個(gè)非常常見的場(chǎng)景,借助apollo這種配置中心組件非常容易實(shí)現(xiàn)。作為apollo的官方技術(shù)支持,博主經(jīng)常在技術(shù)群看到有使用者詢問apollo是否可以托管logback的配置文件,畢竟有了配置中心后,消滅所有的本地配置全部交給apollo管理是我們的最終目標(biāo)。可是,apollo不具備直接托管logback-spring.xml配置文件能力,但是,我們可以基于spring和logback的裝載機(jī)制,完全取締logback-spring.xml配置,以apollo中的配置驅(qū)動(dòng)。而且,改造后,大大提高了日志系統(tǒng)的靈活性和可擴(kuò)展性。
APOLLO動(dòng)態(tài)日志
何為apollo動(dòng)態(tài)日志?直接這樣說可能會(huì)有歧義,以為是apollo里的日志,其實(shí)不然。舉個(gè)簡(jiǎn)單的例子,比如,我們項(xiàng)目很多地方使用了log.debug()打印日志,為了方便通過日志信息排查問題,但是一般情況下,生產(chǎn)環(huán)境的日志級(jí)別會(huì)配置成info。只有遇到需要排查線上問題的時(shí)候才會(huì)臨時(shí)打開debug級(jí)別日志。這個(gè)時(shí)候只能需改配置文件,將日志級(jí)別調(diào)整成debug,然后重新打包部署驗(yàn)證。不僅流程繁瑣耗時(shí),還會(huì)破壞當(dāng)時(shí)的"案發(fā)現(xiàn)場(chǎng)的環(huán)境",導(dǎo)致判斷不準(zhǔn)確。如果應(yīng)用具備了apollo動(dòng)態(tài)日志這種能力,就只需在apollo修改下配置然后提交,就可以熱更新日志級(jí)別,馬上打印debug級(jí)別日志。這就是所謂的apollo動(dòng)態(tài)日志。實(shí)現(xiàn)這個(gè)效果,需要具備兩個(gè)能力,分別由spring和apollo提供
spring日志系統(tǒng)熱更新日志級(jí)別
spring應(yīng)用中,spring適配了主流的日志框架,如logback、log4j2等,在這些日志框架之上,又抽象了自己的日志系統(tǒng)服務(wù),這里我們用到了spring的LoggingSystem,用它來熱更新日志級(jí)別,這個(gè)類在日志系統(tǒng)初始化時(shí)就添加到了spring的容器中,所以只要在spring的上下文管理范圍內(nèi),就可以直接注入,以下為主要使用到的api描述:
/**
* 設(shè)置給定日志記錄器的日志級(jí)別.
* @param loggerName 要設(shè)置的日志記錄器的名稱({@code null}可用于根日志記錄器)。
* @param level 日志級(jí)別
*/
public void setLogLevel(String loggerName, LogLevel level) {
throw new UnsupportedOperationException("Unable to set log level");
}apollo日志配置變更動(dòng)態(tài)下發(fā)
apollo作為分布式配置中心,配置集中管理和配置熱更新是其最核心的功能,此外,apollo還提供了配置變更下發(fā)監(jiān)聽的功能。基于這個(gè)配置監(jiān)聽的設(shè)計(jì),實(shí)現(xiàn)動(dòng)態(tài)日志就變得非常簡(jiǎn)單了。而且不僅可以實(shí)現(xiàn)日志動(dòng)態(tài)熱更,基于這個(gè)思路,連接池、數(shù)據(jù)源等都可以輕松實(shí)現(xiàn)。apollo實(shí)現(xiàn)監(jiān)聽配置變更有多種方式,可以通過Config實(shí)例手動(dòng)添加,如:
@ApolloConfig
public Config config;
public void addConfigChangeListener(){
config.addChangeListener(changeEvent->{
System.out.println("config change keys" + changeEvent.changedKeys());
});
}也可以通過注解直接驅(qū)動(dòng)
@ApolloConfigChangeListener
public void addConfigChangeListener(ConfigChangeEvent changeEvent){
System.out.println("config change keys" + changeEvent.changedKeys());
}實(shí)現(xiàn)日志調(diào)整熱更新
有了上述能力,在結(jié)合spring支持的日志加載配置方式,如:
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
可以實(shí)現(xiàn)如下代碼完成功能,遇到需要調(diào)整日志級(jí)別時(shí),修改apollo里的配置,即可實(shí)時(shí)生效
@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配置之前,先看下這個(gè)配置文件有哪些配置信息,起到了哪些作用,下面貼出一個(gè)典型的配置文件內(nèi)容:
<?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>一個(gè)典型的logback配置文件里包含了Appender和日志級(jí)別設(shè)置的信息,Appender可以理解為日志的輸出源。如上貼出的這個(gè)配置,添加了兩個(gè)Appender信息,一個(gè)是spring中內(nèi)置的,將日志輸出到控制臺(tái)的Appender。一個(gè)是將error日志信息發(fā)送到Sentry應(yīng)用監(jiān)控平臺(tái)的Appender。其他的配置描述了每個(gè)包路徑不同的日志級(jí)別信息。到這里,我們很容易想到,上文已經(jīng)說過,spring已經(jīng)支持以logging.level.包名=info這種配置來設(shè)置日志系統(tǒng)的日志級(jí)別。那么剩下的只要解決Appender的配置就ok了。在這里,其實(shí)只需要解決SentryAppender的加載就行,因?yàn)閏onsoleAppender spring自己會(huì)處理。有了目標(biāo)和方向,就好辦了。以logback-spring.xml配置的信息,最終都會(huì)加載成class對(duì)象。就和spring.xml配置一樣。所以研究的方向就變成了Logback的加載原理的問題。
Logback加載原理
在java的日志生態(tài)里,除了響當(dāng)當(dāng)?shù)膌ogback、log4j2、apache common log外,還有一個(gè)日志框架不得不提,就是sl4j。正因?yàn)閖ava生態(tài)強(qiáng)大,日志框架層出不窮,所以sl4j出來了,不干實(shí)事,專門定義日志標(biāo)準(zhǔn)、規(guī)范定義接口。而且,在我們平時(shí)的編碼過程中,也建議使用sl4j的api,這樣,無論底層日志框架實(shí)現(xiàn)怎么切換,都不會(huì)影響。主流的日志框架都有實(shí)現(xiàn)sl4j的接口,spring中日志系統(tǒng)的加載也是面向的sl4j,而不是直接面向日志實(shí)現(xiàn),加載過程是一個(gè)自動(dòng)化的過程,系統(tǒng)會(huì)自動(dòng)掃描實(shí)現(xiàn)了sl4j的接口實(shí)現(xiàn),如:
public interface ILoggerFactory {
public Logger getLogger(String name);
}每個(gè)日志框架都會(huì)實(shí)現(xiàn)這個(gè)接口,如Logback中的LoggerContext。Logback所有的功能都集成在了這個(gè)Context中,logback-spring.xml的配置也是為了配置LoggerContext中的屬性信息,所有我們只要拿到了LoggerContext實(shí)例,問題就解決了一大半。這涉及到sl4j的另一個(gè)接口,獲取ILoggerFactory實(shí)例的接口:
public interface LoggerFactoryBinder {
public ILoggerFactory getLoggerFactory();
public String getLoggerFactoryClassStr();
}Logback的實(shí)現(xiàn)類為StaticLoggerBinder,也就是說,我們可以通過StaticLoggerBinder的getLoggerFactory方法拿到LoggerContext實(shí)例了。
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其實(shí)就是描述了日志組成對(duì)象以及對(duì)象的屬性。在使用java bean的方式配置時(shí)需要注意,Logback的設(shè)計(jì)里,每個(gè)日志系統(tǒng)組成實(shí)例都有一個(gè)start狀態(tài)屬性,上面的start()方法其實(shí)不是動(dòng)作,只是標(biāo)記了這個(gè)屬性為true。而在xml里這個(gè)屬性只要配置了就自動(dòng)激活為true了,這里必須顯示的start()一下。解決了日志級(jí)別配置和Appender配置后,Logback-spring.xml文件就可以徹底的刪除了
以上就是集成apollo動(dòng)態(tài)日志取締logback-spring.xml配置的詳細(xì)內(nèi)容,更多關(guān)于apollo動(dòng)取締logback-spring.xml配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring Boot Dubbo 構(gòu)建分布式服務(wù)的方法
這篇文章主要介紹了Spring Boot Dubbo 構(gòu)建分布式服務(wù)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05
Java選擇結(jié)構(gòu)與循環(huán)結(jié)構(gòu)的使用詳解
循環(huán)結(jié)構(gòu)是指在程序中需要反復(fù)執(zhí)行某個(gè)功能而設(shè)置的一種程序結(jié)構(gòu)。它由循環(huán)體中的條件,判斷繼續(xù)執(zhí)行某個(gè)功能還是退出循環(huán),選擇結(jié)構(gòu)用于判斷給定的條件,根據(jù)判斷的結(jié)果判斷某些條件,根據(jù)判斷的結(jié)果來控制程序的流程2022-03-03
Spring Boot 開發(fā)環(huán)境熱部署詳細(xì)教程
這篇文章主要介紹了Spring Boot 開發(fā)環(huán)境熱部署,本文給大家介紹了Spring Boot 開發(fā)環(huán)境熱部署的原理及快速配置方法,通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06

