版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/andy_zhang2007/article/details/84191672
源码分析
本文源代码基于 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.forEach(
(name, loggers) -> groups.put(name, StringUtils.toStringArray(loggers)));
return groups;
}
private void setLogLevel(LoggingSystem system, String[] names, String level) {
for (String name : names) {
setLogLevel(system, name, level);
}
}
private void setLogLevel(LoggingSystem system, String name, String level) {
try {
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 + "'");
}
}
private LogLevel coerceLogLevel(String level) {
if ("false".equalsIgnoreCase(level)) {
return LogLevel.OFF;
}
return LogLevel.valueOf(level.toUpperCase(Locale.ENGLISH));
}
private void registerShutdownHookIfNecessary(Environment environment,
LoggingSystem loggingSystem) {
boolean registerShutdownHook = environment
.getProperty(REGISTER_SHUTDOWN_HOOK_PROPERTY, Boolean.class, false);
if (registerShutdownHook) {
Runnable shutdownHandler = loggingSystem.getShutdownHandler();
if (shutdownHandler != null
&& shutdownHookRegistered.compareAndSet(false, true)) {
registerShutdownHook(new Thread(shutdownHandler));
}
}
}
void registerShutdownHook(Thread shutdownHook) {
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
/**
* Sets a custom logging level to be used for Spring Boot and related libraries.
* @param springBootLogging the logging level
*/
public void setSpringBootLogging(LogLevel springBootLogging) {
this.springBootLogging = springBootLogging;
}
/**
* Sets if initialization arguments should be parsed for {@literal debug} and
* {@literal trace} properties (usually defined from {@literal --debug} or
* {@literal --trace} command line args). Defaults to {@code true}.
* @param parseArgs if arguments should be parsed
*/
public void setParseArgs(boolean parseArgs) {
this.parseArgs = parseArgs;
}
}
相关文章
Springboot内置ApplicationListener