We work in Spring Boot with SLF4J, but not with the application programming interface (API) of the actual logging provider.
We usually don’t notice which logging implementation is doing the actual work—that is completely abstracted by Spring Boot.
The Spring team has chosen Logback (https://logback.qos.ch) as an implementation. Logback is the successor of the popular Log4j 1.x library. Logback was followed by Log4j 2 (https://logging.apache.org/log4j/2.x), but the Spring team hasn’t yet moved to Log4j 2, even though the topic keeps coming up as this figure proves.
Conversion to Log4j 2
Although Spring Boot uses Logback as a logger provider by default, Log4j 2 can easily be brought in. This is done by first taking out spring-boot-starter-logging and then including spring-boot-starter-log4j2:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-****</artifactId>
<!-- remove logback -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- add log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
Logging Pattern Layout
Spring configures the loggers with a pattern layout. An example output looks like this:
2024-10-27T18:43:29.148+02:00 INFO 22280 --- [ main] ↩
c.t.s.SpringShellApplication: No active profile set.
The default logging format of Spring Boot follows a specific pattern. It starts with a timestamp, followed by the log level, which, in this case, is INFO. The process ID is then displayed, followed by the thread name, which, in our case, is main. Next, the logger name is written, which is usually the same as the class name. Finally, we have the actual log message and possibly a stack trace.
This pattern is configurable and defined for Logback in the defaults.xml file. There are two patterns defined: one for console output and a separate pattern for log files. For console output, the pattern looks like this:
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_
DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_
PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([
%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_
EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
Let’s go through the log output with the format again in detail. The pattern in the XML file can be decomposed into the components that are shown in this table.
For Log4j 2, the settings look similar.
Change the Logging Configuration
Spring Boot defines a whole set of generic configuration properties that are mapped to the corresponding logger implementations. The log settings are located under the logging root element:
- file.name: Logging to a given file instead of the console.
- file.path: Path for the log file (spring.log by default).
- pattern.console: Pattern for log messages on the console.
- pattern.file: Pattern for log messages in the log file.
There are quite a few other things you can configure. The Spring Boot reference documentation elaborates on this.
Banner
By default, after starting up, Spring Boot displays a banner. However, we disabled the banner early on by setting a configuration property. The text for the banner is built-in and comes from a Java archive, but it can be easily replaced with custom text. To define your own banner, simply create a text file named banner.txt, and place it directly in the source class path under src/main/resources.
Tip: There are various websites that compose text from letters, numbers, and special characters into large decorative letters. A well-known program is FIGlet (www.figlet.org). There are command-line tools and various web pages that generate a large text from a text and a chosen FIGlet font—there are dozens of them. Here’s an example of the text “Spring”:
o-o
| o
o-o o-o o-o o-o o--o
| | | | | | | | |
o--o O-o o | o o o--O
| |
o o--o
The lines can be copied directly into a separate file banner.txt.
Variables in Banner: In the banner.txt file, expressions with ${…} can access configuration properties. This can be used to display certain information directly at startup, such as the title of the application (${application.title}) or even the Spring Boot version (${springboot. version}). In addition, ANSI colors can be set, such as ${Ansi.GREEN}. (For an overview, see http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#bootfeatures-banner.)
Spring Boot automatically detects if the console recognizes colors and sets spring.output. ansi.enabled=DETECT. This means that if the console doesn’t support colors, ANSI control codes for colors won’t be displayed. Possible assignments are ALWAYS, DETECT, and NEVER. This is one of the important differences between logging to files and logging to the console. When outputs go to files, colors are out of place and also not set.
That this is done by spring.main.bannermode= off. An alternative is to control the mode via SpringApplication and the setBannerMode(Banner.Mode) method.
Logging at Start Time
When an application starts, Spring Boot logs additional output very early, for example, about the set profile. These startup outputs can be disabled. If this isn’t desired, you can set the setting via a configuration property, for example, in application.properties:
spring.main.log-startup-info=false
Alternatively, the property can be set via code:
var app = new SpringApplication( Date4uApplication.class );
app.setLogStartupInfo( false );
app.setBannerMode( Banner.Mode.OFF );
app.run( args );
Testing Written Log Message
Logging can be tested, and Spring Boot provides a way to do this. We can test whether an application has written something to the log stream. The following example demonstrates this: the NiceGuy class has a method that writes an info message to the log stream, and the LogTest test class accesses this method:
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
class NiceGuy {
void doWhat?() {
LoggerFactory.getLogger( getClass() ).info( "nice guys finish last" );
}
}
@ExtendWith( OutputCaptureExtension.class )
public class LogTest {
private NiceGuy niceGuy = new NiceGuy();
@Test void testLog( CapturedOutput output ) {
niceGuy.doWhat?();
Assertions.assertThat( output.getOut() ).contains( "finish last" );
}
}
No slice test or annotation @SpringBootTest is needed for this test, just JUnit extension @ExtendWith(OutputCaptureExtension.class). Then, when we instantiate NiceGuy—usually, we would inject the component—we can call the niceGuy.doWhat? method, which should result in log output. This can be tested. A call to output.getOut() will return the log messages, and AssertJ can use contains(…) to provide an answer as to whether "finish last" is included.
Editor’s note: This post has been adapted from a section of the book Spring Boot 3 and Spring Framework 6 by Christian Ullenboom.
Comments