SpringBoot日志系统的实现原理

  • 1. SpringBoot的日志系统
  • 2. spring-jcl:适配Apache Commons Logging API
  • 3. 基于应用事件的日志系统配置
  • 4. Logback日志系统的配置
  • 5. Log4J2日志系统的配置
  • 6. 使用SLF4J Logger输出日志
  • 附录
  • 1. jul-to-slf4j
  • 2. 获取日志系统的逻辑
  • 3. 日志系统的初始化过程 - 涉及到文件配置和属性配置的优先级问题
  • 参考


1. SpringBoot的日志系统

SpringBoot支持三种日志系统,分别是Log4J2、Logback和j.u.l。其中Log4J2和Logback实现了Slf4J API。SpringBoot中默认的日志系统是Logback。

spring boot 日志采集 springboot日志原理_Log4J2

2. spring-jcl:适配Apache Commons Logging API

虽然现在我们的大多数应用都是是用SLF4J API,但是我们依赖的第三方组件中很多是使用的Apache Commons Logging API,Spring提供了spring-jcl进行适配以保证 在使用不同API时 日志系统的一致性。

spring-jcl提供了Apache Commons Logging的LogFactory API接口的最小实现,仅提供常见的Log查询方法。它是受JCL-over-SLF4J bridge的启发而产生的, 它与Commons Logging API的所有常用方法兼容,特别是通过 LogFactory.getLog(Class/String) 进行的字段初始化。

此实现不支持Commons Logging的原始提供程序检测。 而是仅检查Spring Framework类路径中Log4j 2.x API和SLF4J 1.7 API是否存在,如果两者都不可用,则退回到java.util.logging。

3. 基于应用事件的日志系统配置

org.springframework.boot.context.logging.LoggingApplicationListener

An ApplicationListener that configures the LoggingSystem. If the environment contains a logging.config property it will be used to bootstrap the logging system, otherwise a default configuration is used. Regardless, logging levels will be customized if the environment contains logging.level.* entries and logging groups can be defined with logging.group

LoggingApplicationListener是一个配置日志系统的ApplicationListener。如果环境变量中包含 logging.config 属性,那么将使用它启动日志系统,否则将使用默认配置。无论如何,如果环境变量中包含 logging.level.* 条目,则依此进行日志级别的个性化配置,日志分组可以通过 logging.group 定义。

LoggingApplicationListener监听的应用事件以及各事件对应的处理逻辑:

  • ApplicationStartingEvent: Detect and return the logging system in use. return the logging system.
  • ApplicationEnvironmentPreparedEvent:Initialize the logging system according to preferences expressed through the Environment and the classpath.
  • ApplicationPreparedEvent:register singleton bean named of springBootLoggingSystem
  • ContextClosedEvent:clean up logging system
  • ApplicationFailedEvent:clean up logging system

4. Logback日志系统的配置

参考Spring Boot In Action - 使用Logback日志系统

5. Log4J2日志系统的配置

参考Spring Boot In Action - 使用Log4J2日志系统

6. 使用SLF4J Logger输出日志

Logger logger = LoggerFactory.getLogger(HelloWorldController.class);

SpringBoot启动过程中的日志处理过程:

  1. 在日志系统的配置尚未生效前,通过org.apache.commons.logging.LogFactory#getLog(java.lang.String)获取LoggerContext,使用各日志系统的默认配置。
  2. 使用最新的配置文件更新LoggerContext,更新前需要stop and reset LoggerContext。
  3. 使用属性配置更新各Logger的日志输出级别。
  4. 按照最新的LoggerContext进行日志处理。

附录

1. jul-to-slf4j

The jul-to-slf4j.jar artifact includes a java.util.logging (jul) handler, namely SLF4JBridgeHandler, which routes all incoming jul records to the SLF4j API. Please see SLF4JBridgeHandler javadocs for usage instructions.

2. 获取日志系统的逻辑

应用程序启动时检查 SYSTEMS 的所有 entry,如果 entry.key 对应的 classs 在应用的 classpath 中出现,则创建 entry.value 对应的日志系统。

org.springframework.boot.logging.LoggingSystem#get(java.lang.ClassLoader)

private static final Map<String, String> SYSTEMS;

static {
	Map<String, String> systems = new LinkedHashMap<>();
	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);
}

/**
 * Detect and return the logging system in use. Supports Logback and Java Logging.
 * @param classLoader the classloader
 * @return the logging system
 */
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);
	}
	return SYSTEMS.entrySet().stream()
			.filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
			.map((entry) -> get(classLoader, entry.getValue())).findFirst()
			.orElseThrow(() -> new IllegalStateException(
					"No suitable logging system located"));
}

3. 日志系统的初始化过程 - 涉及到文件配置和属性配置的优先级问题

org.springframework.boot.context.logging.LoggingApplicationListener#initialize

/**
 * 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);
	// 根据配置文件更新LoggerContext
	initializeSystem(environment, this.loggingSystem, logFile);
	// 根据环境变量更新最终日志级别
	initializeFinalLoggingLevels(environment, this.loggingSystem);
	registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

参考

jul-to-slf4j bridgeSimple Logging Facade for Java (SLF4J)Spring boot——logback 基础使用篇(一)
Logback Layout