8 minutes read

When you create an application, it's important to keep a record of its inputs, processes, outputs, and results. These records can help developers with the task of debugging and maintaining code. The tool you can use to create them is logging. Logging can be used in various situations, ranging from errors to simple informational outputs to confirm that a process has been completed. In this topic, we will see how we can set up and implement logging in Spring Boot.

Adding logging to a Spring Boot project

For our logging, we are going to use a Logback component. It will allow you to produce configurable log files that can track activities within your application. The Logback component is automatically added via spring-boot-starter since it is the default logging component used by Spring. The Logback component is accessible through an external interface called slf4j.

To ensure that everything works, we can now record our first log! Pay attention to the class Logger , which we should import from the slf4j library:

Java
@Component
public class Runner implements CommandLineRunner {

    private static final Logger LOGGER = LoggerFactory.getLogger(Runner.class);
    @Override
    public void run(String... args) {
       LOGGER.info("Spring Boot application was launched!");
    }
}
Kotlin
@Component
class Runner : CommandLineRunner {
    override fun run(vararg args: String) {
        LOGGER.info("Spring Boot application was launched!")
    }

    companion object {
        private val LOGGER: Logger = LoggerFactory.getLogger(Runner::class.java)
    }
}

The output related to this log message is similar to the one shown below.

12:05:30.102 [main] INFO com.example.app.Runner - Spring Boot application was launched!

We used the info method of the logger, so the text provided is logged at the info level. We can see it at the beginning of the log message. As you may remember, there are five possible levels of logging:

  1. Error: used to track information when an application fails to execute successfully.

  2. Warn: used when unexpected events happen that are not critical to the functionality of the application.

  3. Info: used to log general information about the execution of the program.

  4. Debug: used to help track program execution for fixing bugs or issues.

  5. Trace: used to create more detailed and high-volume sets of information about program execution.

Another interesting feature of the log message is the fact that it shows the name of the logger that printed the log message, in this case, Runner. This is possible because when we created our Logger object, we gave it a reference to the class we are logging for. However, the logger's name may not necessarily be a class. We can give a string as an argument to the getLogger() function and everything will work the same way.

Configuring Logback

As you may already know, Logback can be configured using the file called logback.xml, located in the resources folder of the project. In this configuration file, we can define some settings of our logging. Here is an example of the Logback configuration.

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

In this file, we defined the pattern by which the log messages will be formed, as well as the place where the logs will be recorded (console in our case). Also, we defined the lowest level of logging, which is important for us — the information from DEBUG and TRACE levels of logging will be ignored. Since we are working with Spring Boot, we've got used to defining the settings in application.properties. The logging settings are not an exception: we can also define them there. For example, to make everything work as with the logback.xml configuration above, we add the following lines to application.properties:

logging.level.root=info
logging.pattern.console=%d{HH:mm} %-5level %logger{36} - %msg%n

Also, we can specify the path to the log file in two ways. The first is to specify logging.file.name:

logging.file.name=logfile.log                     //in a project root folder
logging.file.name=relativepath/to/logfile.log     //relative path with filename
logging.file.name=/fullpath/to/logfile.log        //full path with filename

The second is to specify logging.file.path. In this case, the log file will automatically be named spring.log:

logging.file.path=./                         // -> spring.log in a project root folder
logging.file.path=relativepath/to/logs       // -> relativepath/to/logs/spring.log
logging.file.path=/fullpath/to/logs          // -> /fullpath/to/logs/spring.log

If we specify both of these parameters, the value of logging.file.path will be ignored. Let's understand how can we define a path to the log file in the example. Imagine that we want to record our logs in the log folder at the root of our project. We can do this by adding the following line to application.properties:

logging.file.name = log/mylogs

After the application launch, the structure of our project will be similar to the one below:

├── HELP.md
├── log
│   └── mylogs
├── logging.iml
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
   ├── main
│   └── test
└── target
    ├── classes
    └── generated-sources

We can do the same with the help of the logging.file.path property, but the name of the file will be spring.log instead of mylogs:

logging.file.path = log

You may notice that the logs in the file have the default formatting, even though we defined the logging.pattern.console property. That's because there is a separate property for the formatting of the logs in the file:

logging.pattern.file=%d{HH:mm} %-5level %logger{36} - %msg%n

Setting a level of logging

As we have seen earlier, we can set the level of logs that will be recorded for our application. Let's understand how it works in the example. We will log the messages of every level when we're starting our application:

Java
@Component
public class Runner implements CommandLineRunner {
    private static final Logger LOGGER = LoggerFactory.getLogger(Runner.class);
    @Override
    public void run(String... args) {
        LOGGER.error("ERROR!");
        LOGGER.warn("WARN!");
        LOGGER.info("INFO!");
        LOGGER.debug("DEBUG!");
        LOGGER.trace("TRACE!");
    }
}
Kotlin
@Component
class Runner : CommandLineRunner {
    override fun run(vararg args: String) {
        LOGGER.error("ERROR!")
        LOGGER.warn("WARN!")
        LOGGER.info("INFO!")
        LOGGER.debug("DEBUG!")
        LOGGER.trace("TRACE!")
    }

    companion object {
        private val LOGGER: Logger = LoggerFactory.getLogger(Runner::class.java)
    }
}

But when we run our application, we will see only logs from the INFO level and above:

11:57 ERROR c.example.app.Runner - ERROR!
11:57 WARN  c.example.app.Runner - WARN!
11:57 INFO  c.example.app.Runner - INFO!

That's because the default value of the logging.level.root property is info. Let's change it to see all our logs:

logging.level.root=trace

Now we can see all the log messages we wanted. But now we have another problem: we have too many logs because, as it turns out, Spring Framework also uses logging of DEBUG and TRACE levels. Luckily for us, we have a property to set the log level for a certain class or package. To set the TRACE level only for our class, we can add the following line to application.properties:

logging.level.com.example.app.Runner=trace

We can set the logging levels not just for the classes and packages we created. For example, we can limit the logs for the whole Spring Framework package:

logging.level.org.springframework=warn

Conclusion

Logs allow us to better understand and debug code as it runs. In real applications, logs can help us identify potential issues in production by showing you the flow of execution, and where potential errors can occur. In this topic, you've learned how to use logging in Spring Boot applications and how to configure it in application.properties.

59 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo