For the program, the log is a very important part, so to speak, without the log program is incomplete. There are many logging solutions on the market. What kind of logging solution does Spring Boot provide? Can we learn from the Spring Boot scheme? This article discusses the following points while analyzing the Spring Boot logging scheme.
- Java logging system overview.
- How the logging framework loads logging implementations.
- Spring Boot log loading process.
- How does the Spring Boot logging configuration take effect?
Spring Boot log integration
The Spring Boot logging system relies on spring-boot-starter-logging. As shown below, we do not need to actively reference this JAR package during development. Because it is included in spring-boot-starter by default.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Copy the code
It’s also easy to use. Declare a logger in a member variable, insert the current class as a parameter into the factory method, and log using logger.info(” XXX “).
private final Logger logger = LoggerFactory.getLogger(DemoInitializer.class);
Copy the code
What is included in spring-boot-starter-logging?
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.13.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.30</version>
<scope>compile</scope>
</dependency>
</dependencies>
Copy the code
-
Logback-classic implements SLF4J. Spring Boot uses the slF4J + Logback logging mode.
-
Log4j-to-slf4j is a log4J-to-SLf4J adapter used to redirect programs that use the Log4J API to SLF4J.
-
Jul-to-slf4j is similar to log4j-to-slf4j in that juL logs are redirected to SLF4J.
Slf4j stands for Simple Logging Facade for Java. Java logging facade is a Java logging facade framework, generally speaking, is an abstract interface to logging, it is not responsible for the implementation of logging itself.
Logback is a logging implementation that follows the SLF4J specification. When we call the SLF4J interface to print, slF4J’s underlying call is actually logback.
Java Logging System
Slf4j + LogBack is the preferred approach in the current Java logging architecture. Let’s review the Java logging architecture in chronological order.
Log4j was one of the earliest logging systems. Before that, users could only print logs using Java’s own system.out.print(). Log4j emerged as the sole logging system and quickly became widely used as the de facto standard for Java logging, and it is still used by a large number of projects.
The JDK added JUL(Java Util Logging) logging implementation in 1.4. Because JUL was built into the JDK, it didn’t need to reference any JAR packages, and sun’s halo made it attractive to many users at the time. But the use of JUL has some drawbacks, and few people should be using JUL these days.
Given that log4J and JUL are both on the market, Apache provides Commons Logging to resolve this confusion. Commons Logging, known as JCL, allows you to mount different Logging systems and specify which Logging system to mount through a configuration file. By default, JCL automatically searches for and uses Log4j, and if Log4j is not found, JUL is used. In this case, JCL is sure to work for most scenarios and is pretty much a one-man operation. Even Spring relies on JCL.
The log4j author decided that the interface provided by JCL was not good enough, and he wanted to do something more elegant, so slF4J came up with it. Slf4j is positioned in the same way as JCL and is indeed much more user-friendly. Not only that, but the authors themselves created an implementation for SLF4J, logback. Performance is higher than log4j. Slf4j also provides some bridging ways to convert JCL. Spring Boot currently takes this approach.
Log4j is no longer recommended because log4j was discontinued in 2015 and the author went to maintain log4j2. The general framework layer uses SLF4J and the implementation layer uses log4j2 or LogBack. Thanks to the fact that log4j2 and Logback are the same author, the current mainstream usage of logs tends to be consistent.
How does SLF4J load logBack?
The entry point for slF4J to start invoking the log is the getLogger method.
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
Copy the code
GetILoggerFactory has a series of state judgments, just to determine the initialization state of the ILoggerFactory in the current environment. The first time it is called, it is definitely not loaded yet, so use the performInitialization method to initialize it. PerformInitialization executes the bind method.
public static ILoggerFactory getILoggerFactory(a) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if(INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); }}}switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
returnStaticLoggerBinder.getSingleton().getLoggerFactory(); . }}Copy the code
Slf4j itself does not provide an implementation of logging, so during the bind phase it looks for a possible implementation of SLf4J. How exactly is the agreement? We need to focus on next findPossibleStaticLoggerBinderPathSet method.
private final static void bind(a) {
try {
Set<URL> staticLoggerBinderPathSet = null;
if(! isAndroid()) {// Find out what implementations of SLF4J might existstaticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); }... }... }Copy the code
FindPossibleStaticLoggerBinderPathSet’s core idea is to find the org/slf4j/impl/StaticLoggerBinder. The class the class, if there are multiple, then return multiple paths.
// The agreed package path
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set<URL> findPossibleStaticLoggerBinderPathSet(a) {
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
/ / this
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
// Call the getResources method of classLoader
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while(paths.hasMoreElements()) { URL path = paths.nextElement(); staticLoggerBinderPathSet.add(path); }}catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
Copy the code
We find in the package path of logback org/slf4j impl/StaticLoggerBinder class – this class.
As always, we should use reflection to reflect the class at this point. Slf4j is not to do so, it will directly org. Slf4j. Impl. StaticLoggerBinder introduction, and then call the corresponding method to create a ILoggerFactory.
package org.slf4j;
import org.slf4j.impl.StaticLoggerBinder;
public static ILoggerFactory getILoggerFactory(a) {...switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
returnStaticLoggerBinder.getSingleton().getLoggerFactory(); . }}Copy the code
Slf4j uses the idea of convention first to specify the path of the implementation class, directly introduce the implementation class into the code, and generate log factory instances. It’s simple and efficient.
Spring Boot Log loading process
We mentioned above that the recommended logging method for Spring Boot projects is SLf4J + Logback. What about the logging framework used by the Spring Boot project itself?
In keeping with Spring, Spring Boot uses JCL as the logging framework, which allows logging to be printed without introducing any additional JAR packages.
When we start the Spring Boot program, a New Instance of SpringApplication will be created. This instance has one member variable, which is Logger. This type of logger is org.apache.com mons. Logging. The Log.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private static final Log logger = LogFactory.getLog(SpringApplication.class);
Copy the code
Why does Spring Boot print the same logs as slf4J? Let’s look at the follow-up process. The getLog method uses LogAdapter to get the actual Log implementation.
public static Log getLog(String name) {
return LogAdapter.createLog(name);
}
Copy the code
The LogAdapter determines which logging system is being used based on whether the current project has a executing class. The LogAdapter defines the following cases:
- With log4j2 dependencies and no bridge to SLf4J, use log4j.2.x.
- Has log4j2 dependencies and Bridges to SLf4J, using slf4J.
- There is no log4j2 dependency, but slf4J (ignoring the different uses of slf4J) is used.
- If not, use JUL.
final class LogAdapter {
/ / log4j2 provided
private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
/ / the log4j - to - slf4j provided
private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
/ / slf4j - API provides
private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
/ / slf4j - API provides
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
static {
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness supportlogApi = LogApi.LOG4J; }}else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as defaultlogApi = LogApi.JUL; }}private static boolean isPresent(String className) {
try {
Class.forName(className, false, LogAdapter.class.getClassLoader());
return true;
}
catch (ClassNotFoundException ex) {
return false; }}... }Copy the code
Why slF4J is in the JCL, and we can see that Spring 5 has updated the logging system. Spring 4 relies on JCL, while Spring 5 relies on Spring-JCL. Spring-jcl is an internal update to JCL that maintains the original JCL interface and adds support for Log4j2 and SLF4J. Spring then switches the logging system to log4j2 or SLF4J without changing the code, just by changing the dependencies.
If you go back to the createLog method, the actual logging system will be created using the corresponding adapter based on the current jar dependency determined by the LogAdapter static method.
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
returnJavaUtilAdapter.createLog(name); }}Copy the code
Since the slF4J dependency is included in spring-boot-starter-logging, Slf4jAdapter is called here. The next step is to delegate slF4J’s LoggerFactory to create the logging system. The conversion from JCL to SLF4J is perfectly completed.
private static class Slf4jAdapter {
public static Log createLocationAwareLog(String name) {
Logger logger = LoggerFactory.getLogger(name);
return (logger instanceof LocationAwareLogger ?
new Slf4jLocationAwareLog((LocationAwareLogger) logger) : newSlf4jLog<>(logger)); }}Copy the code
How does the logging configuration take effect in Spring Boot
This section is relatively trivial, if you are not interested in the internal principle, there is a conclusion at the end of the section, you can read it directly.
This is a copy of the logback configuration, stored in the same directory as application.properties. We will not introduce the meaning of each item in the configuration. We will learn about the loading time and policy of the configuration file.
<configuration debug="false">
<! -- File path -->
<property name="logPath" value="./logs" />
<! -- Log level -->
<property name="logLevel" value="INFO" />
<! -- Console output -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<! -- Output format -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<! -- Log file -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<! -- File name -->
<FileNamePattern>${logPath}/log.%d{yyyy-MM-dd}.log</FileNamePattern>
<! -- Retention days -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<! -- Output format -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<! -- Log file cutting threshold -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<root level="${logLevel}">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
Copy the code
As we mentioned earlier, slF4J uses getILoggerFactory to get the log factory class, by convention it loads the implementation class at the specified location through the ClassLoader. If there is one and only one, an implementation class exists. The next step is to get the configuration of the implementation class (logback, for example).
StaticLoggerBinder is responsible for obtaining and parsing the configuration in LogBack. This class has a static code block that loads the specified configuration file.
-
First is to look at the system variables have specified logback. ConfigurationFile, so, load this file.
-
Find files in the order is logback – test. The XML – > logback. Groovy – > logback. ConfigurationFile – > logback. The XML.
-
If none is present, LogBack provides a BasicConfigurator, which is the default and prints logs to the console.
final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if(url ! =null) {
return url;
}
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if(url ! =null) {
return url;
}
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if(url ! =null) {
return url;
}
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
Copy the code
This is how logback loads logging configurations, but in Spring Boot, we usually configure some configuration that starts with logging in application.properties, such as logging.file.path, Logging. The file. The name, etc. These configuration items can be read only if they depend on Spring Boot’s capabilities. This point in time is later than the logback loading log configuration. So if we configure application.properties but use the logback.xml configuration file, the configuration in application.properties will not take effect.
Spring Boot recommends using logback-spring. XML + application.properties to configure the file
So how does Spring Boot combine the configuration of logging itself with that provided by Spring Boot?
We talked about how Spring Boot listens for startup events. Spring Boot emits different events at different stages of startup. We can listen for these events by implementing ApplicationListener.
In the spring – the boot jar package found in the meta-inf spring. The factories, the realization of the ApplicationListener is a LoggingApplicationListener in class. This listener is key to implementing the custom configuration of Spring Boot logging.
# Application Listeners
org.springframework.context.ApplicationListener=
org.springframework.boot.context.logging.LoggingApplicationListener,
...
Copy the code
As can be seen from the onApplicationEvent method, LoggingApplicationListener to monitor more than one event, including ApplicationStartingEvent, ApplicationEnvironmentPreparedEvent ApplicationPreparedEvent, ContextClosedEvent and ApplicationFailedEvent. ApplicationEnvironmentPreparedEvent is the most concern in these events. This event indicates that the application configuration information has been resolved.
@Override
public void onApplicationEvent(ApplicationEvent event) {
// Get the current Logback, log4j2 or Java logging system
// The essence is to determine whether there is a convention class
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
// This method focuses on
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 instanceofApplicationFailedEvent) { onApplicationFailedEvent(); }}Copy the code
OnApplicationEnvironmentPreparedEvent method will be called the initialize to initialize the log configuration.
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
Copy the code
The initialize method has a long stack, so instead of listing the code, we will simply describe what each method does.
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
// Read log related configurations from all configurations
getLoggingSystemProperties(environment).apply();
// Generate logFile objects with logging.file.name and logging.file.path
// The reason this object is handled separately is that both objects in pure configuration mode decide whether to generate log files or not
this.logFile = LogFile.get(environment);
// If logFile is not empty, put the above two configurations in LoggingSystemProperties as well
if (this.logFile ! =null) {
this.logFile.applyToSystemProperties();
}
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
initializeEarlyLoggingLevel(environment);
// Read the log system configuration
initializeSystem(environment, this.loggingSystem, this.logFile);
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
Copy the code
Spring Boot defines a LoggingSystemProperties class that stores and resolves logging configurations. It stores common logging configurations such as logging.Pattern.file. Logging. The pattern. Level and so on. Its subclasses and log system, such as logback is LogbackLoggingSystemProperties, it stores the configuration logging system, Such as logging, logback. Rollingpolicy. Max – file – size and so on. So after the configuration information for the application is ready, the LoggingSystemProperties object is generated based on those configurations.
Once you have this configuration object, Spring Boot reinterprets the log configuration. This time the main body of the parse configuration is changed from Logback to Spring Boot. Files are parsed in the same order as logBack parses them.
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy"."logback-test.xml"."logback.groovy"."logback.xml" };
}
Copy the code
If none of these files exist, you need to obtain another configuration file. The rule is to add -spring to the name of the original configuration file, and the file type remains the same.
protected String[] getSpringConfigLocations() {
String[] locations = getStandardConfigLocations();
for (int i = 0; i < locations.length; i++) {
String extension = StringUtils.getFilenameExtension(locations[i]);
locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1) + "-spring."
+ extension;
}
return locations;
}
Copy the code
Spring Boot parsing log profiles differs from logback parsing log profiles in that Spring Boot allows environment variables to be present in log profiles (not all, but specific). Spring Boot can easily resolve these variables. Logback doesn’t work. This is why Spring Boot recommends using logback-spring. XML for configuration, since there is no way to read Spirng Boot configuration variables from logback. XML.
Another important reason is that when Spring Boot parses log files, it adds a rule to parse the springProfile node, which provides the basis for our multi-environment configuration. With it, we can easily customize different logging configurations for different environments.
@Override
public void addInstanceRules(RuleStore rs) {
super.addInstanceRules(rs);
Environment environment = this.initializationContext.getEnvironment();
rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment));
rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment));
rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction());
}
Copy the code
Let’s assume that we have done the following configuration in application.properties. Please refer to the official document Common Application Properties for full configuration
logging.file.path=./logs
logging.file.name=log
logging.pattern.level=ERROR
Copy the code
We can use the LOG_PATH, LOG_FILE, and LOG_LEVEL_PATTERN variables in logback-spring. XML. The springProfile node is used to determine whether the current environment meets the criteria. In a real project, you can use these environment variables flexibly according to your needs.
<configuration debug="false">
<! -- Console output -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<! -- Output format -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<! -- Log file -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<! -- File name -->
<FileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.log</FileNamePattern>
<! -- Retention days -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<! -- Output format -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<! -- Log file cutting threshold -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<springProfile name="dev">
<root level="${LOG_LEVEL_PATTERN}">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</springProfile>
<springProfile name="test,prod">
<root level="${LOG_LEVEL_PATTERN}">
<appender-ref ref="FILE" />
</root>
</springProfile>
</configuration>
Copy the code
In another case, our project does not have any configuration files, and all configuration is written in application.properties, which is supported by Sping Boot. However, this configuration is limited and only applies to simple scenarios. Let’s summarize the enhancements Spring Boot has made to logback configuration.
-
First, logback automatically loads the logback.xml configuration file upon initialization (other file names are ignored for simplicity). In this case, all configuration does not take effect until the Spring environment is initialized, and logback. XML must be written in accordance with the logback standard. If there is no configuration file, logBack has a configuration scheme by default, which is printed to the console.
-
Spring Boot has a built-in listener that reconfigures logBack after listening for the time when the environment variable is initialized. The configuration in application.properties is now resolved.
-
Determine if there is a native configuration file, logback.xml. If so, and no logging.file.path or logging.file.name is configured in application.properties. Use the configuration in logback.xml. The process stops. Even if logging.file.path or logging.file.name is configured, it does not take effect. It just adds one more step to the process of writing this configuration to the logging configuration class.
-
Check whether logback-sping.xml exists. If it exists, Sping Boot parses the file, and logback-sping.xml can use not only the configuration in application.properties (for specific ones), but also the springProfile node for environment judgment.
-
There are no configuration files and a default configuration is constructed with the configuration parameters in application.properties. The rules are constructed according to the meaning of each configuration in the official document Common Application Properties.
conclusion
-
In Java logging, JUL is a thing of the past and Log4j stops updating. JCL also exists in name only after Spring 5. Slf4j is recommended as the logging facade framework and Logback or Log4j2 as the logging system. The simplest way to add logging to a new system is to introduce the Spring-boot-starter-logging package, slF4J and Logback in one go.
-
Slf4j uses the idea of convention first to specify the path of the implementation class, directly introduce the implementation class into the code, and generate log factory instances. This is how SLF4J implements the logging facade.
-
After Spring 5, JCL was updated to Spring-JCL. Spring-jcl retains the original interface, but the implementation is based on the existing classes in the current project. Bridge the logging system to log4j2 or SLF4J, and at this point JCL exists in Spring in name only.
-
Spring Boot introduces the spring-boot-starter-logging package by default and uses slF4J + Logback to record logs. Since logback itself reads configuration files, time takes precedence over initialization of Spring environment variables. Spring Boot has redesigned a configuration resolution approach using event listening, adding logging configuration support for application.properties, and multi-environment configuration support.
If you think you’ve learned something, please give it a thumbs up!