Logging in spring boot

In this article, we will understand how spring boot logs messages, what framework it used behind the scenes, how to configure log levels, log message pattern, how to switch to a different logging framework and much more with examples.

Default Spring boot logging
Starting Spring boot 2, when you add a spring starter dependency such as

// gradle
org.springframework.boot:spring-boot-starter-web

// maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

it automatically pulls another starter dependency used for logging, which is

// gradle
implementation 'org.springframework.boot:spring-boot-starter-logging'


// maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

By default, spring uses Logback framework for logging.

This is what spring docs state,

By default, if you use the “Starters”, Logback is used for logging.

So, when you create and start a spring boot application, you can see below logs on the console

[2023-07-02 13:06:49.614] INFO
[background-preinit] – Version(21):HV000001: Hibernate Validator 6.2.0.Final
2023-07-02 21:48:22.974 INFO 3672 — [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 16.0.1 on RENLTP2N159 with PID 3672 (D:\workspace\demo\target\classes started by codippa in D:\workspace\demo)
2023-07-02 21:48:22.975 INFO 3672 — [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2023-07-02 21:48:24.112 INFO 3672 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)

These logs are printed by Logback.

To verify, look at the classpath of the spring boot application and you will find below jars present

logback-classic-<version>.jar
logback-core-<version>.jar

Default format of log messages is

  • Date and Time till millisecond.
  • Log Level
    Possible values are ERRORWARNINFODEBUG, or TRACE.
  • Process ID or PID.
  • --- separator.
  • Thread name within square brackets.
  • Class name with package also referred as logger name.
  • Log message.

Adding Log messages
With default spring boot application, this is how logs are printed

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {
  private static final Logger LOGGER = LoggerFactory.
                                       getLogger(UserService.class);
 
  public void getUsers() {
    // some code
         
    LOGGER.info("Sample info log message");

    // more code    
  }
}

which prints log in below format

21:48:22.436 [main] INFO com.example.demo.UserService – Sample info log message

Default log level is set to INFO.
This means that DEBUG and TRACE messages will not be visible as per the below hierarchy

ERROR – WARN – INFO – DEBUG – TRACE

Note that we are using slf4j classes to create logger object.

slf4j is an interface. Both Logback and Log4J2 implement it.
This means that we can switch the logging framework anytime without making any modifications to our code.

slf4J uses Logback behind the scenes by default in a spring boot application.

Configuring log4j
As stated above, spring boot uses Logback framework for logging by default.
But, if you do not want to use Logback but some other framework such as Log4j2, it is possible with a very simple configuration.

By default, spring boot pulls Logback dependencies and adds them to classpath. To use some other framework, we need to exclude Logback and add the dependency of our preferred framework.

So, if you are using gradle, you can exclude Logback with the below configuration

configurations.all {
   exclude group: 'org.springframework.boot', 
   module: 'spring-boot-starter-logging'
}

and add Log4j2 dependency as

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'org.springframework.boot:spring-boot-starter-log4j2'
}

With maven, Logback dependency can be excluded as

<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>

Use below tag to add Log4j2 dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Using log4j2
As stated earlier, spring boot by default uses Logback for logging but through slf4j. That is, the classes that are imported into our code belong to slf4j.

With above configuration, we can switch Logback with Log4j2 and if we are using slf4j classes for logging, there is no change that we need to make.

So, below code works absolutely fine and uses Log4j2 for logging

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

public class UserService { 
  private static final Logger LOGGER = LoggerFactory.
                                       getLogger(UserService.class); 

  public void getUsers() { 
    // some code 
    LOGGER.info("Sample info log message"); 
    // more code 
  } 
}

If you look closely, it is the same as used before. That is the magic !!.

Without any code change, we can switch logging framework.
Writing logs to file
By default, spring boot logs to console. If you want to write logs to console, set below properties in application.properties file

  • logging.file property with complete path and file name
  • logging.path property with path. File name is spring.log in this case.

When the log file size reaches 10MbB, a new file is created and existing logs are moved to a backup file.
Setting Log levels
Default log level is INFO. If you want to change log level, then use below properties in application.properties file

logging.level.root=debug
logging.level.org.springframework.web=trace
logging.level.com.codippa=error

where root sets the log level for entire application. Other properties set package specific logging levels.

