1. 概述
在使用 Spring Boot 时,默认就已经提供了日志功能,使用 Logback 作为默认的日志框架。本文,我们就来一起研究下,Spring Boot 是如何自动初始化好日志系统的。
不了解 Spring Boot 日志功能的胖友,可以先看看 《一起来学 SpringBoot 2.x | 第三篇:SpringBoot 日志配置》 文章。
2. LoggingApplicationListener
Spring Boot 提供日志功能,关键在于 LoggingApplicationListener 类。在 《精尽 Spring Boot 源码分析 —— ApplicationListener》 中,我们已经简单介绍过它:
org.springframework.boot.context.logging.LoggingApplicationListener
,实现 GenericApplicationListener 接口,实现根据配置初始化日志系统 Logger 。
2.1 supportsEventType
实现 #supportsEventType(ResolvableType resolvableType)
方法,判断是否是支持的事件类型。代码如下:
// LoggingApplicationListener.java |
2.2 supportsSourceType
实现 #supportsSourceType(Class<?> sourceType)
方法,判断是否是支持的事件来源。代码如下:
// LoggingApplicationListener.java |
2.3 onApplicationEvent
实现 #onApplicationEvent(ApplicationEvent event)
方法,处理事件。代码如下:
// LoggingApplicationListener.java |
- 不同的事件,对应不同的处理方法。下文,我们一一来看。
2.4 onApplicationStartingEvent
#onApplicationStartingEvent(ApplicationStartingEvent event)
方法,代码如下:
// LoggingApplicationListener.java |
-
<1>
处,调用LoggingSystem#get(ClassLoader classLoader)
方法,创建(获得) LoggingSystem 对象。关于这个,可以先看看 「3.1 get」 小节。- 通过 LoggingSystem 的抽象,对应不同日志框架对应的 LoggingSystem 实现,达到方便透明的接入不同的日志框架~
-
<2>
处,调用LoggingSystem#beforeInitialize()
方法,执行 LoggingSystem 的初始化的前置处理。关于这个,可以先看看 「3.2 beforeInitialize」 小节。
2.5 onApplicationEnvironmentPreparedEvent
#onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event)
方法,代码如下:
// LoggingApplicationListener.java |
-
<1>
处,调用LoggingSystemProperties#apply()
方法,初始化 LoggingSystemProperties 配置。关于这个,可以先看看 「4. LoggingSystemProperties」 小节。 -
<2>
处,调用LogFile#get(environment)
方法,创建(获得)LogFile 。关于这个,可以先看看 「5. LogFile」 小节。-
<2.1>
处,调用LogFile#applyToSystemProperties()
方法,应用LogFile.path
和LogFile.file
到系统属性中。
-
-
<3>
处,调用#initializeEarlyLoggingLevel(ConfigurableEnvironment environment)
方法,初始化早期的 Spring Boot Logging 级别。详细解析,见 「2.5.1 initializeEarlyLoggingLevel」 中。 -
<4>
处,调用#initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile)
方法,初始化 LoggingSystem 日志系统。详细解析,见 「2.5.2 initializeSystem」 中。 -
<5>
处,调用#initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system)
方法,初始化最终的 Spring Boot Logging 级别。详细解析,见 「2.5.3 initializeFinalLoggingLevels」 中。 -
<6>
处,调用#registerShutdownHookIfNecessary(Environment environment, LoggingSystem loggingSystem)
方法,注册 ShutdownHook 。详细解析,见 「2.5.4」 中。
2.5.1 initializeEarlyLoggingLevel
#initializeEarlyLoggingLevel(ConfigurableEnvironment environment)
方法,初始化早期的 Spring Boot Logging 级别。代码如下:
// LoggingApplicationListener.java |
- 可以通过在启动 jar 的时候,跟上
--debug
或--trace
。 - 也可以在配置文件中,添加
debug=true
或trace=true
。 - 关于日志级别,可以先看看 「6. LogLevel」 。
2.5.2 initializeSystem
#initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile)
方法,初始化 LoggingSystem 日志系统。代码如下:
// LoggingApplicationListener.java |
-
<1>
处,创建 LoggingInitializationContext 对象。其中,org.springframework.boot.logging.LoggingInitializationContext
,LoggingSystem 初始化时的 Context 。代码如下:// LoggingInitializationContext.java
public class LoggingInitializationContext {
private final ConfigurableEnvironment environment;
public LoggingInitializationContext(ConfigurableEnvironment environment) {
this.environment = environment;
}
public Environment getEnvironment() {
return this.environment;
}
}- 虽然目前只有
environment
属性。但是未来可以在后面增加新的参数,而无需改动LoggingSystem#initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法。
- 虽然目前只有
-
<2>
处,从environment
中获得"logging.config"
,即获得日志组件的配置文件。一般情况下,我们无需配置。因为根据不同的日志系统,Spring Boot 按如下“约定规则”组织配置文件名加载日志配置文件:
Logback | logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy |
Log4j | log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml |
Log4j2 | log4j2-spring.xml, log4j2.xml |
JDK (Java Util Logging) | logging.properties |
-
<3>
和<4>
处,调用LoggingSystem#initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法,初始化 LoggingSystem 日志系统。详细解析,可以先看看 「3.3 initialize」 。 -
<3>
和<4>
处,差异点在于后者多了ResourceUtils.getURL(logConfig).openStream().close()
代码块,看着有点奇怪哟?它的作用是,尝试去加载logConfig
对应的配置文件,看看是否真的存在~
2.5.3 initializeFinalLoggingLevels
#initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system)
方法,初始化最终的 Spring Boot Logging 级别。代码如下:
// LoggingApplicationListener.java |
-
<1>
处,如果springBootLogging
非空,则调用#initializeLogLevel(LoggingSystem system, LogLevel level)
方法,设置日志级别。代码如下:// LoggingApplicationListener.java
private static final Map<LogLevel, List<String>> LOG_LEVEL_LOGGERS;
static {
MultiValueMap<LogLevel, String> loggers = new LinkedMultiValueMap<>();
loggers.add(LogLevel.DEBUG, "sql");
loggers.add(LogLevel.DEBUG, "web");
loggers.add(LogLevel.DEBUG, "org.springframework.boot");
loggers.add(LogLevel.TRACE, "org.springframework");
loggers.add(LogLevel.TRACE, "org.apache.tomcat");
loggers.add(LogLevel.TRACE, "org.apache.catalina");
loggers.add(LogLevel.TRACE, "org.eclipse.jetty");
loggers.add(LogLevel.TRACE, "org.hibernate.tool.hbm2ddl");
LOG_LEVEL_LOGGERS = Collections.unmodifiableMap(loggers);
}
protected void initializeLogLevel(LoggingSystem system, LogLevel level) {
List<String> loggers = LOG_LEVEL_LOGGERS.get(level);
if (loggers != null) {
for (String logger : loggers) {
system.setLogLevel(logger, level);
}
}
}- 遍历的
loggers
,是LOG_LEVEL_LOGGERS
中对应的level
的值。 - 调用
LoggingSystem#setLogLevel(String loggerName, LogLevel level)
方法,设置指定loggerName
的日志级别。详细解析,见 「3.4 setLogLevel」 。
- 遍历的
-
<2>
处,调用#setLogLevels(LoggingSystem system, Environment environment)
方法,设置 environment 中配置的日志级别。代码如下:// LoggingApplicationListener.java
private static final ConfigurationPropertyName LOGGING_LEVEL = ConfigurationPropertyName.of("logging.level");
private static final ConfigurationPropertyName LOGGING_GROUP = ConfigurationPropertyName.of("logging.group");
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable.mapOf(String.class, String.class);
private static final Bindable<Map<String, String[]>> STRING_STRINGS_MAP = Bindable.mapOf(String.class, String[].class);
protected void setLogLevels(LoggingSystem system, Environment environment) {
if (!(environment instanceof ConfigurableEnvironment)) {
return;
}
// 创建 Binder 对象
Binder binder = Binder.get(environment);
// <1> 获得日志分组的集合
Map<String, String[]> groups = getGroups(); // <1.1>
binder.bind(LOGGING_GROUP, STRING_STRINGS_MAP.withExistingValue(groups)); // <1.2>
// <2> 获得日志级别的集合
Map<String, String> levels = binder.bind(LOGGING_LEVEL, STRING_STRING_MAP).orElseGet(Collections::emptyMap);
// <3> 遍历 levels 集合,逐个设置日志级别
levels.forEach((name, level) -> {
String[] groupedNames = groups.get(name);
if (ObjectUtils.isEmpty(groupedNames)) {
setLogLevel(system, name, level);
} else {
setLogLevel(system, groupedNames, level);
}
});
}-
<1>
处,获得日志分组的集合。-
<1.1>
处,调用#getGroups()
方法,获得默认的日志分组集合。代码如下:// LoggingApplicationListener.java
private static final Map<String, List<String>> DEFAULT_GROUP_LOGGERS;
static {
MultiValueMap<String, String> loggers = new LinkedMultiValueMap<>();
loggers.add("web", "org.springframework.core.codec");
loggers.add("web", "org.springframework.http");
loggers.add("web", "org.springframework.web");
loggers.add("web", "org.springframework.boot.actuate.endpoint.web");
loggers.add("web", "org.springframework.boot.web.servlet.ServletContextInitializerBeans");
loggers.add("sql", "org.springframework.jdbc.core");
loggers.add("sql", "org.hibernate.SQL");
DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers);
}
private Map<String, String[]> getGroups() {
Map<String, String[]> groups = new LinkedHashMap<>();
DEFAULT_GROUP_LOGGERS.forEach(
(name, loggers) -> groups.put(name, StringUtils.toStringArray(loggers)));
return groups;
}- 实际上,就是把我们日常配置的
loggerName
进行了分组。默认情况下,内置了sql
、web
分组。
- 实际上,就是把我们日常配置的
-
<1.2>
处,从environment
中读取logging.group
配置的日志分组。举个例子,在配置文件里增加logging.group.demo=xxx.Dog,yyy.Cat
。
-
-
<2>
处,从environment
中读取logging.level
配置的日志分组。举两个例子,在配置文件里添加:logging.level.web=INFO
logging.level.xxx.Dog=INFO
-
<3>
处,遍历levels
集合,逐个设置日志级别。涉及的方法,代码如下:// LoggingApplicationListener.java
private void setLogLevel(LoggingSystem system, String[] names, String level) {
// 遍历 names 数组
for (String name : names) {
setLogLevel(system, name, level);
}
}
private void setLogLevel(LoggingSystem system, String name, String level) {
try {
// 获得 loggerName
name = name.equalsIgnoreCase(LoggingSystem.ROOT_LOGGER_NAME) ? null : name;
// 设置日志级别
system.setLogLevel(name, coerceLogLevel(level));
} catch (RuntimeException ex) {
this.logger.error("Cannot set level '" + level + "' for '" + name + "'");
}
}
/**
* @param level 日志级别字符串
* @return 将字符串转换成 {@link LogLevel}
*/
private LogLevel coerceLogLevel(String level) {
String trimmedLevel = level.trim();
if ("false".equalsIgnoreCase(trimmedLevel)) { // false => OFF
return LogLevel.OFF;
}
return LogLevel.valueOf(trimmedLevel.toUpperCase(Locale.ENGLISH));
}- 比较简单,胖友瞅瞅~
-
2.5.4 registerShutdownHookIfNecessary
#registerShutdownHookIfNecessary(Environment environment, LoggingSystem loggingSystem)
方法,注册 ShutdownHook 。代码如下:
// LoggingApplicationListener.java |
-
<X>
处,所注册的 ShutdownHook ,通过调用LoggingSystem#getShutdownHandler()
方法,进行获得。详细解析,见 「3.5 getShutdownHandler」。
2.6 onApplicationPreparedEvent
#onApplicationPreparedEvent(ApplicationPreparedEvent event)
方法,代码如下:
// LoggingApplicationListener.java |
- 将创建的 LoggingSystem 对象,注册到 Spring 容器中。
2.7 onContextClosedEvent
#onContextClosedEvent()
方法,代码如下:
// LoggingApplicationListener.java |
- 调用
LoggingSystem#cleanUp()
方法,执行清理。详细解析,见 「3.6 cleanUp」 中。
2.8 onApplicationFailedEvent
#onApplicationFailedEvent()
方法,代码如下:
// LoggingApplicationListener.java |
至此,我们需要来看看 LoggingSystem 的实现类。具体的,可以跳到 「7. LoggingSystem 的实现类」 中。
3. LoggingSystem
org.springframework.boot.logging.LoggingSystem
,日志系统抽象类。每个日志框架,都会对应一个实现类。如下图所示:
3.1 get
#get(ClassLoader classLoader)
方法,创建(获得) LoggingSystem 对象。代码如下:
// LoggingApplicationListener.java |
-
<1>
处,从系统参数org.springframework.boot.logging.LoggingSystem
获得 loggingSystem 类型。 -
<2>
处,如果非空,说明配置了。-
<2.1>
处,如果是none
,则创建 NoOpLoggingSystem 对象。 -
<2.2>
处,调用#get(ClassLoader classLoader, String loggingSystemClass)
方法,获得loggingSystem
对应的 LoggingSystem 类,进行创建对象。代码如下:// LoggingSystem.java
private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
try {
Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
return (LoggingSystem) systemClass.getConstructor(ClassLoader.class).newInstance(classLoader);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}-
systemClass
中的 VALUES ,就是loggingSystem
对应的类。
-
-
-
<3>
处,如果为空,说明未配置,则顺序查找SYSTEMS
中的类。如果存在指定类,则创建该类。
3.2 beforeInitialize
#beforeInitialize()
抽象方法,初始化的前置方法。代码如下:
// LoggingSystem.java |
3.3 initialize
#initialize()
方法,初始化。代码如下:
// LoggingSystem.java |
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
3.4 setLogLevel
#setLogLevel(String loggerName, LogLevel level)
方法,设置指定 loggerName
的日志级别。代码如下:
// LoggingSystem.java |
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
3.5 getShutdownHandler
#getShutdownHandler()
方法,获得 ShutdownHook 的 Runnable 对象。代码如下:
// LoggingSystem.java |
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
3.6 cleanUp
#cleanUp()
方法,清理。代码如下:
// LoggingSystem.java |
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
4. LoggingSystemProperties
org.springframework.boot.logging.LoggingSystemProperties
,LoggingSystem 的配置类。
4.1 构造方法
// LoggingSystemProperties.java |
4.2 apply
#apply()
方法,解析 environment
的配置变量到系统属性中。代码如下:
// LoggingSystemProperties.java |
-
<1>
处,调用#getPropertyResolver()
方法,获得 PropertyResolver 对象。代码如下:// LoggingSystemProperties.java
private PropertyResolver getPropertyResolver() {
if (this.environment instanceof ConfigurableEnvironment) {
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(((ConfigurableEnvironment) this.environment).getPropertySources());
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
return resolver;
}
return this.environment;
}
-
<2>
处,调用#setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName)
方法,解析配置文件到系统属性中。代码如下:// LoggingSystemProperties.java
public static final String PID_KEY = "PID";
public static final String EXCEPTION_CONVERSION_WORD = "LOG_EXCEPTION_CONVERSION_WORD";
public static final String LOG_FILE = "LOG_FILE";
public static final String LOG_PATH = "LOG_PATH";
public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN";
public static final String FILE_MAX_HISTORY = "LOG_FILE_MAX_HISTORY";
public static final String FILE_MAX_SIZE = "LOG_FILE_MAX_SIZE";
public static final String LOG_LEVEL_PATTERN = "LOG_LEVEL_PATTERN";
public static final String LOG_DATEFORMAT_PATTERN = "LOG_DATEFORMAT_PATTERN";
private void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName) {
setSystemProperty(systemPropertyName, resolver.getProperty("logging." + propertyName)); // <X>
}
private void setSystemProperty(String name, String value) {
if (System.getProperty(name) == null && value != null) {
System.setProperty(name, value);
}
}-
<X>
处,读取的是environment
中的logging.
开头的配置属性。
-
5. LogFile
org.springframework.boot.logging.LogFile
,日志文件。
5.1 构造方法
// LogFile.java |
5.2 applyToSystemProperties
#applyToSystemProperties()
方法,应用 file
、path
到系统属性。代码如下:
// LogFile.java |
-
#toString()
方法,返回文件名。代码如下:// LogFile.java
public String toString() {
if (StringUtils.hasLength(this.file)) {
return this.file;
}
return new File(this.path, "spring.log").getPath();
}
-
#put(Properties properties, String key, String value)
方法,添加属性值到系统属性。代码如下:// LogFile.java
private void put(Properties properties, String key, String value) {
if (StringUtils.hasLength(value)) {
properties.put(key, value);
}
}
5.3 get
#get(PropertyResolver propertyResolver)
方法,获得(创建)LogFile 对象。代码如下:
// LogFile.java |
-
<1>
处,调用#getLogFileProperty(PropertyResolver propertyResolver, String propertyName, String deprecatedPropertyName)
方法,获得file
和path
属性。代码如下:// LogFile.java
private static String getLogFileProperty(PropertyResolver propertyResolver, String propertyName, String deprecatedPropertyName) {
String property = propertyResolver.getProperty(propertyName);
if (property != null) {
return property;
}
return propertyResolver.getProperty(deprecatedPropertyName);
}
-
<2>
处,创建 LogFile 对象。
6. LogLevel
org.springframework.boot.logging.LogLevel
,Spring Boot 日志枚举类。代码如下:
// LogLevel.java |
每个日志框架,都有其日志级别。通过 LogLevel 枚举类,和它们映射。
7. LoggingSystem 的实现类
7.1 NoOpLoggingSystem
NoOpLoggingSystem ,是 LoggingSystem 的内部静态类,继承 LoggingSystem 类,空操作的 LoggingSystem 实现类,用于禁用日志系统的时候。代码如下:
// LoggingSystem#NoOpLoggingSystem.java |
7.2 AbstractLoggingSystem
org.springframework.boot.logging.AbstractLoggingSystem
,继承 LoggingSystem 抽象类,是 LoggingSystem 的抽象基类。
7.2.1 构造方法
// AbstractLoggingSystem.java |
7.2.2 initialize
实现 #initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法,提供模板化的初始化逻辑。代码如下:
// AbstractLoggingSystem.java |
-
<1>
处,有自定义的配置文件,则调用#initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法,使用指定配置文件进行初始化。详细解析,见 「7.2.2.1 initializeWithSpecificConfig」 。 -
<2>
处,无自定义的配置文件,则调用#initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile)
方法,使用约定配置文件进行初始化。详细解析,见 7.2.2.2 initializeWithConventions 。
7.2.2.1 initializeWithSpecificConfig
#initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法,使用指定配置文件进行初始化。代码如下:
// AbstractLoggingSystem.java |
-
<1>
处,获得配置文件(可能有占位符)。 -
<2>
处,调用#loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile))
抽象方法,加载配置文件。代码如下:// AbstractLoggingSystem.java
/**
* Load a specific configuration.
* @param initializationContext the logging initialization context
* @param location the location of the configuration to load (never {@code null})
* @param logFile the file to load or {@code null} if no log file is to be written
*/
protected abstract void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile);
7.2.2.2 initializeWithConventions
#initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile)
方法,使用约定配置文件进行初始化。代码如下:
// AbstractLoggingSystem.java |
-
<1>
处,调用#getSelfInitializationConfig()
方法,获得约定配置文件。代码如下:// AbstractLoggingSystem.java
protected String getSelfInitializationConfig() {
return findConfig(getStandardConfigLocations());
}
protected abstract String[] getStandardConfigLocations();
private String findConfig(String[] locations) {
// 遍历 locations 数组,逐个判断是否存在。若存在,则返回
for (String location : locations) {
ClassPathResource resource = new ClassPathResource(location, this.classLoader);
if (resource.exists()) {
return "classpath:" + location;
}
}
return null;
}-
#getStandardConfigLocations()
抽象方法,获得约定的配置文件。例如说:LogbackLoggingSystem 返回的是"logback-test.groovy"
、"logback-test.xml"
、"logback.groovy"
、"logback.xml"
。
-
-
<2>
处,如果获取到,结果logFile
为空,则调用#reinitialize(LoggingInitializationContext initializationContext)
方法,重新初始化。代码如下:// AbstractLoggingSystem.java
/**
* Reinitialize the logging system if required. Called when
* {@link #getSelfInitializationConfig()} is used and the log file hasn't changed. May
* be used to reload configuration (for example to pick up additional System
* properties).
* @param initializationContext the logging initialization context
*/
protected void reinitialize(LoggingInitializationContext initializationContext) {
}- 一般情况下,
logFile
非空~
- 一般情况下,
-
<3>
处,如果获取不到,则调用#getSpringInitializationConfig()
方法,尝试获得约定配置文件(带-spring
后缀)。代码如下:// AbstractLoggingSystem.java
/**
* Return any spring specific initialization config that should be applied. By default
* this method checks {@link #getSpringConfigLocations()}.
* @return the spring initialization config or {@code null}
*/
protected String getSpringInitializationConfig() {
return findConfig(getSpringConfigLocations());
}
/**
* Return the spring config locations for this system. By default this method returns
* a set of locations based on {@link #getStandardConfigLocations()}.
* @return the spring config locations
* @see #getSpringInitializationConfig()
*/
protected String[] getSpringConfigLocations() {
String[] locations = getStandardConfigLocations();
for (int i = 0; i < locations.length; i++) {
String extension = StringUtils.getFilenameExtension(locations[i]);
// 在文件名和后缀之间,拼接一个
locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1)
+ "-spring." + extension;
}
return locations;
}- 例如说:LogbackLoggingSystem 返回的是
"logback-test-spring.groovy"
、"logback-test-spring.xml"
、"logback-spring.groovy"
、"logback-spring.xml"
。
- 例如说:LogbackLoggingSystem 返回的是
-
<4>
处,如果获取到,则调用#loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile))
抽象方法,加载配置文件。 -
<5>
处,如果获取不到,则调用#loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile)
抽象方法,加载默认配置。代码如下:// AbstractLoggingSystem.java
/**
* Load sensible defaults for the logging system.
* @param initializationContext the logging initialization context
* @param logFile the file to load or {@code null} if no log file is to be written
*/
protected abstract void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile);
7.2.3 LogLevels
LogLevels ,是 AbstractLoggingSystem 的内部静态类,用于 Spring Boot LogLevel 和日志框架的 LogLevel 做映射。代码如下:
// AbstractLoggingSystem#LogLevels.java |
7.3 Slf4JLoggingSystem
org.springframework.boot.logging.Slf4JLoggingSystem
,继承 AbstractLoggingSystem 抽象类,基于 Slf4J 的 LoggingSystem 的抽象基类。
7.3.1 beforeInitialize
重写 #beforeInitialize()
方法,代码如下:
// Slf4JLoggingSystem.java |
- 因为艿艿没有特别完整的了解过日志框架,所以下面的解释,更多凭的是“直觉”!如果有错误的地方,给艿艿星球留言哈~
-
<1>
处,调用#configureJdkLoggingBridgeHandler()
方法,配置 JUL 的桥接处理器。详细解析,见 「7.3.1.1 configureJdkLoggingBridgeHandler」 。
7.3.1.1 configureJdkLoggingBridgeHandler
#configureJdkLoggingBridgeHandler()
方法,配置 JUL 的桥接处理器。代码如下:
// Slf4JLoggingSystem.java |
-
<1>
处,调用#isBridgeJulIntoSlf4j()
方法,判断 JUL 是否桥接到 SLF4J 了。代码如下:// Slf4JLoggingSystem.java
private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler";
/**
* Return whether bridging JUL into SLF4J or not.
* @return whether bridging JUL into SLF4J or not
* @since 2.0.4
*/
protected final boolean isBridgeJulIntoSlf4j() {
return isBridgeHandlerAvailable() // 判断是否存在 SLF4JBridgeHandler 类
&& isJulUsingASingleConsoleHandlerAtMost(); // 判断是否 JUL 只有 ConsoleHandler 处理器被创建了
}
protected final boolean isBridgeHandlerAvailable() {
return ClassUtils.isPresent(BRIDGE_HANDLER, getClassLoader());
}
private boolean isJulUsingASingleConsoleHandlerAtMost() {
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler[] handlers = rootLogger.getHandlers();
return handlers.length == 0
|| (handlers.length == 1 && handlers[0] instanceof ConsoleHandler);
}- 第一个方法,调用后面的两个方法判断~
-
<2>
处,调用#removeJdkLoggingBridgeHandler()
方法,移除 JUL 桥接处理器。代码如下:// Slf4JLoggingSystem.java
private void removeJdkLoggingBridgeHandler() {
try {
// 移除 JUL 的 ConsoleHandler
removeDefaultRootHandler();
// 卸载 SLF4JBridgeHandler
SLF4JBridgeHandler.uninstall();
} catch (Throwable ex) {
// Ignore and continue
}
}
private void removeDefaultRootHandler() {
try {
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler[] handlers = rootLogger.getHandlers();
if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) {
rootLogger.removeHandler(handlers[0]);
}
} catch (Throwable ex) {
// Ignore and continue
}
}- 移除 JUL 的 ConsoleHandler ,卸载 SLF4JBridgeHandler 。
-
<3>
处,会重新安装 SLF4JBridgeHandler。
7.3.2 cleanUp
重写 #cleanUp()
方法,代码如下:
// Slf4JLoggingSystem.java |
7.3.3 loadConfiguration
重写 #loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile)
方法,代码如下:
// Slf4JLoggingSystem.java |
- 调用
#applySystemProperties(Environment environment, LogFile logFile)
方法,应用environment
和logFile
的属性,到系统属性种。在 「4.2 apply」 中,已经详细解析。 - 不过有一点,搞不懂,为什么这么实现。
7.4 LogbackLoggingSystem
org.springframework.boot.logging.logback.LogbackLoggingSystem
,继承 Slf4JLoggingSystem 抽象类,基于 Logback 的 LoggingSystem 实现类。
7.4.1 beforeInitialize
重写 #beforeInitialize()
方法,代码如下:
// LogbackLoggingSystem.java |
-
<1.1>
处,调用#getLoggerContext()
方法,获得 LoggerContext 对象。代码如下:// LogbackLoggingSystem.java
private LoggerContext getLoggerContext() {
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
Assert.isInstanceOf(LoggerContext.class, factory,
String.format(
"LoggerFactory is not a Logback LoggerContext but Logback is on "
+ "the classpath. Either remove Logback or the competing "
+ "implementation (%s loaded from %s). If you are using "
+ "WebLogic you will need to add 'org.slf4j' to "
+ "prefer-application-packages in WEB-INF/weblogic.xml",
factory.getClass(), getLocation(factory)));
return (LoggerContext) factory;
}
-
<1.2>
处,调用#isAlreadyInitialized(LoggerContext loggerContext)
方法,判断如果已经初始化过,则直接返回。代码如下:// LogbackLoggingSystem.java
private boolean isAlreadyInitialized(LoggerContext loggerContext) {
return loggerContext.getObject(LoggingSystem.class.getName()) != null;
}
-
<2>
处,调用父方法。 -
<3>
处,添加FILTER
到loggerContext
其中。代码如下:// LogbackLoggingSystem.java
private static final TurboFilter FILTER = new TurboFilter() {
public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger,
Level level, String format, Object[] params, Throwable t) {
return FilterReply.DENY;
}
};- 因为此时,Logback 并未初始化好,所以全部返回
FilterReply.DENY
。即,先不打印日志。
- 因为此时,Logback 并未初始化好,所以全部返回
7.4.2 getStandardConfigLocations
实现 #getStandardConfigLocations()
方法,获得约定的配置文件的数组。代码如下:
// LogbackLoggingSystem.java |
7.4.3 initialize
重写 #initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法,代码如下:
// LogbackLoggingSystem.java |
-
<1>
处,如果已经初始化,则返回。 -
<2>
处,调用父方法,进行初始化。 -
<3>
处,从loggerContext
中,移除FILTER
。发表评论
相关推荐
《Spring5 源码分析(第 2 版)》是针对Spring框架第五个主要版本的深度解析著作,由知名讲师倾心打造,旨在帮助读者深入理解Spring框架的内部工作机制,提升对Java企业级应用开发的专业技能。本书涵盖了Spring框架的...
5. 监控与日志:Spring Boot集成了Actuator,可以对应用性能进行监控,同时Spring Boot的日志管理方便了问题排查和系统优化。 四、LIMS系统核心模块 1. 样本管理:系统的核心功能,包括样本的登记、分类、编码、...
总的来说,《Spring高级源码分析》将带你深入Spring的各个模块,从IoC容器到AOP,从MVC到Data访问,再到Boot的自动化配置。通过对源码的深度探索,你将能够更好地理解和利用Spring框架,提升自己的开发技能。
结合源码分析和工具使用,如IDEA的数据库插件,可以进一步提升开发效率和对Spring数据访问机制的理解。 总的来说,Spring框架提供了强大的日志管理和数据库集成能力,使得开发者能够专注于业务逻辑,而非底层细节。...
在源码分析中,我们首先看到`prepareContext`方法被调用,这是`SpringApplication`类中的一个重要方法,它负责初始化`ApplicationContext`(应用上下文)并为后续的bean加载做准备。 `prepareContext`方法做了以下...
《基于Spring Boot的疫情信息管理系统设计与实现》 在当今社会,信息的快速传播和有效管理对于应对公共卫生事件,如疫情,至关重要。本系统利用先进的技术栈,特别是Spring Boot框架,构建了一个集用户互动和管理员...
《Spring Boot企业微信CAN系统构建详解》 在现代企业级应用开发中,Spring Boot以其简洁高效的特点成为首选框架之一。本教程将深入探讨如何利用Spring Boot构建一个与微信深度集成的企业级CAN(Customer Activity ...
Spring Boot是Java后端开发领域中的一个热门框架,它极大地简化了Spring应用的初始搭建以及开发过程。Spring Boot的核心设计...通过阅读和分析源码,你将能够更深入地掌握Spring Boot,从而在实际开发中更加得心应手。
根据提供的文件信息,我们可以推断出这是一份关于Java毕业设计项目的文档,主要涉及Spring Boot技术栈,并且针对的是一个类似企业微信的功能模块——“点can系统”。下面将基于这个项目的信息来提炼出相关的知识点。...
本项目是一个Java毕业设计,它基于Spring Boot框架构建了一个在线招标网站。这个系统的主要目标是为用户提供一个方便、...通过学习和分析这个项目,学生不仅可以深化对Spring Boot的理解,还能提升实际项目开发的能力。
本视频教程将带你深入理解Spring Boot的核心特性,并结合源码分析,提升你的技能水平。 首先,我们来探讨Spring Boot的初始化机制。Spring Boot启动时,会自动扫描`@SpringBootApplication`注解所在的包及其子包下...
这个项目提供了从需求分析到实际开发的完整实例,适合学习Spring Boot集成第三方库以及如何构建企业级的审批流程管理系统。通过对源代码的深入研究,开发者可以学习到如何在实际场景中应用上述技术。
书中可能包含如何阅读和分析Spring源码的方法和技巧。 10. **工具的使用**: 除了理论知识,本书可能还会介绍一些辅助开发的工具,如IDE集成、构建工具Maven或Gradle,以及调试和测试工具的使用方法。 在提供的...
通过`spring-boot-starter-actuator`起步依赖,可以查看应用的健康状况、指标、日志等。 8. **测试支持**: SpringBoot内置了测试支持,`@SpringBootTest`注解可以启动整个应用上下文进行集成测试,而`@WebMvcTest...
Spring框架是Java开发中最常用的...对于初学者,通过阅读和分析这些源码,可以加深对Spring的理解,提升开发技能。同时,文档则可能包含Spring的使用指南、最佳实践以及常见问题解答,是学习和解决问题的重要参考资料。
这些切面可以在不修改原有代码的情况下被插入到系统中,如日志、事务管理等。AOP通过代理模式实现,Spring支持JDK动态代理和CGLIB字节码生成两种方式。 再来,Spring MVC是Spring用于构建Web应用的模块,它提供了...
《Spring Boot源码深度解析——以demospringboot为例》 Spring Boot作为现代Java开发的首选框架,以其简洁、高效和快速启动的特点深受开发者喜爱。本文将以“demospringboot”项目为例,深入剖析Spring Boot的源码...
通过深入理解源码,可以掌握Web开发技术,如前端框架(如React、Vue)、后端框架(如Spring Boot)、数据库操作(如SQL)、权限控制机制(如JWT)等。此外,还可以学习到如何处理并发选课、如何优化数据库查询效率等...
Spring 源码分析对于理解其内部工作原理、优化应用性能以及进行二次开发至关重要。让我们深入探讨 Spring 框架的核心组成部分和主要特性。 1. **依赖注入(Dependency Injection, DI)** Spring 的核心特性之一是...
源码分析可以帮助我们理解如何配置Spring Security,保护应用程序免受未经授权的访问。 10. **测试支持**:Spring提供强大的单元测试和集成测试支持,如MockMVC、TestContext框架等。通过源码,我们可以了解如何...