Springboot內建ApplicationListener--LoggingApplicationListener
阿新 • • 發佈:2018-11-19
原始碼分析
本文原始碼基於 Springboot 2.1.0
package org.springframework.boot.context.logging;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons. logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework. boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties. bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingInitializationContext;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.logging.LoggingSystemProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* An ApplicationListener that configures the LoggingSystem.
* 配置日誌系統LoggingSystem應用程式事件監聽器。
*
* If the environment contains a logging.config property it will be used to bootstrap the
* logging system, otherwise a default configuration is used.
* 如果系統屬性中設定了logging.config,則會根據這個屬性載入日誌系統,否則使用預設配置。
*
* Regardless, logging levels will be customized if the environment contains logging.level.*
* entries and logging groups can be defined with logging.group.
* 不過無論上面哪種日誌配置載入方案,如果環境屬性中包含了logging.level.*的話,這些屬性都會用於定製
* 日誌級別;而且如果環境中設定了logging.group,它也會用於定義日誌組。
*
* Debug and trace logging for Spring, Tomcat, Jetty and Hibernate will be enabled when
* the environment contains debug or trace properties that aren't set to "false"
* 如果環境屬性中debug/trace屬性被設定為不是"false",那麼Spring,Tomcat,Jetty和Hibernate的除錯和
* 跟蹤日誌會被啟用。
* (i.e. if you start your application using "java -jar myapp.jar [--debug | --trace]").
* 比如你通過命令列方式打開了debug/trace:
* java -jar myapp.jar [--debug | --trace]
*
* If you prefer to ignore these properties you can set parseArgs false.
* 如果你想忽略這些屬性,也可以把parseArgs設定為false。
*
*
* By default, log output is only written to the console. If a log file is required the
* logging.path and logging.file properties can be used.
* 預設情況下,日誌輸出僅僅寫到控制檯上。如果你想輸出到檔案,可以使用屬性logging.path/logging.file。
*
* Some system properties may be set as side effects, and these can be useful if the
* logging configuration supports placeholders (i.e. log4j or logback):
*
* 1.LOG_FILE is set to the value of path of the log file that should be written
* (if any).
* 2.PID is set to the value of the current process ID if it can be determined.
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 2.0.0
* @see LoggingSystem#get(ClassLoader)
*/
public class LoggingApplicationListener implements GenericApplicationListener {
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);
/**
* The default order for the LoggingApplicationListener.
*/
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 20;
/**
* The name of the Spring property that contains a reference to the logging
* configuration to load.
*/
public static final String CONFIG_PROPERTY = "logging.config";
/**
* 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";
/**
* The name of the {@link LoggingSystem} bean.
*/
public static final String LOGGING_SYSTEM_BEAN_NAME = "springBootLoggingSystem";
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("sql", "org.springframework.jdbc.core");
loggers.add("sql", "org.hibernate.SQL");
DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers);
}
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);
}
private static final Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class,
ContextClosedEvent.class, ApplicationFailedEvent.class };
private static final Class<?>[] SOURCE_TYPES = { SpringApplication.class,
ApplicationContext.class };
private static final AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
private final Log logger = LogFactory.getLog(getClass());
private LoggingSystem loggingSystem;
private int order = DEFAULT_ORDER;
private boolean parseArgs = true;
private LogLevel springBootLogging = null;
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return isAssignableFrom(sourceType, SOURCE_TYPES);
}
private boolean isAssignableFrom(Class<?> type, Class<?>... supportedTypes) {
if (type != null) {
for (Class<?> supportedType : supportedTypes) {
if (supportedType.isAssignableFrom(type)) {
return true;
}
}
}
return false;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
// ApplicationStartingEvent 事件發生時初始化日誌系統
// 支援 Logback 和 Java Logging ,
// 具體的做法是根據相應類在classpath上的存在性建立相應的日誌系統,
// 然後呼叫其方法beforeInitialize
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
// ApplicationEnvironmentPreparedEvent 事件處理邏輯,此時環境剛剛準備好
// 如果此時日誌系統尚未構造,嘗試再次構造
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
}
// 對日誌系統進行初始化
// 1. 應用環境屬性 logging.file/logging.path
// 2. 應用debug/trace引數
// 3. 載入日誌配置檔案,應用環境屬性 logging.config 等
// 4. 根據環境屬性設定日誌輸出級別等
// 5. 註冊停止鉤子函式
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
// 應用上下文已經準備好,現在把日誌系統作為一個單例bean註冊到應用上下文:
// springBootLoggingSystem
ConfigurableListableBeanFactory beanFactory = event.getApplicationContext()
.getBeanFactory();
if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
}
}
private void onContextClosedEvent() {
// 應用上下文關閉時日誌系統執行相應的清場工作
if (this.loggingSystem != null) {
this.loggingSystem.cleanUp();
}
}
private void onApplicationFailedEvent() {
// 應用上下文出錯時日誌系統執行相應的清場工作
if (this.loggingSystem != null) {
this.loggingSystem.cleanUp();
}
}
/**
* Initialize the logging system according to preferences expressed through the
* {@link Environment} and the classpath.
* @param environment the environment
* @param classLoader the classloader
*/
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);
}
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"));
}
private void initializeSystem(ConfigurableEnvironment environment,
LoggingSystem system, LogFile logFile) {
LoggingInitializationContext initializationContext = new LoggingInitializationContext(
environment);
// 配置檔案中指定的要載入的日誌配置檔案:logging.config
String logConfig = environment.getProperty(CONFIG_PROPERTY);
if (ignoreLogConfig(logConfig)) {
// 如果沒有通過logging.config指定日誌配置檔案,則使用預設配置初始化日誌系統
system.initialize(initializationContext, null, logFile);
}
else {
// 如果通過logging.config指定了日誌配置檔案,則使用它配置初始化日誌系統
try {
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);
}
}
}
private boolean ignoreLogConfig(String logConfig) {
return !StringUtils.hasLength(logConfig) || logConfig.startsWith("-D");
}
private void initializeFinalLoggingLevels(ConfigurableEnvironment environment,
LoggingSystem system) {
if (this.springBootLogging != null) {
initializeLogLevel(system, this.springBootLogging);
}
setLogLevels(system, environment);
}
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);
}
}
}
protected void setLogLevels(LoggingSystem system, Environment environment) {
if (!(environment instanceof ConfigurableEnvironment)) {
return;
}
Binder binder = Binder.get(environment);
Map<String, String[]> groups = getGroups();
binder.bind(LOGGING_GROUP, STRING_STRINGS_MAP.withExistingValue(groups));
Map<String, String> levels = binder.bind(LOGGING_LEVEL, STRING_STRING_MAP)
.orElseGet(Collections::emptyMap);
levels.forEach((name, level) -> {
String[] groupedNames = groups.get(name);
if (ObjectUtils.isEmpty(groupedNames)) {
setLogLevel(system, name, level);
}
else {
setLogLevel(system, groupedNames, level);
}
});
}
private Map<String, String[]> getGroups() {
Map<String, String[]> groups = new LinkedHashMap<>();
DEFAULT_GROUP_LOGGERS.