先来看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配置锁定配置出错。