Integrating log4j2 with spring boot 3 with global Async Logger

Prashantprakash
3 min readDec 31, 2022

--

When you are working on a project at production grade, it becomes a necessity to add proper logger with configuration that might help you with faster debugging. I usually prefer log4j because it is easy to configure and has better performance over other common options. In this blog, I will walk you through a basic configuration to setup logging in your spring boot project.

Prerequisite

I am assuming that you have the following already setup:

  1. Java 17(You can get it from eclipse website)
  2. An IDE of your choice
  3. A spring boot 3 project(give spring initializr a look rather looking for a plugin in your IDE)

Adding Dependencies

First dependency we are gonna add is lombok. It is not necessary for logging, but it makes adding logger instances to classes a lot easier.

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

Spring boot provides a dependency to add all required dependencies for log4j2.

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

log4j2 uses the disruptor to implement async operations for logging. If you happen to get errors like ClassDefNotFoundException, it might be due to missing disruptor library.

<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${async.logger.lmax.disruptor.version}</version>
</dependency>

Since we added lombok, which already has slf4j dependencies, you will effectively have multiple logging implementations, log4j2 and commons-logging or logback. You need to exclude them form respective dependencies. like, spring-boot-starter-web brings in logback and spring-boot-starter-logging dependencies.

<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>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>

log4j2 configurations for loggers and appenders

To add the configuration, create a file log4j2.xml in src/main/resources folder. Here is a sample configuration file, edit it as per your needs. Don’t add async logger here, we will get to that part later.

<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<Properties>
<Property name="logpath-location">app/logs</Property>
<Property name="logfile-name">db_service.log</Property>
<Property name="archive">${logpath-location}/archive/dbservice</Property>
<Property name="interval">10</Property>
</Properties>

<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %C.%M():%L %X - %m%n"/>
</Console>

<RollingFile name="RollingFileAppender" fileName="${logpath-location}/${logfile-name}"
filePattern="${archive}/${logfile-name}.%d{yyyy-MM-dd-HH}.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %c.%M ():%L %X - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</Appenders>

<Loggers>
<Logger name="com.projects.spring_web_service" level="DEBUG" additivity="false" includeLocation="true">
<AppenderRef ref="Console" level="INFO"/>
<AppenderRef ref="RollingFileAppender" level="DEBUG"/>
</Logger>

<Root level="INFO" includeLocation="true">
<AppenderRef ref="Console" level="INFO"/>
<AppenderRef ref="RollingFileAppender" level="DEBUG"/>
</Root>
</Loggers>

</Configuration>

One thing to notice here is the attribute includeLocation for Logger tag. When we configure a global async logging context, the location is not passed by default. If you have a log pattern with location, it might print empty in class and method placeholders.

As per the log4j2 documentation:

If one of the layouts is configured with a location-related attribute like HTML locationInfo, or one of the patterns %C or $class, %F or %file, %l or %location, %L or %line, %M or %method, Log4j will take a snapshot of the stack, and walk the stack trace to find the location information.

Using locations in sync loggers are 1.3–1.5 times expensive in terms of performance. In async loggers, passing locations makes the logging 30–100 times slower, which is the reason it is not enabled by default.

Enable Global Asynchronous Logging

To enable asynchronous logging globally(all loggers will be async), you can either add a VM option to run configuration or add a property file to store log4j2 system properties. Although there are multiple options to even which property file you use, I personally find this one as the easiest.
Create a file log4j2.component.properties in src/main/resources and add the following property:

log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

At this point, you have setup everything for logging, all you need is a class. Create a class, annotate it with @Log4j2 annotation of lombok, and you are good to go.

If you need more details, I would suggest you to go through the documentation, it is very comprehensive and detailed.

Reference:

https://logging.apache.org/log4j/2.x/manual/async.html

--

--