现在我们来说说spring boot 中对于log相关的源码,因为我们现在需要对LoggersEndpoint进行分析,可是LoggersEndpoint中使用到了log相关的类,因此我们需要先对是如何实现Log的进行分析,之后再来看看LoggersEndpoint的实现
spring boot 中有关log的代码在org.springframework.boot.logging包下,如图:
其中log的类图如下(只画出主要的类):
本文先解析JavaLoggingSystem的实现.
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 SYSTEMS;
static {
Map systems = new LinkedHashMap();
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 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 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 entry : SYSTEMS.entrySet()) {
if (ClassUtils.isPresent(entry.getKey(), classLoader)) {
return get(classLoader, entry.getValue());
}
}
// 4. 如果没有获取到,则抛出IllegalStateException
throw new IllegalStateException("No suitable logging system located");
}
其中, NoOpLoggingSystem 代码如下:
static class NoOpLoggingSystem extends LoggingSystem {
@Override
public void beforeInitialize() {
}
@Override
public void setLogLevel(String loggerName, LogLevel level) {
}
@Override
public List getLoggerConfigurations() {
return Collections.emptyList();
}
@Override
public LoggerConfiguration getLoggerConfiguration(String loggerName) {
return null;
}
}
AbstractLoggingSystem–>实现了initialize方法
字段如下:
protected static final Comparator 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());
}
遍历给定的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;
}
此外该类还声明了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 systemToNative;
// key--> log底层支持的日志级别,value --> LogLevel
private final Map nativeToSystem;
public LogLevels() {
this.systemToNative = new HashMap();
this.nativeToSystem = new HashMap();
}
其声明的方法都很简单,如下:
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 getSupported() {
return new LinkedHashSet(this.nativeToSystem.values());
}
JavaLoggingSystem 继承自AbstractLoggingSystem,使用的java.util.logging来实现的.
字段,构造器如下:
private static final LogLevels LEVELS = new LogLevels();
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的映射关系如下:
方法如下:
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: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);
}
因此,在默认情况下:
问题来了,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);
}
根据名字获得对应的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);
}
获得对应的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
由于spring boot 默认依赖的是logback,因此需要去除spring-boot-starter-logging的依赖,
如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
同时,还需要加入commons-logging依赖,不然就会在SpringApplication 初始化时抛出异常,原因是SpringApplication有如下字段:
private static final Log logger = LogFactory.getLog(SpringApplication.class);
因此,修改pom文件,加入如下配置:
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
dependency>
由于java.util.logging 是jdk自带的,因此不需要加入其它的jar包了
注意,该启动流程分析是基于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());
}
初始化,代码如下:
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);
}
如果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
初始化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);
}
}
}
从ConfigurableEnvironment中获取logging.config 所对应的配置
默认情况下,使用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);
}
读取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));
}
}
}
如果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() {
}