先來看springboot中apollo的配置:
不仔細看或者對apollo配置不熟的人來說是很難看出配置錯在哪的,正確配置應該是如下:
eagerLoad配置錯,會導致springboot日志框架初始化時apollo的配置時還未加載成功,導致apollo配置的日志DEBUG級別沒起效果。
雖然是團隊哪位大佬的一不小心的疏忽,但是還是花了不少時間找問題。其他項目同樣配置可以DEBUG級別有效,可以確認apollo的日志配置是正確的,應該是配置加載出了問題。
下面我們就來分析下springboot的日志初始化過程:
首先找到springboot的日志初始化入口:
我們在spring-boot包中找到LoggingApplicationListener這個類,這個是spring的事件監聽器,主要代碼如下:
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartingEvent) { onApplicationStartingEvent((ApplicationStartingEvent) event); } else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } else if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) { onContextClosedEvent(); } else if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent(); } }
日志初始化主要是onApplicationEnvironmentPreparedEvent事件,也就是環境准備好事件,代表着所以配置已經加載,比如放apollo的配置。
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { if (this.loggingSystem == null) { this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); } initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader()); }
然后看initialize方法:
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) { new LoggingSystemProperties(environment).apply(); this.logFile = LogFile.get(environment); if (this.logFile != null) { this.logFile.applyToSystemProperties(); }
//初始化前的日志級別 initializeEarlyLoggingLevel(environment);
//初始化 initializeSystem(environment, this.loggingSystem, this.logFile);
//初始化后的日志級別 initializeFinalLoggingLevels(environment, this.loggingSystem); registerShutdownHookIfNecessary(environment, this.loggingSystem); }
我們主要看下initializeSystem方法,這個方法也主要是調用system.initialize方法,這里我們使用的是logback,所以system是LogbackLoggingSystem。
下面我們看LogbackLoggingSystem的initialize方法:
@Override public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { LoggerContext loggerContext = getLoggerContext(); if (isAlreadyInitialized(loggerContext)) { return; } super.initialize(initializationContext, configLocation, logFile); loggerContext.getTurboFilterList().remove(FILTER); markAsInitialized(loggerContext); if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) { getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY + "' system property. " + "Please use 'logging.config' instead."); } }
這個方面里面核心是super.initialize,調用父類的initialize方法,父類是AbstractLoggingSystem,這些代碼都是放在spring-boot包的logging下。
然后我們看AbstractLoggingSystem的initialize方法:
@Override public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { if (StringUtils.hasLength(configLocation)) { initializeWithSpecificConfig(initializationContext, configLocation, logFile); return; } initializeWithConventions(initializationContext, logFile); }
configLocation是沒設置值的,所以會進入initializeWithConventions方法:
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
//獲取logback自己支持的配置,搜索logback-test.groovy,logback-test.xml,logback.groovy,logback.xml這些文件 String config = getSelfInitializationConfig(); if (config != null && logFile == null) { // self initialization has occurred, reinitialize in case of property changes reinitialize(initializationContext); return; } if (config == null) {
//獲取spring支持的配置,搜索logback-test-spring.groovy,logback-test-spring.xml,logback-spring.groovy,logback-spring.xml這些文件 config = getSpringInitializationConfig(); } if (config != null) {
//搜索到logback的配置文件,就就在,這里面主要是解析logback的過程,就不分析了。 loadConfiguration(initializationContext, config, logFile); return; } loadDefaults(initializationContext, logFile); }
然后我們回到最開始分析的initializeFinalLoggingLevels方法:
private void initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) { if (this.springBootLogging != null) { initializeLogLevel(system, this.springBootLogging); } setLogLevels(system, environment); }
setLogLevels這個方法是設置日志級別,我們繼續往下看:
protected void setLogLevels(LoggingSystem system, Environment environment) { if (!(environment instanceof ConfigurableEnvironment)) { return; }
//獲取到所以的配置 Binder binder = Binder.get(environment); Map<String, String[]> groups = getGroups(); binder.bind(LOGGING_GROUP, STRING_STRINGS_MAP.withExistingValue(groups));
//從配置中獲取所以logging.level開頭的配置,並轉換為map Map<String, String> levels = binder.bind(LOGGING_LEVEL, STRING_STRING_MAP).orElseGet(Collections::emptyMap); levels.forEach((name, level) -> { String[] groupedNames = groups.get(name); if (ObjectUtils.isEmpty(groupedNames)) {
//遍歷logging.level,設置每個配置的級別 setLogLevel(system, name, level); } else { setLogLevel(system, groupedNames, level); } }); }
map結構如下:
"org.springframework.cloud.sleuth" -> "INFO"
"com.xxx" -> "DEBUG"
"org.springframework" -> "INFO"
當初找到DEBUG不起效,就是在這個方法中發現levels根本沒值,正常的里面是有如上的map結果,然后就感覺配置沒讀到,繼續查看environment對象,果然apollo的配置是沒讀到,確定apollo的飢餓加載有問題,最后對比springboot的apollo配置鎖定配置出錯。