Customizing logging configuration
Default logging is suitable for most applications. However, if you want to customize the logs such as console log format, log file name format etc., you can provide such configuration in framework specific files as listed below

Logback Log4j2 Java Util
logback-spring.xml or
logback-spring.groovy or
logback.xml or
logback.groovy
log4j2-spring.xml or
log4j2.xml
logging.properties

Below is a sample configuration file for Logback.

Its name should be any of those given in above table and should be placed in src/main/resources folder, so that spring can locate it easily.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <property name="LOG_PATH" value="D:\temp"/>
  <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>
        %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
      </Pattern>
    </layout>
  </appender>

  <appender name="File" class="ch.qos.logback.core.FileAppender">
    <File>{LOG_PATH}/app-log.log</File>
      <encoder>
       <pattern>%d{yyyy-MM-dd HH:mm:ss} - %logger{36}-%msg%n</pattern>
      </encoder>
      <rollingPolicy 
        class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>
          ${LOG_PATH}/app-log-%d{yyyy-MM-dd}.%i.log
        </fileNamePattern>
        <timeBasedFileNamingAndTriggeringPolicy
           class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
           <maxFileSize>15MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
      </rollingPolicy>
    </appender> 
  <root level="info">
    <appender-ref ref="Console" />
    <appender-ref ref="File" />
  </root>

  <!-- Log trace level logs on console -->
  <logger name="com.codippa" level="trace" additivity="false">
    <appender-ref ref="Console" />
  </logger>

  <!-- Log debug level logs in file -->
  <logger name="com.codippa" level="debug" additivity="false"> 
    <appender-ref ref="File" /> 
  </logger>
</configuration>

This configuration declares two appenders.

  • First is a console appender which prints logs at the console. This appender specifies the format of the logs.
  • Second is a file appender used for directing logs to a file.
    This appender specifies details such as location of file, pattern of logs, max size of log file after which it should create a new file and the name of file to store archived logs.

Finally, it attaches these appender to a logger with a name.

Below is a similar configuration for Log4j2.
File name should be either log4j2.xml or log4j2-spring.xml and placed in src/main/resources folder.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Properties>
    <Property name="LOG_PATTERN">[%d{yyyy-MM-dd HH:mm:ss.SSS}]%5p
      [%t] - %c{1}(%L):%m%n</Property>
    <Property name="REQ_LOG_PATTERN">
             [%d{yyyy-MM-dd HH:mm:ss.SSS}]%5p - %m %spi%n
    </Property>  
  </Properties>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT" follow="true">
      <PatternLayout pattern="${LOG_PATTERN}" />
    </Console>
    <RollingFile name="File" fileName="app-log.log"
      immediateFlush="true" ignoreExceptions="true"
      filePattern="logs/app-log-%d{yyyy-MM-dd-HH}-%i.log.gz">
      <PatternLayout>
        <Pattern>${LOG_PATTERN}</Pattern>
      </PatternLayout>
      <Policies>
        <SizeBasedTriggeringPolicy size="10 MB" />
      </Policies>
      <DefaultRolloverStrategy max="10" />
    </RollingFile>
  </Appenders>
  <Loggers>
    <logger name="com.rhsm" level="debug" additivity="false">
      <AppenderRef ref="File" />
      <AppenderRef ref="Console" />
    </logger>

    <Root level="info">
      <AppenderRef ref="Console" />
    </Root>
  </Loggers>
</Configuration>

It is similar to Logback configuration.

For getting in depth explanation of these tags and appenders, refer Logback and Log4j2 documentation, since it is outside the scope of this article.
Log4j without slf4j
You can use Log4j2 along with slf4j classes as mentioned in the last section. But, you can also use Log4j2 without slf4j by using Log4j2 specific classes for printing logs as shown below

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class UserService { 
  private static final Logger LOGGER = LogManager.
                                       getLogger(UserService.class); 
 
  public void getUsers() { 
    // some code 
    LOGGER.info("Sample info log message"); 
    // more code 
  } 
}

This uses org.apache.logging.log4j.LogManager and org.apache.logging.log4j.Logger instead of org.slf4j.LoggerFactory and org.slf4j.Logger.

But why would you want to do that when there is no difference.
The answer is that if there are some features in Log4j2 which are not available through slf4j, then we can’t use them.
But with direct Log4j2 classes, we can leverage all its features.

Problem with this approach is that if we need to switch our logging framework, then all the classes should be updated to modify the imports.