前言
现在我们来说说spring boot 中对于log相关的源码,因为我们现在需要对LoggersEndpoint进行分析,可是LoggersEndpoint中使用到了log相关的类,因此我们需要先对是如何实现Log的进行分析,之后再来看看LoggersEndpoint的实现
spring boot 中有关log的代码在org.springframework.boot.logging包下,如图:
其中log的类图如下(只画出主要的类):
解析
本文先解析JavaLoggingSystem的实现.
LoggingSystem
LoggingSystem–>logging框架的基本抽象
定义了如下几个字段:
// 系统属性名,用该属性来配置使用哪个LoggingSystem的实现 public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName(); // 如果SYSTEM_PROPERTY对应的属性值为none,则意味着不希望使用LoggingSystem-->此时获得的是NoOpLoggingSystem,什么都不会进行打印 public static final String NONE = "none"; // 根节点logger的名字,LoggingSystem的实现应该使其作为根节点logger的名字,而不去管底层的实现 public static final String ROOT_LOGGER_NAME = "ROOT"; // 默认支持的类,key-->logger框架的类,value--> 对应的LoggingSystem 实现 private static final Map<String, String> SYSTEMS; static { Map<String, String> systems = new LinkedHashMap<String, String>(); systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem"); systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem"); systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem"); SYSTEMS = Collections.unmodifiableMap(systems); }
定义了1个抽象方法–>重新设置日志系统以减少输出,该方法应该在initialize方法前调用以减少日志的噪声直到日志系统初始化完毕,如下:
public abstract void beforeInitialize();
定义了如下几个方法:
initialize–>初始化日志系统,代码如下:
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { }
cleanUp–>清除资源,代码如下:
public void cleanUp() { }
getShutdownHandler–>返回1个Runnable –> 当jvm退出时来处理日志系统的关闭,默认返回null,表明不需要进行处理.代码如下:
public Runnable getShutdownHandler() { return null; }
getSupportedLogLevels–>返回该日志系统支持的日志级别,默认是所有都支持,代码如下:
public Set<LogLevel> getSupportedLogLevels() { return EnumSet.allOf(LogLevel.class); }
LogLevel 定义如下:
public enum LogLevel { TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF }
setLogLevel–>为给定的logger设置日志级别.代码如下:
public void setLogLevel(String loggerName, LogLevel level) { throw new UnsupportedOperationException("Unable to set log level"); }
getLoggerConfigurations–>返回当前LoggingSystem中的所有的logger,代码如下:
public List<LoggerConfiguration> getLoggerConfigurations() { throw new UnsupportedOperationException("Unable to get logger configurations"); }
getLoggerConfiguration–>返回给定的logger所对应的LoggerConfiguration,代码如下:
public LoggerConfiguration getLoggerConfiguration(String loggerName) { throw new UnsupportedOperationException("Unable to get logger configuration"); }
get–> 获得LoggingSystem.代码如下:
public static LoggingSystem get(ClassLoader classLoader) { // 1. 获得环境变量中key为org.springframework.boot.logging.LoggingSystem所对应的值 String loggingSystem = System.getProperty(SYSTEM_PROPERTY); // 2. 如果配置的值为none,则返回NoOpLoggingSystem,否则,对其进行实例化 if (StringUtils.hasLength(loggingSystem)) { if (NONE.equals(loggingSystem)) { return new NoOpLoggingSystem(); } return get(classLoader, loggingSystem); } // 3. 否则,遍历SYSTEMS,如果在当前类路径下存在所配置的类,则直接实例化,返回,默认使用的是LogbackLoggingSystem for (Map.Entry<String, String> entry : SYSTEMS.entrySet()) { if (ClassUtils.isPresent(entry.getKey(), classLoader)) { return get(classLoader, entry.getValue()); } } // 4. 如果没有获取到,则抛出IllegalStateException throw new IllegalStateException("No suitable logging system located"); }
- 获得环境变量中key为org.springframework.boot.logging.LoggingSystem所对应的值
- 如果配置的值为none,则返回NoOpLoggingSystem,否则,对其进行实例化
- 否则,遍历SYSTEMS,如果在当前类路径下存在所配置的类,则直接实例化,返回,默认使用的是LogbackLoggingSystem
- 如果没有获取到,则抛出IllegalStateException
其中, NoOpLoggingSystem 代码如下:
static class NoOpLoggingSystem extends LoggingSystem { @Override public void beforeInitialize() { } @Override public void setLogLevel(String loggerName, LogLevel level) { } @Override public List<LoggerConfiguration> getLoggerConfigurations() { return Collections.emptyList(); } @Override public LoggerConfiguration getLoggerConfiguration(String loggerName) { return null; } }
AbstractLoggingSystem
AbstractLoggingSystem–>实现了initialize方法
字段如下:
protected static final Comparator<LoggerConfiguration> CONFIGURATION_COMPARATOR = new LoggerConfigurationComparator( ROOT_LOGGER_NAME); private final ClassLoader classLoader;
其中LoggerConfigurationComparator的作用是在进行排序时,将root logger 排在第1位,其他的按照字典顺序排序.代码如下:
public int compare(LoggerConfiguration o1, LoggerConfiguration o2) { if (this.rootLoggerName.equals(o1.getName())) { return -1; } if (this.rootLoggerName.equals(o2.getName())) { return 1; } return o1.getName().compareTo(o2.getName()); }
构造器如下:
public AbstractLoggingSystem(ClassLoader classLoader) { this.classLoader = classLoader; }
initialize 方法实现如下:
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { // 1. 如果配置有configLocation,则调用initializeWithSpecificConfig if (StringUtils.hasLength(configLocation)) { initializeWithSpecificConfig(initializationContext, configLocation, logFile); return; } // 2. 按照log的底层实现的规则进行初始化 initializeWithConventions(initializationContext, logFile); }
如果配置有configLocation,则调用initializeWithSpecificConfig,根据指定的配置文件进行初始化,代码如下:
private void initializeWithSpecificConfig( LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { // 1. 占位符处理 configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation); // 2. 加载配置文件,子类实现 loadConfiguration(initializationContext, configLocation, logFile); }
- 占位符处理
- 加载配置文件,子类实现
按照log的底层实现的规则进行初始化,代码如下:
private void initializeWithConventions( LoggingInitializationContext initializationContext, LogFile logFile) { // 1. 获得配置文件的路径 String config = getSelfInitializationConfig(); if (config != null && logFile == null) { // 2. 如果配置文件存在并且LogFile等于null,则调用reinitialize重新修改一些属性 // self initialization has occurred, reinitialize in case of property changes reinitialize(initializationContext); return; } // 3. 如果config等于null,则 if (config == null) { config = getSpringInitializationConfig(); } // 4. 如果config有值了,则加载配置,然后return if (config != null) { loadConfiguration(initializationContext, config, logFile); return; } // 5. 加载默认配置 loadDefaults(initializationContext, logFile); }
获得配置文件的路径,代码如下:
protected String getSelfInitializationConfig() { return findConfig(getStandardConfigLocations()); }
- 调用getStandardConfigLocations获得配置文件的路径,子类实现
遍历给定的locations,依次通过ClassPathResource进行加载,如果存在,则加上classpath:前缀 返回,否则,返回null.代码如下:
private String findConfig(String[] locations) { for (String location : locations) { ClassPathResource resource = new ClassPathResource(location, this.classLoader); if (resource.exists()) { return "classpath:" + location; } } return null; }
如果配置文件存在并且LogFile等于null,则调用reinitialize重新修改一些属性,该方法默认是空实现,代码如下:
protected void reinitialize(LoggingInitializationContext initializationContext) { }
如果config等于null,则在log底层实现支持的配置文件加上-spring 后进行加载,代码如下:
protected String getSpringInitializationConfig() { return findConfig(getSpringConfigLocations()); }
log底层实现支持的配置文件加上-spring,代码如下:
protected String[] getSpringConfigLocations() { // 1. 获得标准的配置文件路径,依次遍历之 String[] locations = getStandardConfigLocations(); for (int i = 0; i < locations.length; i++) { // 2.1 修改原配置为xxx-spring.extension 进行加载 String extension = StringUtils.getFilenameExtension(locations[i]); locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1) + "-spring." + extension; } return locations; }
- 遍历给定的locations,依次通过ClassPathResource进行加载,如果存在,则加上classpath:前缀 返回,否则,返回null
- 如果config有值了,则加载配置,然后return.该方法默认是空实现
- 加载默认配置.该方法默认是空实现
此外该类还声明了2个方法,供子类使用:
getPackagedConfigFile–>在当前类所在的路径下拼接指定的fileName生成路径.代码如下:
protected final String getPackagedConfigFile(String fileName) { String defaultPath = ClassUtils.getPackageName(getClass()); defaultPath = defaultPath.replace('.', '/'); defaultPath = defaultPath + "/" + fileName; defaultPath = "classpath:" + defaultPath; return defaultPath; }
applySystemProperties–>设置系统的环境变量.代码如下:
protected final void applySystemProperties(Environment environment, LogFile logFile) { new LoggingSystemProperties(environment).apply(logFile); }
这里说一下LoggingSystemProperties,该类的作用是设置系统属性的工具类,以供log 配置文件使用.
字段如下:
// PID static final String PID_KEY = LoggingApplicationListener.PID_KEY; // LOG_EXCEPTION_CONVERSION_WORD static final String EXCEPTION_CONVERSION_WORD = LoggingApplicationListener.EXCEPTION_CONVERSION_WORD; // CONSOLE_LOG_PATTERN static final String CONSOLE_LOG_PATTERN = LoggingApplicationListener.CONSOLE_LOG_PATTERN; // FILE_LOG_PATTERN static final String FILE_LOG_PATTERN = LoggingApplicationListener.FILE_LOG_PATTERN; // LOG_LEVEL_PATTERN static final String LOG_LEVEL_PATTERN = LoggingApplicationListener.LOG_LEVEL_PATTERN; private final Environment environment; LoggingSystemProperties(Environment environment) { this.environment = environment; }
apply 方法实现如下:
public void apply(LogFile logFile) { // 1. 实例化RelaxedPropertyResolver,前缀为logging. RelaxedPropertyResolver propertyResolver = RelaxedPropertyResolver .ignoringUnresolvableNestedPlaceholders(this.environment, "logging."); // 2. 如果不存在环境变量中不存在LOG_EXCEPTION_CONVERSION_WORD的配置并且environment中配置了logging.exception-conversion-word // 则进行设置 setSystemProperty(propertyResolver, EXCEPTION_CONVERSION_WORD, "exception-conversion-word"); // 3. 如果不存在环境变量中不存在LPID,则实例化ApplicationPid获取pid值后进行设置 setSystemProperty(PID_KEY, new ApplicationPid().toString()); // 4. 如果不存在环境变量中不存在CONSOLE_LOG_PATTERN的配置并且environment中配置了logging.pattern.console // 则进行设置 setSystemProperty(propertyResolver, CONSOLE_LOG_PATTERN, "pattern.console"); // 5. 如果不存在环境变量中不存在CONSOLE_LOG_PATTERN的配置并且environment中配置了logging.pattern.console // 则进行设置 setSystemProperty(propertyResolver, FILE_LOG_PATTERN, "pattern.file"); // 6. 如果不存在环境变量中不存在LOG_LEVEL_PATTERN的配置并且environment中配置了pattern.level // 则进行设置 setSystemProperty(propertyResolver, LOG_LEVEL_PATTERN, "pattern.level"); // 7. 如果LogFile不等于null,则将LogFile配置的path,文件路径设置到环境变量中 if (logFile != null) { logFile.applyToSystemProperties(); } }
在AbstractLoggingSystem中还声明了1个泛型静态内部类–>LogLevels,作用是维护1个关于log底层支持的日志级别和LogLevel的映射.泛型参数T–>底层的日志级别类型.
字段,构造器如下:
// key -->LogLevel,value --> log底层支持的日志级别 private final Map<LogLevel, T> systemToNative; // key--> log底层支持的日志级别,value --> LogLevel private final Map<T, LogLevel> nativeToSystem; public LogLevels() { this.systemToNative = new HashMap<LogLevel, T>(); this.nativeToSystem = new HashMap<T, LogLevel>(); }
其声明的方法都很简单,如下:
public void map(LogLevel system, T nativeLevel) { if (!this.systemToNative.containsKey(system)) { this.systemToNative.put(system, nativeLevel); } if (!this.nativeToSystem.containsKey(nativeLevel)) { this.nativeToSystem.put(nativeLevel, system); } } public LogLevel convertNativeToSystem(T level) { return this.nativeToSystem.get(level); } public T convertSystemToNative(LogLevel level) { return this.systemToNative.get(level); } public Set<LogLevel> getSupported() { return new LinkedHashSet<LogLevel>(this.nativeToSystem.values()); }
JavaLoggingSystem
JavaLoggingSystem 继承自AbstractLoggingSystem,使用的java.util.logging来实现的.
字段,构造器如下:
private static final LogLevels<Level> LEVELS = new LogLevels<Level>(); static { LEVELS.map(LogLevel.TRACE, Level.FINEST); LEVELS.map(LogLevel.DEBUG, Level.FINE); LEVELS.map(LogLevel.INFO, Level.INFO); LEVELS.map(LogLevel.WARN, Level.WARNING); LEVELS.map(LogLevel.ERROR, Level.SEVERE); LEVELS.map(LogLevel.FATAL, Level.SEVERE); LEVELS.map(LogLevel.OFF, Level.OFF); } public JavaLoggingSystem(ClassLoader classLoader) { super(classLoader); }
至此,我们知道了JavaLoggingSystem中和LogLevel的映射关系如下:
- TRACE–>FINEST
- DEBUG–>FINE
- INFO–>INFO
- WARN–>WARNING
- ERROR, FATAL–> SEVERE
- OFF–>OFF
方法如下:
getStandardConfigLocations,代码如下:
protected String[] getStandardConfigLocations() { return new String[] { "logging.properties" }; }
loadDefaults–>如果LogFile不等于null,则读取org/springframework/boot/logging/java/logging-file.properties,否则,读取org/springframework/boot/logging/java/logging.properties.实现如下:
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { if (logFile != null) { loadConfiguration(getPackagedConfigFile("logging-file.properties"), logFile); } else { loadConfiguration(getPackagedConfigFile("logging.properties"), logFile); } }
loadConfiguration–>加载配置文件,实现如下:
protected void loadConfiguration(String location, LogFile logFile) { Assert.notNull(location, "Location must not be null"); try { // 1. 读取配置文件的信息到String 中 String configuration = FileCopyUtils.copyToString( new InputStreamReader(ResourceUtils.getURL(location).openStream())); if (logFile != null) { // 2. 如果logFile不等于null,则将${LOG_FILE} 替换为logFile的路径 configuration = configuration.replace("${LOG_FILE}", StringUtils.cleanPath(logFile.toString())); } // 3. 重新配置 LogManager.getLogManager().readConfiguration( new ByteArrayInputStream(configuration.getBytes())); } catch (Exception ex) { throw new IllegalStateException( "Could not initialize Java logging from " + location, ex); } }
因此,其加载配置文件的顺序如下:
- 如果classpath:logging.properties 存在,并且LogFile不存在的话,则进行加载
- 如果classpath:logging-spring.properties存在,则进行加载
- 如果LogFile存在,则加载classpath:org/springframework/boot/logging/java/logging-file.properties
- 否则,加载classpath:org/springframework/boot/logging/java/logging.properties
因此,在默认情况下,会加载classpath:org/springframework/boot/logging/java/logging.properties ,其内容如下所示:
handlers =java.util.logging.ConsoleHandler .level = INFO java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter java.util.logging.ConsoleHandler.level = ALL org.hibernate.validator.internal.util.Version.level = WARNING org.apache.coyote.http11.Http11NioProtocol.level = WARNING org.crsh.plugin.level = WARNING org.apache.tomcat.util.net.NioSelectorPool.level = WARNING org.apache.catalina.startup.DigesterFactory.level = SEVERE org.apache.catalina.util.LifecycleBase.level = SEVERE org.eclipse.jetty.util.component.AbstractLifeCycle.level = SEVERE
这里配置了Console日志的格式化由SimpleFormatter来实现,这里我们就来看看其实现.
SimpleFormatter 继承了java.util.logging.Formatter,其字段如下:
private static final String DEFAULT_FORMAT = "[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL] - %8$s %4$s [%7$s] --- %3$s: %5$s%6$s%n"; // 日志格式 private final String format = getOrUseDefault("LOG_FORMAT", DEFAULT_FORMAT); // pid private final String pid = getOrUseDefault("PID", "????"); private final Date date = new Date();
其中getOrUseDefault方法如下:
private static String getOrUseDefault(String key, String defaultValue) { String value = null; try { // 1. 获取指定的环境变量值。环境变量是一个取决于系统的外部指定的值。 value = System.getenv(key); } catch (Exception ex) { // ignore } // 2. 如果value等于null,则赋值为默认值 if (value == null) { value = defaultValue; } // 3. 系统属性的字符串值,如果没有带有此键的属性,则返回默认值。 return System.getProperty(key, value); }
- 获取指定的环境变量值。环境变量是一个取决于系统的外部指定的值。
- 如果value等于null,则赋值为默认值
- 系统属性的字符串值,如果没有带有此键的属性,则返回默认值。
因此,在默认情况下:
- 对于format来说,由于在环境变量,系统属性中都不存在LOG_FORMAT的配置,因此默认使用[%1 tY− tm-%1 td tH:%1 tM: tS.%1 tL]− s %4 s[ s] — %3 s: s%6$s%n.
- 对于PID来说,由于在系统属性中设置了PID,因此会返回该程序的pid.
问题来了,PID是何时设置的呢?
在SpringApplication的run方法中,会调用prepareEnvironment方法,在该方法中会发送ApplicationEnvironmentPreparedEvent事件,LoggingApplicationListener会监听该事件,最终调用initialize方法,代码如下:protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) { new LoggingSystemProperties(environment).apply(); LogFile logFile = LogFile.get(environment); if (logFile != null) { logFile.applyToSystemProperties(); } initializeEarlyLoggingLevel(environment); initializeSystem(environment, this.loggingSystem, logFile); initializeFinalLoggingLevels(environment, this.loggingSystem); registerShutdownHookIfNecessary(environment, this.loggingSystem); }
在该方法中调用了LoggingSystemProperties#apply,最终将PID设置到了系统属性中
beforeInitialize –>将root logger 的日志级别设置为SEVERE,代码如下:
public void beforeInitialize() { super.beforeInitialize(); Logger.getLogger("").setLevel(Level.SEVERE); }
setLogLevel,代码如下:
public void setLogLevel(String loggerName, LogLevel level) { Assert.notNull(level, "Level must not be null"); // 1. 如果loggerName等于null或者root 等于loggerName,则将其赋值"" if (loggerName == null || ROOT_LOGGER_NAME.equals(loggerName)) { loggerName = ""; } // 2.根据loggerName 获得对应的logger Logger logger = Logger.getLogger(loggerName); if (logger != null) { // 3. 如果获取到,则将其传入的LogLevel 转换为 JavaLogging 对应的log logger.setLevel(LEVELS.convertSystemToNative(level)); } }
getLoggerConfigurations,代码如下:
public List<LoggerConfiguration> getLoggerConfigurations() { List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>(); // 1.获得JavaLogging 中的所有logger的名字,遍历之 Enumeration<String> names = LogManager.getLogManager().getLoggerNames(); while (names.hasMoreElements()) { // 2. 根据名字获得对应的LoggerConfiguration,添加到result中 result.add(getLoggerConfiguration(names.nextElement())); } // 3. 排序,将root logger 排在第1位,其他的按照字典顺序排序 Collections.sort(result, CONFIGURATION_COMPARATOR); return Collections.unmodifiableList(result); }
- 获得JavaLogging 中的所有logger的名字,遍历之
根据名字获得对应的LoggerConfiguration,添加到result中,代码如下:
public LoggerConfiguration getLoggerConfiguration(String loggerName) { // 1 .根据loggerName 获得对应的logger,如果获取不到,返回null Logger logger = Logger.getLogger(loggerName); if (logger == null) { return null; } // 2. 获得对应的level,effectiveLevel LogLevel level = LEVELS.convertNativeToSystem(logger.getLevel()); LogLevel effectiveLevel = LEVELS.convertNativeToSystem(getEffectiveLevel(logger)); // 3.如果logger没有名字,则将其赋值为root String name = (StringUtils.hasLength(logger.getName()) ? logger.getName() : ROOT_LOGGER_NAME); // 4. 实例化LoggerConfiguration return new LoggerConfiguration(name, level, effectiveLevel); }
- 根据loggerName 获得对应的logger,如果获取不到,返回null
获得对应的level,effectiveLevel.其中getEffectiveLevel方法如下:
private Level getEffectiveLevel(Logger root) { Logger logger = root; while (logger.getLevel() == null) { logger = logger.getParent(); } return logger.getLevel(); }
如果Logger对应的Level 等于null,说明该logger的日志级别是继承其父类的,那么此时就需要不断的先上查找,只至获取到Level
- 获得对应的level,effectiveLevel
- 实例化LoggerConfiguration
- 排序,将root logger 排在第1位,其他的按照字典顺序排序
集成
由于spring boot 默认依赖的是logback,因此需要去除spring-boot-starter-logging的依赖,
如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
同时,还需要加入commons-logging依赖,不然就会在SpringApplication 初始化时抛出异常,原因是SpringApplication有如下字段:
private static final Log logger = LogFactory.getLog(SpringApplication.class);
因此,修改pom文件,加入如下配置:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
由于java.util.logging 是jdk自带的,因此不需要加入其它的jar包了
LoggingSystem生命周期分析
注意,该启动流程分析是基于JavaLoggingSystem来分析的,其他的都一样,只不过在具体的实现上依赖于底层依赖,但是大致流程是不变的
在SpringApplication#run方法中,会调用SpringApplicationRunListeners#starting方法,如下:
SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();
在该方法中,会发送ApplicationEnvironmentPreparedEvent事件.
LoggingApplicationListener监听了该事件,最终调用了onApplicationStartingEvent进行处理.代码如下:
private void onApplicationStartingEvent(ApplicationStartingEvent event) { this.loggingSystem = LoggingSystem .get(event.getSpringApplication().getClassLoader()); this.loggingSystem.beforeInitialize(); }
由于此时我们使用的是java.util.logging,因此此时返回的是JavaLoggingSystem,并执行JavaLoggingSystem#beforeInitialize
接着执行,接下来在SpringApplication#run,会调用prepareEnvironment,而在该方法中会调用SpringApplicationRunListeners#environmentPrepared方法,发送ApplicationEnvironmentPreparedEvent代码如下:
listeners.environmentPrepared(environment);
同样,最终会调用LoggingApplicationListener#onApplicationEnvironmentPreparedEvent方法,代码如下:
private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { if (this.loggingSystem == null) { this.loggingSystem = LoggingSystem .get(event.getSpringApplication().getClassLoader()); } initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader()); }
- 如果loggingSystem等于null,则通过调用LoggingSystem#get获得,由于已经在第1步获得了,因此此处不执行
初始化,代码如下:
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) { // 1. 向系统属性中写入PID,CONSOLE_LOG_PATTERN等配置 new LoggingSystemProperties(environment).apply(); // 2. 获取LogFile,如果LogFile不等于null,则向系统属性写入LogFile配置的文件路径 // 由于默认情况下,没有配置logging.file和logging.path中的任意1个,因此返回null LogFile logFile = LogFile.get(environment); if (logFile != null) { logFile.applyToSystemProperties(); } // 3. 如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null,则尝试设置springBootLogging initializeEarlyLoggingLevel(environment); // 4. 初始化loggingSystem initializeSystem(environment, this.loggingSystem, logFile); // 5. 设置日志级别 initializeFinalLoggingLevels(environment, this.loggingSystem); // 6. 注册ShutdownHook registerShutdownHookIfNecessary(environment, this.loggingSystem); }
- 向系统属性中写入PID,CONSOLE_LOG_PATTERN等配置
- 获取LogFile,如果LogFile不等于null,则向系统属性写入LogFile配置的文件路径,由于默认情况下,没有配置logging.file和logging.path中的任意1个,因此返回nul
如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null,则尝试设置springBootLogging.代码如下:
private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) { // 1. 如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null if (this.parseArgs && this.springBootLogging == null) { // 1.1 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG if (isSet(environment, "debug")) { this.springBootLogging = LogLevel.DEBUG; } // 1.2 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG if (isSet(environment, "trace")) { this.springBootLogging = LogLevel.TRACE; } } }
如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null
- 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG
- 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG
初始化loggingSystem.代码如下:
private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) { // 1. 实例化LoggingInitializationContext LoggingInitializationContext initializationContext = new LoggingInitializationContext( environment); // 2. 从ConfigurableEnvironment中获取logging.config 所对应的配置 String logConfig = environment.getProperty(CONFIG_PROPERTY); if (ignoreLogConfig(logConfig)) { // 2.1 如果logging.config没有配置或者配置的值是-D开头的,则最终调用AbstractLoggingSystem#initializeWithConventions 进行初始化 system.initialize(initializationContext, null, logFile); } else { try { // 2.2 否则,通过ResourceUtils进行加载判断其文件是否存在,如果不存在,则抛出IllegalStateException // 否则,最终调用AbstractLoggingSystem#initializeWithSpecificConfig ResourceUtils.getURL(logConfig).openStream().close(); system.initialize(initializationContext, logConfig, logFile); } catch (Exception ex) { // NOTE: We can't use the logger here to report the problem System.err.println("Logging system failed to initialize " + "using configuration from '" + logConfig + "'"); ex.printStackTrace(System.err); throw new IllegalStateException(ex); } } }
- 实例化LoggingInitializationContext
从ConfigurableEnvironment中获取logging.config 所对应的配置
- 如果logging.config没有配置或者配置的值是-D开头的,则最终调用AbstractLoggingSystem#initializeWithConventions 进行初始化
- 否则,通过ResourceUtils进行加载判断其文件是否存在,如果不存在,则抛出IllegalStateException,否则,最终调用AbstractLoggingSystem#initializeWithSpecificConfig
默认情况下,使用2.1,由于此时我们使用的是JavaLoggingSystem,因此会首先加载classpath:logging.properties ,由于不存在,并且此时LogFile等于null,因此最终执行JavaLoggingSystem #loadDefaults,代码如下:
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { // 1. 如果LogFile不等于null,则读取org/springframework/boot/logging/java/logging-file.properties,否则,读取org/springframework/boot/logging/java // logging.properties if (logFile != null) { loadConfiguration(getPackagedConfigFile("logging-file.properties"), logFile); } else { loadConfiguration(getPackagedConfigFile("logging.properties"), logFile); } }
同样,由于logFile等于null,最终会读取classpath:org/springframework/boot/logging/java/logging.properties,这点我们前面有叙述.
设置日志级别,代码如下:
private void initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) { // 1. 如果springBootLogging配置了,则获取到其对应的logger,设置其日志级别和springBootLogging的一样 if (this.springBootLogging != null) { initializeLogLevel(system, this.springBootLogging); } // 2.读取environment中logging.level.*=*的配置进行日志的配置,一般都是到这里 setLogLevels(system, environment); }
- 如果springBootLogging配置了,则获取到其对应的logger,设置其日志级别和springBootLogging的一样
读取environment中logging.level.*=*的配置进行日志的配置,一般都是到这里,代码如下:
protected void setLogLevels(LoggingSystem system, Environment environment) { // 1. 获得environment中logging.level.*=* 的配置 Map<String, Object> levels = new RelaxedPropertyResolver(environment) .getSubProperties("logging.level."); // 标志位,标记是否是对root logger的配置 boolean rootProcessed = false; // 2. 遍历之 for (Entry<String, Object> entry : levels.entrySet()) { String name = entry.getKey(); // 2.1 如果其logger 名和root 一样,如果rootProcessed等于false,则continue.否则,将name设置为null,rootProcessed // 设置为true.--> 意味着,如果logging.level.root= xxx配置了多个,则只有第1个生效 if (name.equalsIgnoreCase(LoggingSystem.ROOT_LOGGER_NAME)) { if (rootProcessed) { continue; } name = null; rootProcessed = true; } // 2.2 设置对应的logger的日志级别 setLogLevel(system, environment, name, entry.getValue().toString()); } }
注册ShutdownHook,代码如下:
private void registerShutdownHookIfNecessary(Environment environment, LoggingSystem loggingSystem) { // 1. 从Environment中读取logging.register-shutdown-hook的配置(如果没有配置,则返回false) boolean registerShutdownHook = new RelaxedPropertyResolver(environment) .getProperty(REGISTER_SHUTDOWN_HOOK_PROPERTY, Boolean.class, false); if (registerShutdownHook) { // 2. 如果registerShutdownHook等于true,则获得LoggingSystem对应的ShutdownHandler // 如果不等于null则进行注册 Runnable shutdownHandler = loggingSystem.getShutdownHandler(); if (shutdownHandler != null && shutdownHookRegistered.compareAndSet(false, true)) { registerShutdownHook(new Thread(shutdownHandler)); } } }
- 从Environment中读取logging.register-shutdown-hook的配置(如果没有配置,则返回false)
如果registerShutdownHook等于true,则获得LoggingSystem对应的ShutdownHandler,如果不等于null则进行注册.代码如下:
void registerShutdownHook(Thread shutdownHook) { Runtime.getRuntime().addShutdownHook(shutdownHook); }
由于默认情况下,我们没有配置logging.register-shutdown-hook=true,因此是不会注册的. 如果配置了,则JavaLoggingSystem#getShutdownHandler 会返回ShutdownHandler,当jvm退出前,会最终调用其run方法,代码如下:
public void run() { // 重新设置日志配置,所有被命名的logger被删除,所有的Handlers 关闭 // 除了root logger外所有的日志级别设置为null,root logger 的日志级别设置为info LogManager.getLogManager().reset(); }
接下来,SpringApplication会调用其prepareContext方法,在该方法中,最终会调用SpringApplicationRunListeners#contextLoaded方法,代码如下:
listeners.contextLoaded(context);
在该方法中,最终会发送ApplicationPreparedEvent事件,LoggingApplicationListener监听到该事件后,最终会调用onApplicationPreparedEvent方法进行注册,代码如下:
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { ConfigurableListableBeanFactory beanFactory = event.getApplicationContext() .getBeanFactory(); if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) { beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem); } }
如果beanFactory中不包含id为springBootLoggingSystem的bean,则将loggingSystem进行注册,id为springBootLoggingSystem
当spring应用结束前,会执行AbstractApplicationContext#close方法,在该方法中调用了doClose,而在该方法总,调用了publishEvent方法发布了ContextClosedEvent事件,代码如下:
publishEvent(new ContextClosedEvent(this));
LoggingApplicationListener监听到该事件后,最终会调用onContextClosedEvent方法,代码如下:
private void onContextClosedEvent() { if (this.loggingSystem != null) { this.loggingSystem.cleanUp(); } }
由于JavaLoggingSystem没有覆写cleanUp方法,因此,该方法不会带来任何的副作用(因为是空实现). 代码如下:
public void cleanUp() { }