Apart from having logs displayed directly in the console, we can configure our Spring Boot project to write log entries into files. It not only allows us to easily store and share logs for debugging, but also can be the first step towards browsing them in Kibana.
What we are going to build
Spring Boot provides a ready to use logging mechanism within the spring-boot-starter-logging dependency. First, we will disable it so our logs won’t be handled by Logback. Then, we will include the dependency for Log4j 2 and configure it using the YAML format. We are working on a basic Spring Boot 2.2.6
app with the Web
dependency.
You can find the example project in the spring-boot-log4j-2-scaffolding repository.
Add Log4j 2 dependency to the project
In this part you will find details about switching from the default logging mechanism to Log4j 2.
Disable the default logging
According to the documentation, thanks to the Web
dependency, logs are handled with Logback
by default. The spring-boot-starter-web depends transitively on the logging starter that provides Logback. Therefore, we have to exclude
it manually in our pom.xml
file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!-- pom.xml --> … <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> … |
Add the Log4j 2 Spring Boot starter
Now we can update the pom.xml
file by adding the starter for Log4j2:
1 2 3 4 5 6 7 |
<!-- pom.xml --> … <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> … |
The documentation contains information about handling logging dependencies without the starters:
If you do not use the starters, you need to provide (at least)
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-configure-log4j-for-loggingspring-jcl
in addition to Log4j 2.
Enable YAML configuration
To use an alternative to the XML format, we have to append additional dependencies. For applying a config in the YAML
format, include jackson-databind and jackson-dataformat-yaml in your pom.xml
file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!-- pom.xml --> … <properties> … <jackson-databind.version>2.10.3</jackson-databind.version> <jackson-dataformat-yaml.version>2.10.3</jackson-dataformat-yaml.version> </properties> … <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson-databind.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>${jackson-dataformat-yaml.version}</version> </dependency> … |
You can run the project. The logs will be printed in the console in line with the Log4j 2 Automatic Configuration. The work done in this section is contained in the f57af50f8338787633a88badc127ce0b4fd7864f commit.
Configure Log4j 2 to handle printing logs in the console as well as saving them in a file
The main concepts used in the library are listed in the Architecture section of the Log4j 2 documentation. Spring Boot also gives some insight about custom log configuration. You can find the official YAML configuration example in the documentation.
Let’s assume that we want to print logs to the console and, at the same time, save them in files. The log level for our loggers is the same – INFO.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# src/main/resources/log4j2-spring.yaml Configuration: properties: property: - name: LOG_EXCEPTION_CONVERSION_WORD value: "%xwEx" - name: LOG_LEVEL_PATTERN value: "%5p" - name: LOG_DATEFORMAT_PATTERN value: "yyyy-MM-dd HH:mm:ss.SSS" - name: LOG_DIR value: "logs" - name: CONSOLE_LOG_PATTERN value: "%d{${LOG_DATEFORMAT_PATTERN}} %highlight{${LOG_LEVEL_PATTERN}}{TRACE=blue} %style{%pid}{Magenta} --- [%15.15t] %style{%-40.40c{1.}}{Cyan} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}" - name: FILE_LOG_PATTERN value: "%d{${LOG_DATEFORMAT_PATTERN}} ${LOG_LEVEL_PATTERN} %pid --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}" appenders: Console: name: STDOUT PatternLayout: Pattern: ${CONSOLE_LOG_PATTERN} File: - name: AllFile fileName: ${LOG_DIR}/all.log PatternLayout: Pattern: ${FILE_LOG_PATTERN} - name: KeepgrowingAppFile fileName: ${LOG_DIR}/app.log PatternLayout: Pattern: ${FILE_LOG_PATTERN} Loggers: logger: - name: in.keepgrowing.springbootlog4j2scaffolding level: info AppenderRef: - ref: KeepgrowingAppFile Root: level: info AppenderRef: - ref: AllFile - ref: STDOUT |
Below you will find description for all options from the example file alongside with the links to the official documentation.
properties
To keep the config more readable I used the Property Substitution mechanism. There are many places the values can originate from. To provide an attribute value from the outside of the config file we use the ${prefix:name}
syntax. prefix
points to the context in which the property value should be taken from. If there is no value set in the given context, the one provided within the config file will be used by default. In the ${sys:LOG_EXCEPTION_CONVERSION_WORD}
example the value from the system
context, if present, will be used.
Caveat: Another possible source for property values are environment variables. You can read about differences between an env and a system property in the Java system properties and environment variables question on Stack Overflow.
Appenders
To specify the output for logs I need to configure Appenders. It is extremely useful to get yourself acquainted with the role that appenders play in the Log4j 2 architecture. You can also browse available appenders to find those that are the most suitable for your use case.
Console appender
After adding the custom log4j2-spring.yaml
configuration file I no longer enjoy benefits of having logs printed in my console by default. Therefore, I had to configure the Console appender. I used it as an output for the root
logger. It means that all logs are going to be sent to this appender. I had to give it a unique name so I can later provide it to the associated logger
. I also specified the pattern
for my log entries.
File appender
I want to send all logs that are printed in the console to a file. For that reason, I added a File appender that is associated with the root
logger and will store log entries in the all.log
file. For the sake of the simplicity, the log/
directory is kept directly in the project’s root folder.
PatternLayout
We can configure our appenders to send formatted output to their destinations by adding the PatternLayout. The specified layout will be responsible for the arrangement of log entries. You can configure the format to keep the default Spring Boot pattern for a console and a file output.
We are going to build our layouts using available conversion patterns according to the following structure: the %
sign followed by a format modifier
with a conversion character
at the end. The formatting used in the example is based on the log4j2.xml file from the spring-boot project.
- Our example console log pattern:
1 |
%d{${LOG_DATEFORMAT_PATTERN}} %highlight{${LOG_LEVEL_PATTERN}}{TRACE=blue} %style{%pid}{Magenta} --- [%15.15t] %style{%-40.40c{1.}}{Cyan} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} |
- The file log pattern:
1 |
%d{${LOG_DATEFORMAT_PATTERN}} ${LOG_LEVEL_PATTERN} %pid --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} |
Conversion characters
Let’s start with the elements that determine the type of data. Below you can see the documentation for characters used in the example config file:
d
– the date of the logging event;p
– the level of the logging event;pid
– the process ID;t
– the name of the thread that generated the logging event;c{1.}
– the name of the logger that published the logging event (adding the{1.}
precision specifier in our example will cause a logger name to be abbreviated fromorg.apache.commons.Foo
too.a.c.Foo
);m
– the message associated with the logging event;n
– the platform dependent line separator;xwEx
– the throwable conversion word. The ExtendedWhitespaceThrowablePatternConverter adds additional whitespace around the stack trace (Meaning of xwEx for log4j2? question on Stack Overflow).
Below you will find the code that generates an example exception (logged with Lombok):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package in.keepgrowing.springbootlog4j2scaffolding; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @Slf4j public class SpringBootLog4j2ScaffoldingApplication { public static void main(String[] args) throws IllegalArgumentException { SpringApplication.run(SpringBootLog4j2ScaffoldingApplication.class, args); try { throw new IllegalArgumentException("Exception message"); } catch (IllegalArgumentException e) { log.error("Log message", e); } } } |
On the following screenshot you can see the console output for that code:
As you can see the exception is visually separated from the rest of the log entries (the %xwEx
pattern).
Format modifiers
An optional format modifier is placed between the %
sign and a conversion character
. The format modifiers control such things as field width, padding, left and right justification. The details are enclosed in the documentation. For example:
-40.40c
– this pattern starts with the left justification flag (-
), then we specified the minimum field width (40, filled with whitespace if shorter) and, separated by the period (.
), the maximum width (40) for a logger name.15.15t
– a thread name is right-aligned, the minimum (filled with whitespace if shorter) and maximum width for this field is 15 characters.
Highlight and style
- I overridden the colour for the TRACE level with the following code:
1 2 |
# src/main/resources/log4j2-spring.yaml …%highlight{${LOG_LEVEL_PATTERN}}{TRACE=blue}… |
- The colour of the process ID is set to magenta:
1 2 |
# src/main/resources/log4j2-spring.yaml …%style{%pid}{Magenta}… |
- The logger name is printed in cyan:
1 2 |
# src/main/resources/log4j2-spring.yaml …%style{%-40.40c{1.}}{Cyan}… |
Therefore, my console output looks like on the image below:
If you want to change colours of log levels, read about the highlight
conversion in the docs:
File pattern
The entries collected in files look like on the image below:
fileName
The file that the logs will be saved to. If the file or its parent directories don’t exist, they will be automatically created.
Loggers
We determined the destination of logs in the Appenders
section. Now it’s time to actually capture logs and send them to the right appender. We are required to name all loggers except the root
one. If we don’t indicate a level attribute it will default to ERROR. Because I didn’t set the additivity
attribute it will default to false
and log entries from all loggers will be printed in the appenders associated with the root
logger.
AppenderRef
You have to provide targets for entries gathered by each logger. Use the names specified in the appenders
section. For the root
logger I send entries to the all.log
file and the console with the following lines:
1 2 3 4 5 6 7 8 9 10 |
# src/main/resources/log4j2-spring.yaml Configuration: … Loggers: … Root: … AppenderRef: - ref: AllFile - ref: STDOUT |
Additivity
We can send entries extracted by one logger to multiple appenders. By default, the logger that is higher in the hierarchy will print in its appenders entries collected by all “children” loggers.
In our example all appenders associated with the root
logger would contain entries from the in.keepgrowing.springbootlog4j2scaffolding
logger. Let’s change the level to DEBUG for this particular logger, and see, how during the application startup we have the following entries printed in the console, the all.log
and the app.log
files:
If we want to send logs from one logger explicitly to its own appenders we need to set the additivity flag to false:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# src/main/resources/log4j2-spring.yaml Configuration: … appenders: … File: … - name: AllFile fileName: ${LOG_DIR}/all.log PatternLayout: Pattern: ${FILE_LOG_PATTERN} Loggers: logger: - name: in.keepgrowing.springbootlog4j2scaffolding level: debug additivity: false AppenderRef: - ref: AllFile Root: … |
With those setting we can see that the entries form this logger are missing in the console and other appenders associated with the parent logger – the root
logger:
This logger sends entries solely to the app.log
file:
Root logger
The root
logger is required in every configuration. It doesn’t need a name. By default it will be assigned the ERROR level and the console as its appender. This logger doesn’t have a parent so we don’t specify the additivity
attribute for it. In the example project it collects all entries that are at least at the INFO level and sends them to the console and the all.log
file.
Other loggers
You can add other, more granular loggers. Name them after the package from which they are supposed to draw entries and provide one or more destination (appender) for those logs. In the example we have the in.keepgrowing.springbootlog4j2scaffolding
logger. Remember to adjust the configuration of your loggers so the logs are send to appenders according to your preferences.
The work done in this section is contained in the 75c3667a79a7342a82f2d0df5f6e286e5a5b299f commit.
Deeper understanding
The example provided in this post represents a very simplistic approach and can be only used as a starting point for configuring Log4j 2 in a project. Please follow this advice from the documentation when working with this library:
An understanding of how loggers work in Log4j is critical before trying to configure them. Please reference the Log4j architecture if more information is required. Trying to configure Log4j without understanding those concepts will lead to frustration.
https://logging.apache.org/log4j/2.x/manual/configuration.html