1. 程式人生 > >Springboot內建ApplicationListener--LoggingApplicationListener

Springboot內建ApplicationListener--LoggingApplicationListener

原始碼分析

本文原始碼基於 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.