环境:jdk 1.8,spring-boot-autoconfigure-1.5.17.RELEASE.jar,slf4j-api-1.7.25.jar
查看springboot-autoconfigure中的日志模块logging,提供两个类:AutoConfigurationReportLoggingInitializer、ConditionEvaluationReportMessage。从类名中可以看到是一个报告类。查看源码确实如此。那么也就是springboot的自动配置类中没有操作什么。
查看springboot中的日志模块logging,其中提供两个应用监听器:ClasspathLoggingApplicationListener、LoggingApplicationListener。了解springboot启动流程的同学应该知道springboot通过spi注册了许多的监听器,查看spring.factories中发现其中就包含刚才说到的两个日志相关的监听器
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
ClasspathLoggingApplicationListener监听代码很简单,主要是监听spring应用启动或者启动失败类型事件,在收到对应事件时打印debug日志输出当前classLoader中urls列表
LoggingApplicationListener
LoggingApplicationListener监听器监听ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent、ContextClosedEvent、ApplicationFailedEvent五种类型的事件。并且Source类型为:SpringApplication、ApplicationContext。常用的source为SpringApplication类型。例如:
@SpringBootApplication
@EnableDubbo(multipleConfig = true)
public class Application {
public static void main(String[] args) {
new SpringApplication(Application.class).run(args);
}
}
事件发送顺序
- ApplicationStartedEvent(ApplicationStartingEvent子类)
- ApplicationEnvironmentPreparedEvent
- ApplicationPreparedEvent
我们按照顺序查看该监听器的对应动作
onApplicationStartingEvent
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
- 根据source(例如:案例中的Application.class)的classloader获取日志系统实例LoggingSystem
- 回调日志系统初始化前的前置动作beforeInitialize
获取日志系统实例
// LoggingSystem.class
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
if (NONE.equals(loggingSystem)) {
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystem);
}
for (Map.Entry<String, String> entry : SYSTEMS.entrySet()) {
if (ClassUtils.isPresent(entry.getKey(), classLoader)) {
return get(classLoader, entry.getValue());
}
}
throw new IllegalStateException("No suitable logging system located");
}
- 如果配置了System的LoggingSystem属性指定日志系统类,则根据配置获取日志系统实例
- 如果没有配置System的LoggingSystem的系统属性,遍历_SYSTEMS配置,返回第一个匹配的日志系统实例_
// LoggingSystem
static {
Map<String, String> systems = new LinkedHashMap<String, String>();
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);
}
根据日志系统类获取实例
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);
}
}
springboot提供了三种实现
- LogbackLoggingSystem
- Log4J2LoggingSystem
- JavaLoggingSystem
除了JavaLoggingSystem,其他两种均是Slf4JLoggingSystem子类
初始化日志系统前置动作
目前系统中使用的log4j2日志,所以以Log4J2LoggingSystem为例
public void beforeInitialize() {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
super.beforeInitialize();
loggerContext.getConfiguration().addFilter(FILTER);
}
- 获取日志上下文并初始化,如果已经初始化则直接返回
- 调用父类前置动作,如果存在org.slf4j.bridge.SLF4JBridgeHandler句柄类则移除jdk桥接句柄removeJdkLoggingBridgeHandler安装SLF4JBridgeHandler
- 添加Log4J2LoggingSystem.FILTER过滤器,拒绝所有日志
onApplicationEnvironmentPreparedEvent
// LoggingSystem.class
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
...
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
// LoggingSystem.class
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);
}
// Log4J2LoggingSystem.class
public void initialize(LoggingInitializationContext initializationContext,
String configLocation, LogFile logFile) {
LoggerContext loggerContext = getLoggerContext();
...
loggerContext.getConfiguration().removeFilter(FILTER);
super.initialize(initializationContext, configLocation, logFile);
markAsInitialized(loggerContext);
}
- 将应用Environment环境中的logging.前缀配置填充至System
- 如果存在logging.file、logging.path配置则创建对应的LogFile
- 如果应用Environment环境中配置debug或trace为true,则设置springBootLogging为对应的日志级别
- 如果应用Environment环境中logging.config配置了文件并且不是-D开头,使用配置文件初始化,否则使用null作为logConfig初始化。回调日志系统初始化
- 移除Log4J2LoggingSystem.FILTER过滤器,拒绝所有日志
- 如果指定了configLocation,使用configLocation配置初始化initializeWithSpecificConfig
- 否则initializeWithConventions
- 标记当前初始化完毕
initializeWithSpecificConfig
- 回调子类loadConfiguration加载配置
- 创建ConfigurationSource配置文件源
- ConfigurationFactory工厂类创建Configuration配置实例
- 按照Configuration配置实例启动日志上下文LoggerContext.start(Configuration)
protected void loadConfiguration(LoggingInitializationContext initializationContext,
String location, LogFile logFile) {
super.loadConfiguration(initializationContext, location, logFile);
loadConfiguration(location, logFile);
}
protected void loadConfiguration(String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
try {
LoggerContext ctx = getLoggerContext();
URL url = ResourceUtils.getURL(location);
ConfigurationSource source = getConfigurationSource(url);
ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
}
catch (Exception ex) {
throw new IllegalStateException(
"Could not initialize Log4J2 logging from " + location, ex);
}
}
initializeWithConventions
- 回调子类获取标准配置文件名列表getStandardConfigLocations,resource中查找标准配置文件,如果当前系统支持yml则同样查找log4j2.yaml配置,如果当前系统支持json则同样查找log4j2.json配置,否则使用log4j2.xml。如果resource中存在对应配置,则使用自身配置重新初始化
- 获取spring初始化配置文件,使用子类标准配置文件名列表,名称增加-spring后缀,例如:log4j2-spring.xml
- 如果配置文件不为空则按照配置文件初始化日志上下文,逻辑同initializeWithSpecificConfig
- 如果配置文件为空则加载默认配置并初始化,默认配置文件在当前目录下,如果logFile不为空则使用log4j2-file.xml配置,否则使用log4j2.xml配置
private void initializeWithConventions(
LoggingInitializationContext initializationContext, LogFile logFile) {
String config = getSelfInitializationConfig();
if (config != null && logFile == null) {
// self initialization has occurred, reinitialize in case of property changes
reinitialize(initializationContext);
return;
}
if (config == null) {
config = getSpringInitializationConfig();
}
if (config != null) {
loadConfiguration(initializationContext, config, logFile);
return;
}
loadDefaults(initializationContext, logFile);
}
initializeFinalLoggingLevels
初始化设置最终的日志级别
registerShutdownHookIfNecessary
注册钩子如果需要的话
onApplicationPreparedEvent
将当前日志系统注册至spring容器
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);
}
}