In the previous chapter, we discussed the ideas, features, and implementation of Cross-Origin Resource Sharing (CORS) in securing our application. We also learned about JSON Web Tokens (JWTs) and how to generate one by creating authentication endpoints.
This chapter will focus on logging events in our Spring Boot application. We will discuss the popular packages for logging in to and configuring Spring Boot, where to save them, and what to do with logs as we develop our application.
In this chapter, we will cover the following topics:
The finished version of this chapter’s code may be seen at the following link: https://github.com/PacktPublishing/Spring-Boot-and-Angular/tree/main/Chapter-08/superheroes.
Logging is considered one of the most important aspects of developing an application. Its importance tends to be underrated and, worse, we forget to apply it to our applications.
Event logging is used in most tech industries, especially those providing enterprise applications. It is applied with a given standard to prevent complex debugging and allow for an easier understanding of the code we are reading. A well-written and structured log can benefit developers, especially when maintaining or debugging code from another developer. Instead of searching exhaustively for an error, records will expedite the debugging process, providing information on where and why the error occurred, and what has happened in our application.
Logging has also evolved with improvements in languages and frameworks; in backend development, several logging frameworks have been developed to provide more flexible logging capabilities. Some of the example frameworks that we will discuss are SLF4J and Log4j2 for Java Spring Boot. Before proceeding, let’s discuss the features of a logging framework.
A logging framework contains the following three features for us to display and capture events in our application:
A logging framework also displays messages with different severity levels, allowing the developer to quickly identify which event has occurred. The severity levels in a logging framework are as follows:
One of the popular logging frameworks being used with Java is Simple Logging Façade for Java, or SLF4J. It is one of the most widely used frameworks since it allows users to use any logging frameworks, such as Log4j, Logback, the java.util.logging package, or Java’s own logging engine, JUL, using only a single dependency. This means that we can switch from one logging framework to another depending on what is needed.
There are several advantages to using SLF4J:
SLF4J provides several classes and methods for displaying messages with severity levels, profiling the time of execution, or simply returning the instance of the logger. Let’s discuss the provided methods and classes.
The logger interface is mainly used to display the messages or logs provided with the severity level. This is also the entry point of the SLF4J API.
The LoggerFactory class is the SLF4J utility class, commonly used to create loggers using frameworks such as JUL and Log4j.
Logger getLogger(String name) generates the logger object with a specified name. The following example uses the getLogger() method:
private static final org.SLF4J.Logger log = org.SLF4J.LoggerFactory.getLogger(LogClass.class);
The Profiler class is mainly used to identify the execution time of a specific task in our application, also known as the poor man’s profiler.
Various methods may be used:
SLF4J has several features that make logs more helpful in debugging. It provides support for parameterized logging, which allows us to display dynamic values in our messages. Another feature is profiling, which is commonly used to measure different attributes such as the memory and execution time of specific tasks in an application.
Let’s discuss the concept and implementation of each feature.
To use parameterized logging in SLF4J, we will include placeholders {} in the message where we want to pass the value.
Let’s have a look at the following example:
public class LoggerExample { public static void main(String[] args) { //Creating the Logger object Logger logger = LoggerFactory.getLogger(LoggerExample.class); String name = "seiji"; //Logger using info level logger.info("Hello {}, here is your log", name); }
In the preceding example, we have created a parameter in our message to display the value of the name variable. Once we execute the application, the output will be as follows:
INFO: Hello seiji, here is your log
Parameterized logging also supports multiple parameters in messages, as in the following example:
public class LoggerExample { public static void main(String[] args) { //Creating the Logger object Logger logger = LoggerFactory.getLogger(LoggerExample.class); Integer x = 3; Integer y = 5; //Logging the information logger.info("The two numbers are {} and {}", x, y); logger.info("The sum of the two number is" + (x + y)); }
In the preceding example, we can display the x and y variables in a single log. We can also execute operations directly in our messages. The output would be as follows:
INFO: The two numbers are 3 and 5 INFO: The sum of the two numbers is 8
SLF4J also provides profiling, which is used to measure the memory, usage, and execution time of specific tasks in an application. The profiling feature can be used by a class named Profiler.
To implement a profiler in our code, we have to execute the following steps:
Profiler profiler = new Profiler("ExampleProfiler");
profiler.start("Example1");
class.methodExample();
TimeInstrument tm = profiler.stop();
Now we have learned the concepts, features, and advantages of SLF4J, we will discuss a framework called Log4j2.
Log4j2 is one of the most common logging frameworks used with Java. Since SLF4J is an abstraction of logging frameworks, Log4j2 can be used with SLF4J. Log4j2 is very flexible and offers different ways to store log information for debugging; it also supports asynchronous logging and displays logs with a severity level to quickly identify the importance of messages.
Let’s discuss the following features of Log4j2:
The Logger is the main feature used by our application to create LogRecord instances. This means the logger is responsible for dispatching the messages. To create a Log4j2 Logger, we only need the following code:
Logger log = LogManager.getLogger(ExampleClass.class);
After creating a new Logger, we can now use it to call several methods, such as info(), to dispatch messages.
Appenders are responsible for placing the logs dispatched by the Logger. In Log4j2, there are a wide range of Appenders that help us decide where to store our logs.
Here are some of the Appenders that are available from Log4j2:
You can visit the Log4j2 documentation for other available Appenders at the following link: https://logging.apache.org/log4j/2.x/manual/appenders.html.
Appenders use Layouts to format the output of a LogEvent. Log4j2 has different Layouts we can choose from to format our logs:
14:25:30 Example log message
Markers are objects commonly used to mark a single log statement to identify whether we need to execute certain actions to specific logs. For example, we can mark a single log statement using the IMPORTANT Marker, which can indicate to the Appender that it needs to store the log in a different destination.
Let’s have a look at an example of how to create and use Markers:
public class Log4j2Marker { private static Logger LOGGER = LoggerFactory.getLogger(Log4j2Marker.class); private static final Marker IMPORTANT = MarkerFactory.getMarker("IMPORTANT"); public static void main(String[] args) { LOGGER.info("Message without a marker"); LOGGER.info(IMPORTANT,"Message with marker" } }
In the preceding example, we can create a new Marker using the MarkerFactory.getLogger() method. To use the new Marker, we can apply it to a specific logger that indicates a particular action needed for significant events.
Log4j2 Filters are another valuable feature for use in displaying loggers. This gives us the capability to control log events that we want to say or publish based on the given criteria. In executing a Filter, we can set it with the ACCEPT, DENY, or NEUTRAL values. Here are some of the Filters we can use to display loggers:
In the following section, we will configure the logging frameworks in our project.
We will now implement several logging frameworks, including Logback and Log4j2, in our Spring Boot application. Remember that SLF4J is already included.
Logback is the default logger used by Spring Applications, so no dependencies need to be installed to use it. The spring-boot-starter-logging dependency is already included once we create our Spring Boot application. The first step we need to take is to make our Logback configuration file.
In our project, under the resources folder, we will add the logback-spring.xml file. This is where we will place our Logback configuration. The following is an example configuration:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="LOGS" value="./logs" /> <!—Please refer to the logback-spring.xml of the GitHub repo. Thank you. --> <!-- LOG everything at INFO level --> <root level="info"> <appender-ref ref="RollingFile" /> <appender-ref ref="Console" /> </root> <logger name="com.example" level="trace" additivity="false"> <appender-ref ref="RollingFile" /> <appender-ref ref="Console" /> </logger> </configuration>
In the preceding XML, several configurations were defined to format our log events. We have created two Appenders – Console and RollingFile. Configuring the two appender tags will create logs in System.out and File Output.
We have also used a pattern that modifies the look and format of the log display. In this example, we have used the %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable pattern to display the logs in System.Out. It shows the date in black, the severity level in highlight, the thread name in blue, the class name in yellow, and the message assigned to the logs.
After successfully configuring Logback, we can run the application and see the logs in our console:
Figure 8.1 – Log events using Logback
We will now use the Log4j2 framework for our logs.
We can also use a different framework for logging events in our application. In this example, we will use Log4j2 to handle our logs:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-Log4j2</artifactId>
</dependency>
To do so, we must add the following XML code to the dependencies under the org.springframework.boot group:
<exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId> spring-boot-starter-logging</artifactId> </exclusion> </exclusions>
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<!—Please refer to the log4j2-spring.xml of the
GitHub repo. -->
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
<AppenderRef ref="RollingFile" />
</Root>
<Logger name="com.example"
level="trace"></Logger>
</Loggers>
</Configuration>
The preceding configuration is almost the same as the one we have implemented using Logback. We have also created two Appenders – Console and RollingFile; the only significant difference is the pattern for the log events. We have now successfully configured Log4j2. When we run our application, we will see the following log output:
Figure 8.2 – Log events using Log4j2
Having configured and modified the configuration of our logs using the Log4j2 framework, we will now use it to add logs to our code.
We can now use the log frameworks we have configured in our Spring Boot application to define logs on the different parts of our code. To do so, we must first create a new logger instance.
An example would be creating a log when a user attempts to get a list of all anti-heroes. In AntiHeroeController, we will add the following code to create a new logger instance:
private static final Logger LOGGER = LoggerFactory.getLogger(AntiHeroController.class);
We must also be aware that LoggerFactory and Logger should be under the SLF4J dependency. It is always recommended to use SLF4J as this is an abstraction of logging frameworks and makes it easier to switch between them.
In this case, our import should be as follows:
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
Once we have created a new logger instance, we can now use it in our methods, for example, if we want to display a log when the user attempts to get a list of anti-heroes.
To accomplish this, under the getAntiHeroes() method, we will add the following code:
public List<AntiHeroDto> getAntiHeroes(Pageable pageable) { int toSkip = pageable.getPageSize() * pageable.getPageNumber(); //SLF4J LOGGER.info("Using SLF4J: Getting anti hero list - getAntiHeroes()"); // Mapstruct is another dto mapper, but it's not // straightforward var antiHeroList = StreamSupport .stream( service.findAllAntiHeroes().spliterator(), false) .skip(toSkip).limit(pageable.getPageSize()) .collect(Collectors.toList()); return antiHeroList .stream() .map(this::convertToDto) .collect(Collectors.toList()); }
In the preceding example, we have invoked info(String message). Every time the user calls the get anti-heroes endpoint, the log will be displayed. We can also invoke the following methods:
Now let’s see how Lombok, a library in our Spring Boot application, can help us. Lombok can simplify our code by using annotations, but it also offers annotations for SLF4J and Log4j2 as follows:
public class LogExample {
private static final org.SLF4J.Logger log =
org.SLF4J.LoggerFactory.getLogger(
LogExample.class);
}
public class LogExample {
private static final org.SLF4J.Logger log =
org.SLF4J.LoggerFactory.getLogger(
LogExample.class);
}
Once we have used the annotations in our class, we don’t need to create a new instance and we can use the log directly in our methods:
//LOMBOK SLF4J log.info("Using SLF4J Lombok: Getting anti-hero list - getAntiHeroes()");
This chapter has explained the concept and importance of loggers and how they can help developers in debugging and maintaining applications. It has introduced Log4j2, a third-party framework for Spring Boot that offers several features, such as Appenders, Filters, and Markers, which can assist in categorizing and formatting log events for developers. It has also introduced the concept of SLF4J, which is an abstraction of logging frameworks that allows us to switch between different frameworks at runtime or deployment.
In the following chapter, we will learn about the concepts and integration of unit testing in our Spring Boot application.