76 KiB
精尽 Spring Boot 源码分析 —— 日志系统
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
private static final Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class,
ContextClosedEvent.class, ApplicationFailedEvent.class };
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
}
private boolean isAssignableFrom(Class<?> type, Class<?>... supportedTypes) {
if (type != null) {
for (Class<?> supportedType : supportedTypes) {
if (supportedType.isAssignableFrom(type)) {
return true;
}
}
}
return false;
}
2.2 supportsSourceType
实现 #supportsSourceType(Class<?> sourceType)
方法,判断是否是支持的事件来源。代码如下:
// LoggingApplicationListener.java
private static final Class<?>[] SOURCE_TYPES = { SpringApplication.class,
ApplicationContext.class };
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return isAssignableFrom(sourceType, SOURCE_TYPES);
}
2.3 onApplicationEvent
实现 #onApplicationEvent(ApplicationEvent event)
方法,处理事件。代码如下:
// LoggingApplicationListener.java
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 在 Spring Boot 应用启动的时候
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
// 在 Spring Boot 的 Environment 环境准备完成的时候
} else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
// 在 Spring Boot 容器的准备工作已经完成(并未启动)的时候
} else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
// 在 Spring Boot 容器关闭的时候
} else if (event instanceof ContextClosedEvent
&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
onContextClosedEvent();
// 在 Spring Boot 容器启动失败的时候
} else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
- 不同的事件,对应不同的处理方法。下文,我们一一来看。
2.4 onApplicationStartingEvent
#onApplicationStartingEvent(ApplicationStartingEvent event)
方法,代码如下:
// LoggingApplicationListener.java
private LoggingSystem loggingSystem;
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
// <1> 创建 LoggingSystem 对象
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
// <2> LoggingSystem 的初始化的前置处理
this.loggingSystem.beforeInitialize();
}
-
<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
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> 初始化 LoggingSystemProperties 配置
new LoggingSystemProperties(environment).apply();
// <2> 初始化 LogFile
LogFile logFile = LogFile.get(environment);
if (logFile != null) {
logFile.applyToSystemProperties(); // <2.1>
}
// <3> 初始化早期的 Spring Boot Logging 级别
initializeEarlyLoggingLevel(environment);
// <4> 初始化 LoggingSystem 日志系统
initializeSystem(environment, this.loggingSystem, logFile);
// <5> 初始化最终的 Spring Boot Logging 级别
initializeFinalLoggingLevels(environment, this.loggingSystem);
// <6>
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
-
<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
private boolean parseArgs = true;
private LogLevel springBootLogging = null;
private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) {
if (this.parseArgs && this.springBootLogging == null) {
if (isSet(environment, "debug")) {
this.springBootLogging = LogLevel.DEBUG;
}
if (isSet(environment, "trace")) {
this.springBootLogging = LogLevel.TRACE;
}
}
}
private boolean isSet(ConfigurableEnvironment environment, String property) {
String value = environment.getProperty(property);
return (value != null && !value.equals("false"));
}
- 可以通过在启动 jar 的时候,跟上
--debug
或--trace
。 - 也可以在配置文件中,添加
debug=true
或trace=true
。 - 关于日志级别,可以先看看 「6. LogLevel」 。
2.5.2 initializeSystem
#initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile)
方法,初始化 LoggingSystem 日志系统。代码如下:
// LoggingApplicationListener.java
public static final String CONFIG_PROPERTY = "logging.config";
private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
// <1> 创建 LoggingInitializationContext 对象
LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
// <2> 获得日志组件的配置文件
String logConfig = environment.getProperty(CONFIG_PROPERTY);
// <3> 如果没配置,则直接初始化 LoggingSystem
if (ignoreLogConfig(logConfig)) {
system.initialize(initializationContext, null, logFile);
// <3> 如果有配置,先尝试加载指定配置文件,然后在初始化 LoggingSystem
} else {
try {
ResourceUtils.getURL(logConfig).openStream().close();
system.initialize(initializationContext, logConfig, logFile); // <X>
} 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);
}
}
}
-
<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
private void initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) {
// <1> 如果 springBootLogging 非空,则设置到日志级别
if (this.springBootLogging != null) {
initializeLogLevel(system, this.springBootLogging);
}
// <2> 设置 environment 中配置的日志级别
setLogLevels(system, environment);
}
-
<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
/**
* The name of the Spring property that controls the registration of a shutdown hook
* to shut down the logging system when the JVM exits.
* @see LoggingSystem#getShutdownHandler
*/
public static final String REGISTER_SHUTDOWN_HOOK_PROPERTY = "logging.register-shutdown-hook";
private void registerShutdownHookIfNecessary(Environment environment, LoggingSystem loggingSystem) {
// 获得 logging.register-shutdown-hook 对应的配置值
boolean registerShutdownHook = environment.getProperty(REGISTER_SHUTDOWN_HOOK_PROPERTY, Boolean.class, false);
// 如果开启
if (registerShutdownHook) {
// <x> 获得 shutdownHandler 钩子
Runnable shutdownHandler = loggingSystem.getShutdownHandler();
// 注册 ShutdownHook(
if (shutdownHandler != null
&& shutdownHookRegistered.compareAndSet(false, true)) {
registerShutdownHook(new Thread(shutdownHandler));
}
}
}
void registerShutdownHook(Thread shutdownHook) {
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
<X>
处,所注册的 ShutdownHook ,通过调用LoggingSystem#getShutdownHandler()
方法,进行获得。详细解析,见 「3.5 getShutdownHandler」。
2.6 onApplicationPreparedEvent
#onApplicationPreparedEvent(ApplicationPreparedEvent event)
方法,代码如下:
// LoggingApplicationListener.java
/**
* The name of the {@link LoggingSystem} bean.
*/
public static final String LOGGING_SYSTEM_BEAN_NAME = "springBootLoggingSystem";
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);
}
}
- 将创建的 LoggingSystem 对象,注册到 Spring 容器中。
2.7 onContextClosedEvent
#onContextClosedEvent()
方法,代码如下:
// LoggingApplicationListener.java
private void onContextClosedEvent() {
if (this.loggingSystem != null) {
this.loggingSystem.cleanUp();
}
}
- 调用
LoggingSystem#cleanUp()
方法,执行清理。详细解析,见 「3.6 cleanUp」 中。
2.8 onApplicationFailedEvent
#onApplicationFailedEvent()
方法,代码如下:
// LoggingApplicationListener.java
private void onApplicationFailedEvent() {
if (this.loggingSystem != null) {
this.loggingSystem.cleanUp();
}
}
至此,我们需要来看看 LoggingSystem 的实现类。具体的,可以跳到 「7. LoggingSystem 的实现类」 中。
3. LoggingSystem
org.springframework.boot.logging.LoggingSystem
,日志系统抽象类。每个日志框架,都会对应一个实现类。如下图所示:LoggingSystem 实现类
3.1 get
#get(ClassLoader classLoader)
方法,创建(获得) LoggingSystem 对象。代码如下:
// LoggingApplicationListener.java
/**
* A System property that can be used to indicate the {@link LoggingSystem} to use.
*/
public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
/**
* The value of the {@link #SYSTEM_PROPERTY} that can be used to indicate that no
* {@link LoggingSystem} should be used.
*/
public static final String NONE = "none";
private static final Map<String, String> SYSTEMS;
static {
Map<String, String> 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);
}
public static LoggingSystem get(ClassLoader classLoader) {
// <1> 从系统参数 org.springframework.boot.logging.LoggingSystem 获得 loggingSystem 类型
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
// <2> 如果非空,说明配置了
if (StringUtils.hasLength(loggingSystem)) {
// <2.1> 如果是 none ,则创建 NoOpLoggingSystem 对象
if (NONE.equals(loggingSystem)) {
return new NoOpLoggingSystem();
}
// <2.2> 获得 loggingSystem 对应的 LoggingSystem 类,进行创建对象
return get(classLoader, loggingSystem);
}
// <3> 如果为空,说明未配置,则顺序查找 SYSTEMS 中的类。如果存在指定类,则创建该类。
return SYSTEMS.entrySet().stream()
.filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
.map((entry) -> get(classLoader, entry.getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
}
-
<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
/**
* Reset the logging system to be limit output. This method may be called before
* {@link #initialize(LoggingInitializationContext, String, LogFile)} to reduce
* logging noise until the system has been fully initialized.
*/
public abstract void beforeInitialize();
3.3 initialize
#initialize()
方法,初始化。代码如下:
// LoggingSystem.java
/**
* Fully initialize the logging system.
* @param initializationContext the logging initialization context
* @param configLocation a log configuration location or {@code null} if default
* initialization is required
* @param logFile the log output file that should be written or {@code null} for
* console only output
*/
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
}
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
3.4 setLogLevel
#setLogLevel(String loggerName, LogLevel level)
方法,设置指定 loggerName
的日志级别。代码如下:
// LoggingSystem.java
/**
* Sets the logging level for a given logger.
* @param loggerName the name of the logger to set ({@code null} can be used for the
* root logger).
* @param level the log level ({@code null} can be used to remove any custom level for
* the logger and use the default configuration instead)
*/
public void setLogLevel(String loggerName, LogLevel level) {
throw new UnsupportedOperationException("Unable to set log level");
}
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
3.5 getShutdownHandler
#getShutdownHandler()
方法,获得 ShutdownHook 的 Runnable 对象。代码如下:
// LoggingSystem.java
/**
* Returns a {@link Runnable} that can handle shutdown of this logging system when the
* JVM exits. The default implementation returns {@code null}, indicating that no
* shutdown is required.
* @return the shutdown handler, or {@code null}
*/
public Runnable getShutdownHandler() {
return null;
}
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
3.6 cleanUp
#cleanUp()
方法,清理。代码如下:
// LoggingSystem.java
/**
* Clean up the logging system. The default implementation does nothing. Subclasses
* should override this method to perform any logging system-specific cleanup.
*/
public void cleanUp() {
}
- 目前是个空方法,需要子类来实现。
- 我们先不着急看子类的实现,等后面继续看。
4. LoggingSystemProperties
org.springframework.boot.logging.LoggingSystemProperties
,LoggingSystem 的配置类。
4.1 构造方法
// LoggingSystemProperties.java
private final Environment environment;
public LoggingSystemProperties(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
4.2 apply
#apply()
方法,解析 environment
的配置变量到系统属性中。代码如下:
// LoggingSystemProperties.java
public void apply() {
apply(null);
}
public void apply(LogFile logFile) {
// <1> 获得 PropertyResolver 对象
PropertyResolver resolver = getPropertyResolver();
// <2> 解析配置文件到系统属性中
setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "exception-conversion-word");
setSystemProperty(PID_KEY, new ApplicationPid().toString()); // 应用进程编号
setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "pattern.console");
setSystemProperty(resolver, FILE_LOG_PATTERN, "pattern.file");
setSystemProperty(resolver, FILE_MAX_HISTORY, "file.max-history");
setSystemProperty(resolver, FILE_MAX_SIZE, "file.max-size");
setSystemProperty(resolver, LOG_LEVEL_PATTERN, "pattern.level");
setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "pattern.dateformat");
// <3> 如果 logFile 非空,则应用配置
if (logFile != null) {
logFile.applyToSystemProperties();
}
}
-
<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
/**
* 文件名
*/
private final String file;
/**
* 文件路径
*/
private final String path;
5.2 applyToSystemProperties
#applyToSystemProperties()
方法,应用 file
、path
到系统属性。代码如下:
// LogFile.java
public static final String FILE_NAME_PROPERTY = "logging.file.name";
public static final String FILE_PATH_PROPERTY = "logging.file.path";
public void applyToSystemProperties() {
applyTo(System.getProperties());
}
public void applyTo(Properties properties) {
put(properties, LoggingSystemProperties.LOG_PATH, this.path);
put(properties, LoggingSystemProperties.LOG_FILE, toString());
}
-
#toString()
方法,返回文件名。代码如下:
// LogFile.java @Override 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
public static LogFile get(PropertyResolver propertyResolver) {
// <1> 获得 file 和 path 属性
String file = getLogFileProperty(propertyResolver, FILE_NAME_PROPERTY, FILE_PROPERTY);
String path = getLogFileProperty(propertyResolver, FILE_PATH_PROPERTY, PATH_PROPERTY);
// <2> 创建 LogFile 对象
if (StringUtils.hasLength(file) || StringUtils.hasLength(path)) {
return new LogFile(file, path);
}
return null;
}
-
<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
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
}
每个日志框架,都有其日志级别。通过 LogLevel 枚举类,和它们映射。
7. LoggingSystem 的实现类
7.1 NoOpLoggingSystem
NoOpLoggingSystem ,是 LoggingSystem 的内部静态类,继承 LoggingSystem 类,空操作的 LoggingSystem 实现类,用于禁用日志系统的时候。代码如下:
// LoggingSystem#NoOpLoggingSystem.java
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;
}
}
7.2 AbstractLoggingSystem
org.springframework.boot.logging.AbstractLoggingSystem
,继承 LoggingSystem 抽象类,是 LoggingSystem 的抽象基类。
7.2.1 构造方法
// AbstractLoggingSystem.java
private final ClassLoader classLoader;
public AbstractLoggingSystem(ClassLoader classLoader) {
this.classLoader = classLoader;
}
7.2.2 initialize
实现 #initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法,提供模板化的初始化逻辑。代码如下:
// AbstractLoggingSystem.java
@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
// <1> 有自定义的配置文件,则使用指定配置文件进行初始化
if (StringUtils.hasLength(configLocation)) {
initializeWithSpecificConfig(initializationContext, configLocation, logFile);
return;
}
// <2> 无自定义的配置文件,则使用约定配置文件进行初始化
initializeWithConventions(initializationContext, logFile);
}
<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
private void initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
// <1> 获得配置文件(可能有占位符)
configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
// <2> 加载配置文件
loadConfiguration(initializationContext, configLocation, logFile);
}
-
<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
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
// <1> 获得约定配置文件
String config = getSelfInitializationConfig();
// <2> 如果获取到,结果 logFile 为空,则重新初始化
if (config != null && logFile == null) {
// self initialization has occurred, reinitialize in case of property changes
reinitialize(initializationContext);
return;
}
// <3> 如果获取不到,则尝试获得约定配置文件(带 spring 后缀)
if (config == null) {
config = getSpringInitializationConfig();
}
// <4> 如果获取到,则加载配置文件
if (config != null) {
loadConfiguration(initializationContext, config, logFile);
return;
}
// <5> 如果获取不到,则加载默认配置
loadDefaults(initializationContext, logFile);
}
-
<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
/**
* Maintains a mapping between native levels and {@link LogLevel}.
*
* @param <T> the native level type
*/
protected static class LogLevels<T> {
private final Map<LogLevel, T> systemToNative;
private final Map<T, LogLevel> nativeToSystem;
public LogLevels() {
this.systemToNative = new EnumMap<>(LogLevel.class);
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<LogLevel> getSupported() {
return new LinkedHashSet<>(this.nativeToSystem.values());
}
}
7.3 Slf4JLoggingSystem
org.springframework.boot.logging.Slf4JLoggingSystem
,继承 AbstractLoggingSystem 抽象类,基于 Slf4J 的 LoggingSystem 的抽象基类。
7.3.1 beforeInitialize
重写 #beforeInitialize()
方法,代码如下:
// Slf4JLoggingSystem.java
@Override
public void beforeInitialize() {
// 父方法
super.beforeInitialize();
// <1> 配置 JUL 的桥接处理器
configureJdkLoggingBridgeHandler();
}
- 因为艿艿没有特别完整的了解过日志框架,所以下面的解释,更多凭的是“直觉”!如果有错误的地方,给艿艿星球留言哈~
<1>
处,调用#configureJdkLoggingBridgeHandler()
方法,配置 JUL 的桥接处理器。详细解析,见 「7.3.1.1 configureJdkLoggingBridgeHandler」 。
7.3.1.1 configureJdkLoggingBridgeHandler
#configureJdkLoggingBridgeHandler()
方法,配置 JUL 的桥接处理器。代码如下:
// Slf4JLoggingSystem.java
private void configureJdkLoggingBridgeHandler() {
try {
// <1> 判断 JUL 是否桥接到 SLF4J 了
if (isBridgeJulIntoSlf4j()) {
// <2> 移除 JUL 桥接处理器
removeJdkLoggingBridgeHandler();
// <3> 重新安装 SLF4JBridgeHandler
SLF4JBridgeHandler.install();
}
} catch (Throwable ex) {
// Ignore. No java.util.logging bridge is installed.
}
}
-
<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
@Override
public void cleanUp() {
// 判断 JUL 是否桥接到 SLF4J 了
if (isBridgeHandlerAvailable()) {
// 移除 JUL 桥接处理器
removeJdkLoggingBridgeHandler();
}
}
7.3.3 loadConfiguration
重写 #loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile)
方法,代码如下:
// Slf4JLoggingSystem.java
@Override
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
if (initializationContext != null) {
applySystemProperties(initializationContext.getEnvironment(), logFile);
}
}
- 调用
#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
@Override
public void beforeInitialize() {
// <1.1> 获得 LoggerContext 对象
LoggerContext loggerContext = getLoggerContext();
// <1.2> 如果已经初始化过,则直接返回
if (isAlreadyInitialized(loggerContext)) {
return;
}
// <2> 调用父方法
super.beforeInitialize();
// <3> 添加 FILTER 到其中
loggerContext.getTurboFilterList().add(FILTER);
}
-
<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() { @Override 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
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
"logback.xml" };
}
7.4.3 initialize
重写 #initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
方法,代码如下:
// LogbackLoggingSystem.java
private static final String CONFIGURATION_FILE_PROPERTY = "logback.configurationFile";
@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
// <1> 如果已经初始化,则返回
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
// <2> 调用父方法
super.initialize(initializationContext, configLocation, logFile);
// <3> 移除 FILTER
loggerContext.getTurboFilterList().remove(FILTER);
// <4> 标记已经初始化
markAsInitialized(loggerContext);
// <5> 如果配置了 logback.configurationFile ,则打印日志
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY + "' system property. " + "Please use 'logging.config' instead.");
}
}
-
<1>
处,如果已经初始化,则返回。 -
<2>
处,调用父方法,进行初始化。 -
<3>
处,从loggerContext
中,移除FILTER
。😈 如果不移除,就一直打印不出日志列。 -
<4>
处,调用#markAsInitialized(LoggerContext loggerContext)
方法,标记已经初始化。代码如下:// LogbackLoggingSystem.java private void markAsInitialized(LoggerContext loggerContext) { loggerContext.putObject(LoggingSystem.class.getName(), new Object()); }
-
<5>
处,如果配置了"logback.configurationFile"
,则打印日志。
7.4.3.1 loadConfiguration
实现 #loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile)
方法,代码如下:
// LogbackLoggingSystem.java
@Override
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) {
// <1> 调用父方法
super.loadConfiguration(initializationContext, location, logFile);
// <2> 重置
LoggerContext loggerContext = getLoggerContext();
stopAndReset(loggerContext);
// <3> 读取配置文件,并进行配置
try {
configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location));
} catch (Exception ex) {
throw new IllegalStateException("Could not initialize Logback logging from " + location, ex);
}
// <4> 判断是否发生错误。如果有,则抛出 IllegalStateException 异常
List<Status> statuses = loggerContext.getStatusManager().getCopyOfStatusList();
StringBuilder errors = new StringBuilder();
for (Status status : statuses) {
if (status.getLevel() == Status.ERROR) {
errors.append((errors.length() > 0) ? String.format("%n") : "");
errors.append(status.toString());
}
}
if (errors.length() > 0) {
throw new IllegalStateException(String.format("Logback configuration error detected: %n%s", errors));
}
}
-
<1>
处,调用父方法。 -
<2>
处,调用#stopAndReset(LoggerContext loggerContext)
方法,重置。代码如下:// LogbackLoggingSystem.java private void stopAndReset(LoggerContext loggerContext) { // 停止 loggerContext.stop(); // 重置 loggerContext.reset(); // 如果是 SLF4J 桥接 if (isBridgeHandlerInstalled()) { // 添加 LevelChangePropagator addLevelChangePropagator(loggerContext); } } private boolean isBridgeHandlerInstalled() { if (!isBridgeHandlerAvailable()) { return false; } java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); Handler[] handlers = rootLogger.getHandlers(); // 判断有 SLF4JBridgeHandler 唯一元素 return handlers.length == 1 && handlers[0] instanceof SLF4JBridgeHandler; } private void addLevelChangePropagator(LoggerContext loggerContext) { // 创建 LevelChangePropagator 对象(见 https://cloud.tencent.com/developer/ask/174323 说明) LevelChangePropagator levelChangePropagator = new LevelChangePropagator(); // 设置属性 levelChangePropagator.setResetJUL(true); levelChangePropagator.setContext(loggerContext); // 添加 LevelChangePropagator 到 loggerContext 中 loggerContext.addListener(levelChangePropagator); }
- 通过阅读 https://cloud.tencent.com/developer/ask/174323 文章,我们能弄懂这里为什么要使用 LevelChangePropagator ,以及 「7.3.1.1 configureJdkLoggingBridgeHandler」 处的原因。
-
<3>
处,调用#configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext, URL url)
方法,读取配置文件,并进行配置。代码如下:// LogbackLoggingSystem.java private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext, URL url) throws JoranException { // <X> 如果是 xml 配置格式,则使用 SpringBootJoranConfigurator if (url.toString().endsWith("xml")) { JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext); configurator.setContext(loggerContext); configurator.doConfigure(url); // 如果是其它格式,则使用 ContextInitializer } else { new ContextInitializer(loggerContext).configureByResource(url); } }
<X>
处,如果是 Logback xml 配置格式,则使用 SpringBootJoranConfigurator 类。- 至此,Logback 配置文件,就已经被读完落。
-
<4>
处,判断是否发生错误。如果有,则抛出 IllegalStateException 异常。
7.4.3.1.1 SpringBootJoranConfigurator
org.springframework.boot.logging.logback.SpringBootJoranConfigurator
,继承 JoranConfigurator 类,增加 Spring Boot 自定义的标签。代码如下:
// SpringBootJoranConfigurator.java
class SpringBootJoranConfigurator extends JoranConfigurator {
private LoggingInitializationContext initializationContext;
SpringBootJoranConfigurator(LoggingInitializationContext initializationContext) {
this.initializationContext = initializationContext;
}
@Override
public void addInstanceRules(RuleStore rs) {
// 调用父方法
super.addInstanceRules(rs);
Environment environment = this.initializationContext.getEnvironment();
rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment));
rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment));
rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction());
}
}
- 不了解的胖友,可以先看看 《SpringBoot 中 logback.xml 使用 application.yml 中属性》 文章。
org.springframework.boot.logging.logback.SpringProfileAction
,处理<springProfile />
标签。org.springframework.boot.logging.logback.SpringPropertyAction
,处理<springProperty />
标签。
7.4.3.2 reinitialize
实现 #reinitialize(LoggingInitializationContext initializationContext)
方法,代码如下:
// LogbackLoggingSystem.java
@Override
protected void reinitialize(LoggingInitializationContext initializationContext) {
// <1> 重置
getLoggerContext().reset();
// <2> 清空 StatusManager
getLoggerContext().getStatusManager().clear();
// <3> 加载配置
loadConfiguration(initializationContext, getSelfInitializationConfig(), null);
}
<1>
处,重置。<2>
处,清空 StatusManager 。<3>
处,调用#loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile)
方法,加载配置。此时,使用的是约定的 Logback 配置文件。
7.4.3.3 loadDefaults
实现 #loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile)
方法,代码如下:
// LogbackLoggingSystem.java
@Override
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
// <1> 重置
LoggerContext context = getLoggerContext();
stopAndReset(context);
// <2> 创建 LogbackConfigurator 对象
LogbackConfigurator configurator = new LogbackConfigurator(context);
// <3> 从 environment 读取变量,设置到 context 中。
Environment environment = initializationContext.getEnvironment();
context.putProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN, environment.resolvePlaceholders("${logging.pattern.level:${LOG_LEVEL_PATTERN:%5p}}"));
context.putProperty(LoggingSystemProperties.LOG_DATEFORMAT_PATTERN, environment.resolvePlaceholders("${logging.pattern.dateformat:${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}"));
// <4> 创建 DefaultLogbackConfiguration 对象,设置到 configurator 中
new DefaultLogbackConfiguration(initializationContext, logFile).apply(configurator);
// <5> 设置日志文件,按天滚动
context.setPackagingDataEnabled(true);
}
<1>
处,调用#stopAndReset(LoggerContext loggerContext)
方法,重置。<2>
处,创建 LogbackConfigurator 对象。详细解析,见 「7.4.3.3.1 LogbackConfigurator」 。<3>
处,从environment
读取变量,设置到context
中。<4>
处,创建 DefaultLogbackConfiguration 对象,后调用DefaultLogbackConfiguration#apply(LogbackConfigurator)
方法,设置到configurator
中。详细解析,见 「7.4.3.3.2 DefaultLogbackConfiguration」 。<5>
处,调用LoggerContext#setPackagingDataEnabled(boolean packagingDataEnabled)
方法,设置日志文件,按天滚动。
7.4.3.3.1 LogbackConfigurator
org.springframework.boot.logging.logback.LogbackConfigurator
,Logback 配置器,提供一些工具方法,方便配置 Logback 。
因为 LogbackConfigurator 提供的方法,都是被 DefaultLogbackConfiguration 所调用。所以我们先跳到 「7.4.3.3.2 DefaultLogbackConfiguration」 中。
7.4.3.3.1.1 conversionRule
#conversionRule(String conversionWord, Class<? extends Converter> converterClass)
方法,添加转换规则。代码如下:
// LogbackConfigurator.java
public void conversionRule(String conversionWord, Class<? extends Converter> converterClass) {
Assert.hasLength(conversionWord, "Conversion word must not be empty");
Assert.notNull(converterClass, "Converter class must not be null");
// 获得注册表
Map<String, String> registry = (Map<String, String>) this.context.getObject(CoreConstants.PATTERN_RULE_REGISTRY);
// 如果注册表为空,则进行注册
if (registry == null) {
registry = new HashMap<>();
this.context.putObject(CoreConstants.PATTERN_RULE_REGISTRY, registry);
}
// 添加转换规则,到注册表中
registry.put(conversionWord, converterClass.getName());
}
- 比较简单,胖友自己瞅瞅。
- 目前有三个转换规则,分别是:
- org.springframework.boot.logging.logback.ColorConverter ,实现 ANSI 颜色转换器。
- org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter ,在异常堆栈的打印过程中添加一些空格。
- org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter ,在异常堆栈的打印过程中添加一些空格。
- org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter ,在异常堆栈的打印过程中添加一些空格。【同上】
- 就不详细解析啦,胖友自己瞅瞅就明白列。
7.4.3.3.1.2 logger
#logger(String name, Level level)
方法,添加 Logger 。代码如下:
// LogbackConfigurator.java
public void logger(String name, Level level) {
logger(name, level, true);
}
public void logger(String name, Level level, boolean additive) {
logger(name, level, additive, null);
}
public void logger(String name, Level level, boolean additive, Appender<ILoggingEvent> appender) {
// 获得 Logger 对象
Logger logger = this.context.getLogger(name);
// 设置 level
if (level != null) {
logger.setLevel(level);
}
// 设置 additive
logger.setAdditive(additive);
// 设置 appender
if (appender != null) {
logger.addAppender(appender);
}
}
7.4.3.3.1.3 appender
#appender(String name, Appender<?> appender)
方法,启动 Appender 。代码如下:
// LogbackConfigurator.java
public void appender(String name, Appender<?> appender) {
// 设置 name
appender.setName(name);
// 启动 Appender
start(appender);
}
public void start(LifeCycle lifeCycle) {
// 设置 context
if (lifeCycle instanceof ContextAware) {
((ContextAware) lifeCycle).setContext(this.context);
}
// 启动
lifeCycle.start();
}
7.4.3.3.1.4 root
#root(Level level, Appender<ILoggingEvent>... appenders)
方法,设置 appender 到 ROOT Logger 。代码如下:
// LogbackConfigurator.java
public final void root(Level level, Appender<ILoggingEvent>... appenders) {
// 获得 Root Logger 对象
Logger logger = this.context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
// 设置 level
if (level != null) {
logger.setLevel(level);
}
// 添加 appender 到 logger 中
for (Appender<ILoggingEvent> appender : appenders) {
logger.addAppender(appender);
}
}
7.4.3.3.2 DefaultLogbackConfiguration
org.springframework.boot.logging.logback.DefaultLogbackConfiguration
,默认的 Logback 配置类。代码如下:
相当于代码生成
logback.xml
的效果。
7.4.3.3.2.1 构造方法
// DefaultLogbackConfiguration.java
/**
* PropertyResolver 对象。提供从 environment 解析配置
*/
private final PropertyResolver patterns;
private final LogFile logFile;
DefaultLogbackConfiguration(LoggingInitializationContext initializationContext, LogFile logFile) {
this.patterns = getPatternsResolver(initializationContext.getEnvironment());
this.logFile = logFile;
}
private PropertyResolver getPatternsResolver(Environment environment) {
// 创建 PropertySourcesPropertyResolver 对象,无 environment
if (environment == null) {
return new PropertySourcesPropertyResolver(null);
}
// 创建 PropertySourcesPropertyResolver 对象,有 environment
if (environment instanceof ConfigurableEnvironment) {
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(((ConfigurableEnvironment) environment).getPropertySources());
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
return resolver;
}
// 直接返回 environment
return environment;
}
7.4.3.3.2.2 apply
#apply(LogbackConfigurator config)
方法,应用配置。代码如下:
// DefaultLogbackConfiguration.java
public void apply(LogbackConfigurator config) {
// <1> 锁
synchronized (config.getConfigurationLock()) {
// <2> 设置基础属性
base(config);
// <3> 创建 console Appender
Appender<ILoggingEvent> consoleAppender = consoleAppender(config);
// <4> 如果 logFile 非空,则创建 file Appender
if (this.logFile != null) {
Appender<ILoggingEvent> fileAppender = fileAppender(config, this.logFile.toString());
// <5> 设置 appender 到 ROOT Logger
config.root(Level.INFO, consoleAppender, fileAppender);
} else {
// <5> 设置 appender 到 ROOT Logger
config.root(Level.INFO, consoleAppender);
}
}
}
-
<1>
处,锁。代码如下:// LogbackConfigurator.java private LoggerContext context; public Object getConfigurationLock() { return this.context.getConfigurationLock(); }
-
<2>
处,调用#base(LogbackConfigurator config)
方法,设置基础属性。代码如下:// LogbackConfigurator.java private void base(LogbackConfigurator config) { // <2.1> Converter config.conversionRule("clr", ColorConverter.class); config.conversionRule("wex", WhitespaceThrowableProxyConverter.class); config.conversionRule("wEx", ExtendedWhitespaceThrowableProxyConverter.class); // <2.2> 默认的 logger config.logger("org.apache.catalina.startup.DigesterFactory", Level.ERROR); config.logger("org.apache.catalina.util.LifecycleBase", Level.ERROR); config.logger("org.apache.coyote.http11.Http11NioProtocol", Level.WARN); config.logger("org.apache.sshd.common.util.SecurityUtils", Level.WARN); config.logger("org.apache.tomcat.util.net.NioSelectorPool", Level.WARN); config.logger("org.eclipse.jetty.util.component.AbstractLifeCycle", Level.ERROR); config.logger("org.hibernate.validator.internal.util.Version", Level.WARN); }
<2.1>
处,调用LogbackConfigurator#conversionRule(String conversionWord, Class<? extends Converter> converterClass)
方法,添加转换规则。详细解析,见 「 7.4.3.3.1.1 conversionRule」 。<2.2>
处,调用LogbackConfigurator#logger(String name, Level level)
方法,默认的 logger 。详细解析,见 「7.4.3.3.1.2 logger」 。
-
<3>
处,调用#consoleAppender(LogbackConfigurator config)
方法,创建 console Appender 对象。代码如下:// LogbackConfigurator.java private static final String CONSOLE_LOG_PATTERN = "%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} " + "%clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} " + "%clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} " + "%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"; private Appender<ILoggingEvent> consoleAppender(LogbackConfigurator config) { ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>(); PatternLayoutEncoder encoder = new PatternLayoutEncoder(); String logPattern = this.patterns.getProperty("logging.pattern.console", CONSOLE_LOG_PATTERN); // <X> encoder.setPattern(OptionHelper.substVars(logPattern, config.getContext())); config.start(encoder); appender.setEncoder(encoder); config.appender("CONSOLE", appender); // <Y> return appender; }
<X>
处,从environment
中,读取"logging.pattern.console"
作为格式。如果找不到,使用CONSOLE_LOG_PATTERN
。<Y>
处,调用LogbackConfigurator#appender(String name, Appender<?> appender)
方法,启动 Appender 。详细解析,见 「7.4.3.3.1.3 appender」 。
-
<4>
处,如果logFile
非空,则调用#fileAppender(LogbackConfigurator config, String logFile)
方法,创建 file Appender。代码如下:// LogbackConfigurator.java private static final String FILE_LOG_PATTERN = "%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} " + "${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"; private static final String MAX_FILE_SIZE = "10MB"; private Appender<ILoggingEvent> fileAppender(LogbackConfigurator config, String logFile) { RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>(); PatternLayoutEncoder encoder = new PatternLayoutEncoder(); String logPattern = this.patterns.getProperty("logging.pattern.file", FILE_LOG_PATTERN); encoder.setPattern(OptionHelper.substVars(logPattern, config.getContext())); appender.setEncoder(encoder); config.start(encoder); appender.setFile(logFile); // 滚动策略 setRollingPolicy(appender, config, logFile); config.appender("FILE", appender); return appender; } private void setRollingPolicy(RollingFileAppender<ILoggingEvent> appender, LogbackConfigurator config, String logFile) { SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new SizeAndTimeBasedRollingPolicy<>(); rollingPolicy.setFileNamePattern(logFile + ".%d{yyyy-MM-dd}.%i.gz"); // 单文件最大值 setMaxFileSize(rollingPolicy, this.patterns.getProperty("logging.file.max-size", MAX_FILE_SIZE)); rollingPolicy.setMaxHistory(this.patterns.getProperty("logging.file.max-history", Integer.class, CoreConstants.UNBOUND_HISTORY)); appender.setRollingPolicy(rollingPolicy); rollingPolicy.setParent(appender); config.start(rollingPolicy); } private void setMaxFileSize(SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy, String maxFileSize) { try { rollingPolicy.setMaxFileSize(FileSize.valueOf(maxFileSize)); } catch (NoSuchMethodError ex) { // Logback < 1.1.8 used String configuration Method method = ReflectionUtils.findMethod(SizeAndTimeBasedRollingPolicy.class, "setMaxFileSize", String.class); ReflectionUtils.invokeMethod(method, rollingPolicy, maxFileSize); } }
-
<5>
处,调用LogbackConfigurator#root(Level level, Appender<ILoggingEvent>... appenders)
方法,设置 appender 到 ROOT Logger 。详细解析,见 「7.4.3.3.1.4 root」 。
7.4.4 setLogLevel
实现 #setLogLevel(String loggerName, LogLevel level)
方法,代码如下:
// LogbackLoggingSystem.java
private static final LogLevels<Level> LEVELS = new LogLevels<>();
static {
LEVELS.map(LogLevel.TRACE, Level.TRACE);
LEVELS.map(LogLevel.TRACE, Level.ALL);
LEVELS.map(LogLevel.DEBUG, Level.DEBUG);
LEVELS.map(LogLevel.INFO, Level.INFO);
LEVELS.map(LogLevel.WARN, Level.WARN);
LEVELS.map(LogLevel.ERROR, Level.ERROR);
LEVELS.map(LogLevel.FATAL, Level.ERROR);
LEVELS.map(LogLevel.OFF, Level.OFF);
}
@Override
public void setLogLevel(String loggerName, LogLevel level) {
// <1> 获得 Logger 对象
ch.qos.logback.classic.Logger logger = getLogger(loggerName);
// <2> 设置日志级别
if (logger != null) {
logger.setLevel(LEVELS.convertSystemToNative(level));
}
}
-
<1>
处,调用
#getLogger(String name)
方法,获得 Logger 对象。代码如下:
// LogbackLoggingSystem.java private ch.qos.logback.classic.Logger getLogger(String name) { LoggerContext factory = getLoggerContext(); if (StringUtils.isEmpty(name) || ROOT_LOGGER_NAME.equals(name)) { name = Logger.ROOT_LOGGER_NAME; } return factory.getLogger(name); }
-
<2>
处,设置日志级别。
7.4.4 cleanUp
重写 #cleanUp()
方法,代码如下:
// LogbackLoggingSystem.java
@Override
public void cleanUp() {
// 标记为未初始化
LoggerContext context = getLoggerContext();
markAsUninitialized(context);
// 调用父方法
super.cleanUp();
// 清空 StatusManager
context.getStatusManager().clear();
// 移除 FILTER
context.getTurboFilterList().remove(FILTER);
}
private void markAsUninitialized(LoggerContext loggerContext) {
loggerContext.removeObject(LoggingSystem.class.getName());
}
7.4.5 getShutdownHandler
实现 #getShutdownHandler()
方法,代码如下:
// LogbackLoggingSystem.java
@Override
public Runnable getShutdownHandler() {
return new ShutdownHandler();
}
private final class ShutdownHandler implements Runnable {
@Override
public void run() {
getLoggerContext().stop(); // 停止
}
}
7.5 Log4J2LoggingSystem
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
,继承 Slf4JLoggingSystem 抽象类,基于 Log4J2 的 LoggingSystem 实现类。
就暂时不解析了,基本类似。感兴趣的胖友,可以看看 《spring boot 源码解析28-Log4J2LoggingSystem》 。
7.6 JavaLoggingSystem
org.springframework.boot.logging.java.JavaLoggingSystem
,继承 AbstractLoggingSystem 抽象类,基于 JUL 的 LoggingSystem 实现类。
就暂时不解析了,基本类似。感兴趣的胖友,可以看看 《spring boot 源码解析27-JavaLoggingSystem及LoggingSystem生命周期详解》 的 「LoggingSystem」 部分。
666. 彩蛋
Spring Boot 的文章,基本都短不了~咋说呢?虽然长了一些吧,总体还是比较简单和顺畅的。
参考和推荐如下文章:
- 一个努力的码农 《spring boot 源码解析29-LogbackLoggingSystem》
- oldflame-Jm 《Spring boot源码分析-log日志系统(6)》