springboot整合日志系统

环境: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);
	}
}

事件发送顺序

  1. ApplicationStartedEvent(ApplicationStartingEvent子类)
  2. ApplicationEnvironmentPreparedEvent
  3. ApplicationPreparedEvent

我们按照顺序查看该监听器的对应动作

onApplicationStartingEvent

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
	this.loggingSystem = LoggingSystem
			.get(event.getSpringApplication().getClassLoader());
	this.loggingSystem.beforeInitialize();
}
  1. 根据source(例如:案例中的Application.class)的classloader获取日志系统实例LoggingSystem
  2. 回调日志系统初始化前的前置动作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");
}
  1. 如果配置了System的LoggingSystem属性指定日志系统类,则根据配置获取日志系统实例
  2. 如果没有配置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提供了三种实现

  1. LogbackLoggingSystem
  2. Log4J2LoggingSystem
  3. JavaLoggingSystem

除了JavaLoggingSystem,其他两种均是Slf4JLoggingSystem子类

初始化日志系统前置动作

目前系统中使用的log4j2日志,所以以Log4J2LoggingSystem为例

public void beforeInitialize() {
	LoggerContext loggerContext = getLoggerContext();
	if (isAlreadyInitialized(loggerContext)) {
		return;
	}
	super.beforeInitialize();
	loggerContext.getConfiguration().addFilter(FILTER);
}
  1. 获取日志上下文并初始化,如果已经初始化则直接返回
  2. 调用父类前置动作,如果存在org.slf4j.bridge.SLF4JBridgeHandler句柄类则移除jdk桥接句柄removeJdkLoggingBridgeHandler安装SLF4JBridgeHandler
  3. 添加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);
}
  1. 将应用Environment环境中的logging.前缀配置填充至System
  2. 如果存在logging.file、logging.path配置则创建对应的LogFile
  3. 如果应用Environment环境中配置debug或trace为true,则设置springBootLogging为对应的日志级别
  4. 如果应用Environment环境中logging.config配置了文件并且不是-D开头,使用配置文件初始化,否则使用null作为logConfig初始化。回调日志系统初始化
  5. 移除Log4J2LoggingSystem.FILTER过滤器,拒绝所有日志
  6. 如果指定了configLocation,使用configLocation配置初始化initializeWithSpecificConfig
  7. 否则initializeWithConventions
  8. 标记当前初始化完毕

initializeWithSpecificConfig

  1. 回调子类loadConfiguration加载配置
  2. 创建ConfigurationSource配置文件源
  3. ConfigurationFactory工厂类创建Configuration配置实例
  4. 按照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

  1. 回调子类获取标准配置文件名列表getStandardConfigLocations,resource中查找标准配置文件,如果当前系统支持yml则同样查找log4j2.yaml配置,如果当前系统支持json则同样查找log4j2.json配置,否则使用log4j2.xml。如果resource中存在对应配置,则使用自身配置重新初始化
  2. 获取spring初始化配置文件,使用子类标准配置文件名列表,名称增加-spring后缀,例如:log4j2-spring.xml
  3. 如果配置文件不为空则按照配置文件初始化日志上下文,逻辑同initializeWithSpecificConfig
  4. 如果配置文件为空则加载默认配置并初始化,默认配置文件在当前目录下,如果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);
	}
}
发布了91 篇原创文章 · 获赞 103 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/u010597819/article/details/104131093
今日推荐