集成apollo動態日志取締logback-spring.xml配置
前言
動態調整線上日志級別是一個非常常見的場景,借助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文件就可以徹底的刪除瞭
以上就是集成apollo動態日志取締logback-spring.xml配置的詳細內容,更多關於apollo動取締logback-spring.xml配置的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- SpringBoot項目讀取外置logback配置文件的問題及解決
- springboot 使用logback啟動報警報錯的解決
- Springboot內外部logback多環境配置詳解
- springboot實現將自定義日志格式存儲到mongodb中
- Springboot允許logger.debug輸出日志方式