一、引言
二、spring-boot-starter-logging介绍
四、日志框架加载源码分析
五、结论
一、引言
Spring Boot 3在日志处理方面提供了一套灵活且强大的解决方案。默认情况下,Spring Boot 3使用SLF4J(Simple Logging Facade for Java)作为日志门面,而Logback作为日志的实现框架。
SLF4J是一个抽象层,允许开发者在底层更换具体的日志实现而无需修改代码。Logback则是一个高性能的日志框架,由Log4j的作者设计,提供了丰富的配置选项和强大的功能。Spring Boot 3通过spring-boot-starter-logging启动器简化了Logback日志的配置和使用。
本文使用的Spring Boot版本为3.3.5
二、spring-boot-starter-logging介绍
spring-boot-starter-logging是Spring Boot提供的一个日志启动器,它默认集成了SLF4J作为日志门面,并默认使用Logback作为日志实现框架。通过引入这个启动器,开发者可以轻松地在Spring Boot应用程序中使用日志功能,而无需进行复杂的配置。
2.1 日志门面与实现
日志门面
:SLF4J是一个为各种日志框架提供统一接口的抽象层。通过SLF4J,开发者可以在不改变应用程序代码的情况下,灵活地切换和配置不同的日志实现框架。
日志实现
:Logback是Log4j框架的作者开发的新一代日志框架,它效率更高、能够适应多种运行环境,并且天然支持SLF4J。
关于SLF4J实现原理,参见:SLF4J 门面日志框架原理分析
2.2 配置方式
Spring Boot 3默认使用Logback作为日志实现框架,并提供了一套默认的日志配置。这些配置包括日志级别、日志格式、日志输出等,开发者可以通过修改配置文件(如application.properties或application.yml)来自定义这些设置。
1. application.properties或application.yml
配置方式
:
在application.properties中,可以使用键值对的形式来设置日志相关的属性。
在application.yml中,则使用YAML语法来配置日志属性,通常具有更清晰的层次结构。
优点
:
这些文件是Spring Boot应用程序的标准配置文件,用于存储各种应用程序设置,包括日志设置。
它们提供了方便的方式来修改日志级别、文件位置等常用配置。
局限性
:
对于复杂的日志配置(如多个Appender、自定义Layout等),使用这些文件可能会变得冗长且难以管理。
这些文件通常不支持Logback的所有高级配置选项。
2. logback.xml
配置方式
:
logback.xml是Logback框架专用的配置文件,使用XML语法来定义日志配置。
可以在这个文件中定义多个Appender、Filter、Logger等组件,以及它们之间的关系。
优点
:
提供了丰富的配置选项,可以满足复杂的日志需求。
支持高级功能,如日志文件的滚动、分割、压缩等。
可以通过定义变量来简化配置,提高可读性和可维护性。
推荐
:
Spring Boot官方推荐优先使用带有-spring后缀的文件名(如logback-spring.xml)作为日志配置文件,因为这样可以利用Spring Boot提供的特定配置项和功能。
logback.xml文件通常放在src/main/resources目录下,以确保在应用程序启动时能够被正确加载。
3. 区别总结
配置灵活性
:
application.properties或application.yml提供了基本的日志配置选项,适合简单的日志需求。
logback.xml提供了更高级、更灵活的日志配置选项,适合复杂的日志需求。
可维护性
:
对于简单的日志配置,使用application.properties或application.yml可能更容易理解和维护。
对于复杂的日志配置,使用logback.xml可能更合适,因为它提供了更清晰的结构和更丰富的功能。
Spring Boot特性
:
使用logback-spring.xml可以利用Spring Boot的特定配置项和功能,这些在logback.xml中可能无法直接实现。
在选择使用哪种配置文件来自定义Logback
的日志设置时,应根据具体的应用场景和需求来决定。
- 如果只需要简单的日志配置,可以考虑使用application.properties或application.yml;
- 如果需要更高级、更灵活的日志配置,则应选择logback.xml(或logback-spring.xml)。
2.3 使用示例
1. 添加依赖
要在Spring Boot项目中使用spring-boot-starter-logging,只需在项目的pom.xml文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
添加这个依赖后,Spring Boot会自动配置日志系统,开发者无需进行额外的配置即可开始使用日志功能。
有些starter会间接引入spring-boot-starter-logging,如spring-boot-starter或者spring-boot-starter-web等。
引入spring-boot-starter-logging,间接引入了三个依赖logback-classic、log4j-to-slf4j和jul-to-slf4j
【图】spring-boot-starter-logging依赖
logback-classic是默认的日志实现类
log4j-to-slf4j和jul-to-slf4j 是桥接器,可以桥接代码中的log4j 2和JUL日志实现框架的API到SLF4J API。关于桥接器,参见:SLF4J 桥接器及其原理--让你的旧代码也可以起飞
2. 自定义日志配置
虽然默认配置对于开发环境已经足够使用,但在生产环境中,开发者可能需要根据实际需求自定义日志配置。
以下是一些常见的自定义配置选项:
日志级别
:通过logging.level属性可以设置不同包或类的日志级别。
例如:
logging:
level:
root: WARN
org.springframework.web: DEBUG
com.example: INFO
日志格式
:通过logging.pattern.console和logging.pattern.dateformat属性可以自定义日志的输出格式和日期格式。
例如:
logging:
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{15} : %msg%n'
dateformat: 'yyyy-MM-dd HH:mm'
日志输出
:默认情况下,日志会输出到控制台。如果需要将日志输出到文件,可以通过logging.file或logging.path属性进行配置。
例如:
logging:
file:
name: myapp.log
path: /var/logs
更复杂的配置可以通过单独的配置文件logback.xml进行配置。
三、切换日志框架
虽然Spring Boot 3默认使用Logback作为日志实现框架,但开发者也可以通过排除默认的日志依赖并添加其他日志框架的依赖来切换日志框架。
例如,要切换到Log4j2,可以在pom.xml中添加以下依赖并排除Logback依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
在spring-boot-starter-parent官方提供的依赖中,除了默认使用的spring-boot-starter-logging(它基于Logback)和可选的spring-boot-starter-log4j2之外,没有直接提供其他日志框架的starter。但是,Spring Boot的灵活性允许你通过添加其他日志框架的依赖和配置来使用它们。
关于日志框架,参见:一文理清 Java 日志框架的来龙去脉
四、日志框架加载源码分析
4.1 日志框架的加载
在Spring Boot的启动过程中,日志框架的加载是通过LoggingApplicationListener(ApplicationListener的实现类)实现的。具体来说,SpringApplication类在初始化时会通过SPI机制读取spring.factories文件中的ApplicationListener接口实现类LoggingApplicationListener,并将它注册到SpringApplication实例中。
【图】SpringApplication通过SPI机制加载ApplicationListener
【图】Spring Boot 启动加载LoggingApplicationListener
【图】spring.factories中的LoggingApplicationListener配置
关于Spring SPI机制,参见:Java SPI机制及其与Spring SPI、Spring Boot SPI的异同
关于Spring Boot监听器机制,参见:
LoggingApplicationListener监听器的核心源码如下:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent startingEvent) {
onApplicationStartingEvent(startingEvent);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent environmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(environmentPreparedEvent);
}
else if (event instanceof ApplicationPreparedEvent preparedEvent) {
onApplicationPreparedEvent(preparedEvent);
}
else if (event instanceof ContextClosedEvent contextClosedEvent) {
onContextClosedEvent(contextClosedEvent);
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
SpringApplication springApplication = event.getSpringApplication();
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(springApplication.getClassLoader());
}
initialize(event.getEnvironment(), springApplication.getClassLoader());
}
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
ConfigurableApplicationContext applicationContext = event.getApplicationContext();
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
}
if (this.logFile != null && !beanFactory.containsBean(LOG_FILE_BEAN_NAME)) {
beanFactory.registerSingleton(LOG_FILE_BEAN_NAME, this.logFile);
}
if (this.loggerGroups != null && !beanFactory.containsBean(LOGGER_GROUPS_BEAN_NAME)) {
beanFactory.registerSingleton(LOGGER_GROUPS_BEAN_NAME, this.loggerGroups);
}
if (!beanFactory.containsBean(LOGGING_LIFECYCLE_BEAN_NAME) && applicationContext.getParent() == null) {
beanFactory.registerSingleton(LOGGING_LIFECYCLE_BEAN_NAME, new Lifecycle());
}
}
private void onContextClosedEvent(ContextClosedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
if (applicationContext.getParent() != null || applicationContext.containsBean(LOGGING_LIFECYCLE_BEAN_NAME)) {
return;
}
cleanupLoggingSystem();
}
void cleanupLoggingSystem() {
if (this.loggingSystem != null) {
this.loggingSystem.cleanUp();
}
}
private void onApplicationFailedEvent() {
cleanupLoggingSystem();
}
onApplicationEvent:处理应用程序的各种事件,根据事件类型调用相应的处理方法。
onApplicationStartingEvent:在应用程序启动时,获取日志系统并调用 beforeInitialize 方法进行初始化。
onApplicationEnvironmentPreparedEvent:在环境准备完成后,获取 SpringApplication 对象,检查日志系统是否已初始化,如果没有则初始化日志系统。
onApplicationPreparedEvent:在应用程序准备完成后,获取 ApplicationContext 对象,注册日志系统相关的单例 Bean。
onContextClosedEvent:在应用上下文关闭时,检查是否有父上下文或已注册的生命周期 Bean,如果没有则清理日志系统。
cleanupLoggingSystem:清理日志系统,调用 cleanUp 方法。
onApplicationFailedEvent:在应用程序启动失败时,清理日志系统。
initialize:根据环境和类加载器初始化日志系统,包括应用系统属性、获取 LogFile 并设置系统属性、初始化日志组、初始化早期日志级别、初始化系统、初始化最终日志级别、注册关闭钩子。
4.2 日志配置的加载
Spring Boot通过LoggingApplicationListener类来加载日志配置。这个类会在应用程序启动时检查application.properties或application.yml等配置文件中的日志相关配置,并根据这些配置来初始化日志系统。如果配置文件中指定了自定义的日志配置文件(如logback.xml),LoggingApplicationListener会加载并解析这个文件来应用自定义的日志配置。
监听启动事件onApplicationStartingEvent
在上面源码第2步骤onApplicationStartingEvent监听启动事件ApplicationStartingEvent,该方法会获取系统配置的日志系统工厂。源码如下:
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
这个配置是通过Spring SPI机制加载的,Spring Boot包的META-INF/spring.factories中关于日志系统的配置如下:
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
通过调用org.springframework.boot.logging.LoggingSystemFactory#fromSpringFactories方法加载。源码如下
package org.springframework.boot.logging;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Factory class used by {@link LoggingSystem#get(ClassLoader)} to find an actual
* implementation.
*
* @author Phillip Webb
* @since 2.4.0
*/
public interface LoggingSystemFactory {
/**
* Return a logging system implementation or {@code null} if no logging system is
* available.
* @param classLoader the class loader to use
* @return a logging system
*/
LoggingSystem getLoggingSystem(ClassLoader classLoader);
/**
* Return a {@link LoggingSystemFactory} backed by {@code spring.factories}.
* @return a {@link LoggingSystemFactory} instance
*/
static LoggingSystemFactory fromSpringFactories() {
return new DelegatingLoggingSystemFactory(
(classLoader) -> SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader));
}
}
调用栈如下:
【图】LoggingSystemFactory#fromSpringFactories调用栈
在org.springframework.boot.logging.LoggingSystem#get(java.lang.ClassLoader)会调用org.springframework.boot.logging.DelegatingLoggingSystemFactory#getLoggingSystem方法获取日志工厂。从下图可以看到加载了3个日志系统工厂。
【图】3个日志系统工厂
分别看下这三个日志工厂的内部类Factory的源码:
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory
/**
* {@link LoggingSystemFactory} that returns {@link LogbackLoggingSystem} if possible.
*/
@Order(Ordered.LOWEST_PRECEDENCE)
public static class Factory implements LoggingSystemFactory {
private static final boolean PRESENT = ClassUtils.isPresent("ch.qos.logback.classic.LoggerContext",
Factory.class.getClassLoader());
@Override
public LoggingSystem getLoggingSystem(ClassLoader classLoader) {
if (PRESENT) {
return new LogbackLoggingSystem(classLoader);
}
return null;
}
}
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory
/**
* {@link LoggingSystemFactory} that returns {@link Log4J2LoggingSystem} if possible.
*/
@Order(Ordered.LOWEST_PRECEDENCE)
public static class Factory implements LoggingSystemFactory {
private static final boolean PRESENT = ClassUtils
.isPresent("org.apache.logging.log4j.core.impl.Log4jContextFactory", Factory.class.getClassLoader());
@Override
public LoggingSystem getLoggingSystem(ClassLoader classLoader) {
if (PRESENT) {
return new Log4J2LoggingSystem(classLoader);
}
return null;
}
}
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
/**
* {@link LoggingSystemFactory} that returns {@link JavaLoggingSystem} if possible.
*/
@Order(Ordered.LOWEST_PRECEDENCE)
public static class Factory implements LoggingSystemFactory {
private static final boolean PRESENT = ClassUtils.isPresent("java.util.logging.LogManager",
Factory.class.getClassLoader());
@Override
public LoggingSystem getLoggingSystem(ClassLoader classLoader) {
if (PRESENT) {
return new JavaLoggingSystem(classLoader);
}
return null;
}
}
这3个工厂的PRESENT变量是在SPI加载这几个LoggingSystemFactory时初始化的,默认情况下,使用的是Logback,因此它的PRESENT为true。
【图】获取Logback日志系统LogbackLoggingSystem
判断条件org.apache.logging.log4j.core.impl.Log4jContextFactory不存在,因此Log4J2LoggingSystem.Factory的PRESENT为false。
而ch.qos.logback.classic.LoggerContext存在,因此LogbackLoggingSystem.Factory的PRESENT为true。
java.util.logging.LogManager是JDK自带的,因此JavaLoggingSystem.Factory的PRESENT也为true。
【图】ch.qos.logback.classic.LoggerContext存在
DelegatingLoggingSystemFactory#getLoggingSystem方法源码如下:
public LoggingSystem getLoggingSystem(ClassLoader classLoader) {
List<LoggingSystemFactory> delegates = (this.delegates != null) ? this.delegates.apply(classLoader) : null;
if (delegates != null) {
for (LoggingSystemFactory delegate : delegates) {
LoggingSystem loggingSystem = delegate.getLoggingSystem(classLoader);
if (loggingSystem != null) {
return loggingSystem;
}
}
}
return null;
}
delegates中的顺序如下:
【图】delegates
【图】返回LogbackLoggingSystem
遍历delegates,代码delegate.getLoggingSystem(classLoader);根据classloader获取系统日志工厂。这里可以看到,按顺序找到第一个不为null的日志系统就会返回。也就是说,在同时引入了Logback和Log4j 2的日志框架时,也会使用Logback。同时引入多个日志框架时,控制台打印如下:
SLF4J(W): Class path contains multiple SLF4J providers.
SLF4J(W): Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@2e5d6d97]
SLF4J(W): Found provider [org.apache.logging.slf4j.SLF4JServiceProvider@238e0d81]
SLF4J(W): See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J(I): Actual provider is of type [ch.qos.logback.classic.spi.LogbackServiceProvider@2e5d6d97]
我们继续看LoggingApplicationListener#onApplicationStartingEvent源码。 之后this.loggingSystem.beforeInitialize();调用日志系统的beforeInitialize方法,源码如下:
public void beforeInitialize() {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
super.beforeInitialize();
configureJdkLoggingBridgeHandler();
loggerContext.getTurboFilterList().add(FILTER);
}
至此,监听启动事件onApplicationStartingEvent所做的工作就完成了。主要就是获取日志实现的上下文,后续就会通过该上下文进行日志配置的加载。Logback的上下文就是ch.qos.logback.classic.LoggerContext。
监听环境准备事件onApplicationEnvironmentPreparedEvent
在上面源码的第3步的onApplicationEnvironmentPreparedEvent方法中,监听到事件ApplicationEnvironmentPreparedEvent后,会加载配置文件中的配置。该方法调用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) {
getLoggingSystemProperties(environment).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
this.logFile.applyToSystemProperties();
}
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
initializeEarlyLoggingLevel(environment);
initializeSystem(environment, this.loggingSystem, this.logFile);
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
获取日志系统属性:调用 getLoggingSystemProperties(environment).apply() 获取并应用日志系统的属性。
获取日志文件:通过 LogFile.get(environment) 获取日志文件对象,并将其应用到系统属性中。
初始化日志组:创建 LoggerGroups 对象,并传入默认的日志组配置。
初始化早期日志级别:调用 initializeEarlyLoggingLevel(environment) 初始化早期的日志级别。
初始化日志系统:调用 initializeSystem(environment, this.loggingSystem, this.logFile) 初始化日志系统(使用具体实现类的LoggingSystem类加载)。
初始化最终日志级别:调用 initializeFinalLoggingLevels(environment, this.loggingSystem) 设置最终的日志级别。
注册关闭钩子:调用 registerShutdownHookIfNecessary(environment, this.loggingSystem) 注册关闭钩子,以便在 JVM 退出时关闭日志系统。
在Spring Boot中,日志配置文件的查找和加载是由 LoggingSystem 接口及其具体实现类来完成的。LoggingApplicationListener 会使用 LoggingSystem 来进行具体的日志系统初始化工作。在上面的分析中,我们已经获取到LoggingSystem是使用的LogbackLoggingSystem。
调用org.springframework.boot.logging.LoggingSystemProperties#apply(org.springframework.boot.logging.LogFile, org.springframework.core.env.PropertyResolver)方法加载系统配置。源码如下:
protected void apply(LogFile logFile, PropertyResolver resolver) {
String defaultCharsetName = getDefaultCharset().name();
setApplicationNameSystemProperty(resolver);
setSystemProperty(LoggingSystemProperty.PID, new ApplicationPid().toString());
setSystemProperty(LoggingSystemProperty.CONSOLE_CHARSET, resolver, defaultCharsetName);
setSystemProperty(LoggingSystemProperty.FILE_CHARSET, resolver, defaultCharsetName);
setSystemProperty(LoggingSystemProperty.CONSOLE_THRESHOLD, resolver, this::thresholdMapper);
setSystemProperty(LoggingSystemProperty.FILE_THRESHOLD, resolver, this::thresholdMapper);
setSystemProperty(LoggingSystemProperty.EXCEPTION_CONVERSION_WORD, resolver);
setSystemProperty(LoggingSystemProperty.CONSOLE_PATTERN, resolver);
setSystemProperty(LoggingSystemProperty.FILE_PATTERN, resolver);
setSystemProperty(LoggingSystemProperty.LEVEL_PATTERN, resolver);
setSystemProperty(LoggingSystemProperty.DATEFORMAT_PATTERN, resolver);
setSystemProperty(LoggingSystemProperty.CORRELATION_PATTERN, resolver);
if (logFile != null) {
logFile.applyToSystemProperties();
}
}
配置项是通过LoggingSystemProperty
枚举类定义的。源码如下:
public enum LoggingSystemProperty {
/**
* Logging system property for the application name that should be logged.
*/
APPLICATION_NAME("LOGGED_APPLICATION_NAME"),
/**
* Logging system property for the process ID.
*/
PID("PID"),
/**
* Logging system property for the log file.
*/
LOG_FILE("LOG_FILE"),
/**
* Logging system property for the log path.
*/
LOG_PATH("LOG_PATH"),
/**
* Logging system property for the console log charset.
*/
CONSOLE_CHARSET("CONSOLE_LOG_CHARSET", "logging.charset.console"),
/**
* Logging system property for the file log charset.
*/
FILE_CHARSET("FILE_LOG_CHARSET", "logging.charset.file"),
/**
* Logging system property for the console log.
*/
CONSOLE_THRESHOLD("CONSOLE_LOG_THRESHOLD", "logging.threshold.console"),
/**
* Logging system property for the file log.
*/
FILE_THRESHOLD("FILE_LOG_THRESHOLD", "logging.threshold.file"),
/**
* Logging system property for the exception conversion word.
*/
EXCEPTION_CONVERSION_WORD("LOG_EXCEPTION_CONVERSION_WORD", "logging.exception-conversion-word"),
/**
* Logging system property for the console log pattern.
*/
CONSOLE_PATTERN("CONSOLE_LOG_PATTERN", "logging.pattern.console"),
/**
* Logging system property for the file log pattern.
*/
FILE_PATTERN("FILE_LOG_PATTERN", "logging.pattern.file"),
/**
* Logging system property for the log level pattern.
*/
LEVEL_PATTERN("LOG_LEVEL_PATTERN", "logging.pattern.level"),
/**
* Logging system property for the date-format pattern.
*/
DATEFORMAT_PATTERN("LOG_DATEFORMAT_PATTERN", "logging.pattern.dateformat"),
/**
* Logging system property for the correlation pattern.
*/
CORRELATION_PATTERN("LOG_CORRELATION_PATTERN", "logging.pattern.correlation");
}
【图】LoggingSystemProperties#apply方法调用栈
之后调用org.springframework.boot.logging.logback.LogbackLoggingSystemProperties#applyRollingPolicyProperties方法应用滚动策略相关系统属性,源码如下:
private void applyRollingPolicyProperties(PropertyResolver resolver) {
applyRollingPolicy(RollingPolicySystemProperty.FILE_NAME_PATTERN, resolver);
applyRollingPolicy(RollingPolicySystemProperty.CLEAN_HISTORY_ON_START, resolver);
applyRollingPolicy(RollingPolicySystemProperty.MAX_FILE_SIZE, resolver, DataSize.class);
applyRollingPolicy(RollingPolicySystemProperty.TOTAL_SIZE_CAP, resolver, DataSize.class);
applyRollingPolicy(RollingPolicySystemProperty.MAX_HISTORY, resolver);
}
应用文件名模式:调用 applyRollingPolicy 方法,传入 RollingPolicySystemProperty.FILE_NAME_PATTERN 和 resolver 参数。
应用启动时清理历史记录:调用 applyRollingPolicy 方法,传入 RollingPolicySystemProperty.CLEAN_HISTORY_ON_START 和 resolver 参数。
应用日志文件最大大小:调用 applyRollingPolicy 方法,传入 RollingPolicySystemProperty.MAX_FILE_SIZE、resolver 和 DataSize.class 参数。
应用总大小上限:调用 applyRollingPolicy 方法,传入 RollingPolicySystemProperty.TOTAL_SIZE_CAP、resolver 和 DataSize.class 参数。
应用日志文件最大历史记录数:调用 applyRollingPolicy 方法,传入 RollingPolicySystemProperty.MAX_HISTORY 和 resolver 参数。
之后会调用方法org.springframework.boot.logging.AbstractLoggingSystem#initializeWithConventions控制使用logback.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);
}
这段代码的主要功能是根据不同的配置文件初始化日志系统。
具体步骤如下:
1、获取自定义初始化配置:调用 getSelfInitializationConfig() 获取自定义初始化配置。
2、检查配置和日志文件:
如果配置存在且日志文件为 null,则重新初始化日志系统。
如果配置不存在,则尝试获取 Spring 初始化配置。
3、加载配置:
如果配置存在,加载指定的配置文件。
如果配置不存在,加载默认配置。
如果是配置了logback.xml,最终调用org.springframework.boot.logging.logback.LogbackLoggingSystem#configureByResourceUrl加载配置文件。
源码如下:
private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext,
URL url) throws JoranException {
if (url.getPath().endsWith(".xml")) {
JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
configurator.setContext(loggerContext);
configurator.doConfigure(url);
}
else {
throw new IllegalArgumentException("Unsupported file extension in '" + url + "'. Only .xml is supported");
}
}
【图】LogbackLoggingSystem#configureByResourceUrl调用栈
这里构建了JoranConfigurator,该类就是加载logback.xml
中的配置的。
源码如下:
/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.classic.joran;
import ch.qos.logback.classic.joran.action.*;
import ch.qos.logback.classic.joran.sanity.IfNestedWithinSecondPhaseElementSC;
import ch.qos.logback.classic.model.processor.ConfigurationModelHandlerFull;
import ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.joran.JoranConfiguratorBase;
import ch.qos.logback.core.joran.action.AppenderRefAction;
import ch.qos.logback.core.joran.action.IncludeAction;
import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
import ch.qos.logback.core.joran.spi.ElementSelector;
import ch.qos.logback.core.joran.spi.RuleStore;
import ch.qos.logback.core.model.Model;
import ch.qos.logback.core.model.processor.DefaultProcessor;
/**
* JoranConfigurator class adds rules specific to logback-classic.
*
* @author Ceki Gülcü
*/
public class JoranConfigurator extends JoranConfiguratorBase<ILoggingEvent> {
@Override
public void addElementSelectorAndActionAssociations(RuleStore rs) {
// add parent rules
super.addElementSelectorAndActionAssociations(rs);
rs.addRule(new ElementSelector("configuration"), () -> new ConfigurationAction());
rs.addRule(new ElementSelector("configuration/contextName"), () -> new ContextNameAction());
rs.addRule(new ElementSelector("configuration/contextListener"), () -> new LoggerContextListenerAction());
rs.addRule(new ElementSelector("configuration/insertFromJNDI"), () -> new InsertFromJNDIAction());
rs.addRule(new ElementSelector("configuration/logger"), () -> new LoggerAction());
rs.addRule(new ElementSelector("configuration/logger/level"), () -> new LevelAction());
rs.addRule(new ElementSelector("configuration/root"), () -> new RootLoggerAction());
rs.addRule(new ElementSelector("configuration/root/level"), () -> new LevelAction());
rs.addRule(new ElementSelector("configuration/logger/appender-ref"), () -> new AppenderRefAction());
rs.addRule(new ElementSelector("configuration/root/appender-ref"), () -> new AppenderRefAction());
rs.addRule(new ElementSelector("configuration/include"), () -> new IncludeAction());
rs.addRule(new ElementSelector("configuration/propertiesConfigurator"), () -> new PropertiesConfiguratorAction());
rs.addRule(new ElementSelector("configuration/consolePlugin"), () -> new ConsolePluginAction());
rs.addRule(new ElementSelector("configuration/receiver"), () -> new ReceiverAction());
}
@Override
protected void sanityCheck(Model topModel) {
super.sanityCheck(topModel);
performCheck(new IfNestedWithinSecondPhaseElementSC(), topModel);
}
@Override
protected void addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegistry registry) {
LogbackClassicDefaultNestedComponentRules.addDefaultNestedComponentRegistryRules(registry);
}
private JoranConfigurator makeAnotherInstance() {
JoranConfigurator jc = new JoranConfigurator();
jc.setContext(context);
return jc;
}
public void buildModelInterpretationContext() {
super.buildModelInterpretationContext();
this.modelInterpretationContext.setConfiguratorSupplier( () -> this.makeAnotherInstance() );
}
@Override
protected void addModelHandlerAssociations(DefaultProcessor defaultProcessor) {
ModelClassToModelHandlerLinker m = new ModelClassToModelHandlerLinker(context);
m.setConfigurationModelHandlerFactoryMethod(ConfigurationModelHandlerFull::makeInstance2);
m.link(defaultProcessor);
}
// The final filters in the two filter chain are rather crucial.
// They ensure that only Models attached to the firstPhaseFilter will
// be handled in the first phase and all models not previously handled
// in the second phase will be handled in a catch-all fallback case.
private void sealModelFilters(DefaultProcessor defaultProcessor) {
defaultProcessor.getPhaseOneFilter().denyAll();
defaultProcessor.getPhaseTwoFilter().allowAll();
}
}
JoranConfigurator
类,继承自 JoranConfiguratorBase<ILoggingEvent>
。
主要功能包括:
添加配置规则:通过 addElementSelectorAndActionAssociations 方法,为不同的XML配置元素(如 configuration, logger, root 等)添加对应的处理动作(如 ConfigurationAction, LoggerAction, RootLoggerAction 等)。
合理性检查:通过 sanityCheck 方法,对配置模型进行合理性检查,确保配置文件的正确性。
默认嵌套组件注册:通过 addDefaultNestedComponentRegistryRules 方法,注册默认的嵌套组件规则。
构建模型解析上下文:通过 buildModelInterpretationContext 方法,设置配置器实例的创建方式。
模型处理器关联:通过 addModelHandlerAssociations 方法,将模型类与处理器进行关联。
密封模型过滤器:通过 sealModelFilters 方法,确保模型在两个阶段的过滤器中正确处理。
如果没有配置文件logback.xml,会调用org.springframework.boot.logging.logback.LogbackLoggingSystem#loadDefaults方法加载默认Logback配置。源码如下:
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
LoggerContext context = getLoggerContext();
stopAndReset(context);
withLoggingSuppressed(() -> {
boolean debug = Boolean.getBoolean("logback.debug");
if (debug) {
StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
}
Environment environment = initializationContext.getEnvironment();
// Apply system properties directly in case the same JVM runs multiple apps
new LogbackLoggingSystemProperties(environment, getDefaultValueResolver(environment), context::putProperty)
.apply(logFile);
LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(context)
: new LogbackConfigurator(context);
new DefaultLogbackConfiguration(logFile).apply(configurator);
context.setPackagingDataEnabled(true);
context.start();
});
}
【图】LogbackLoggingSystem#loadDefaults方法调用栈
到这里Spring Boot 3中的日志框架初始化过程就基本就分析完成了。具体的配置属性解析,这里不再继续深入,感兴趣的同学可以继续阅读源码。
五、结论
spring-boot-starter-logging为Spring Boot应用程序提供了强大的日志功能。通过简单的配置和灵活的日志框架切换机制,开发者可以轻松地记录和管理应用程序的日志信息。同时,通过源码分析我们可以更深入地了解Spring Boot如何加载和初始化日志系统。希望本文能够帮助开发者更好地理解和使用spring-boot-starter-logging。
标签:logging,springboot,Spring,配置,boot,源码,new,日志,logback From: https://www.cnblogs.com/o-O-oO/p/18590549原创 程序员Ink