How to use Log4j2 in your WildFly applications

Log4j2 is the latest major release of the popular Logging Framework. In this tutorial we will learn how to include Log4j2 configuration file and use it in your deployments running on WildFly.

Log4j2 borrows some concepts from the Logback framework, which can be assimilated to an improved version of the older Log4j release. According to the official document of Apache Log4j 2 the major highlights of this release are:

  • The Log4j 2 API is a logging facade that may be used with the Log4j 2 implementation, but may also be used in front of other logging implementations such as Logback.
  • Improved performance: In multi-threaded scenarios the Asynchronous Loggers have a fairly higher throughput than Log4j and Logback.
  • Support for multiple APIs: Log4j 2 provides support for Log4j 1.2, SLF4J, Commons Logging and java.util.logging (JUL) APIs.
  • Automatic reloading of configurations: Log4j 2 can automatically reload its configuration upon modification.
  • Advanced filtering: Log4j 2 supports filtering based on context data, markers, regular expressions, and other components in the Log event.
  • Java 8 Lambda support
  • Custom log levels:Log4J 2 supports custom log levels. Custom log levels can be defined in code or in configuration.
  • Low Garbage collector impact: Garbage-free logging in Log4j is partially implemented by reusing objects in ThreadLocal fields, and partially by reusing buffers when converting text to bytes.

That being said, let’s see how to implement Log4j2 in WildFly application server.

Within WildFly modules, there is a facade from log4j to JBoss Log Manager:

$ tree log4j
log4j/
└── logmanager
    └── main
        ├── log4j-jboss-logmanager-1.2.0.Final.jar
        └── module.xml

In order to delegate logging to log4j2 there are some steps to be completed. First, we must configure the “per-deployment logging” strategy.  As a matter of fact, per-deployment-logging will search the WildFly ClassLoader for one of the logging provider implementations using a predefined order:

  • WildFLy LogManager
  • Log4j
  • Slf4j
  • JDK logging

If both WildFly LogManager and Log4j2 are found in the classpath then WildFly LogManager will be used because it has the “higher precedence”. We can set to false the use-deployment-logging-config as follows, from the CLI:

/subsystem=logging:write-attribute(name=use-deployment-logging-config,value=false)

Next, let’s create a sample log4j2.xml file which includes a Rolling Appender with both the time and size based triggering policies.

<Configuration status="warn" name="MyApp" packages="">
      <Appenders>
        <RollingFile name="RollingFile" fileName="${env:JBOSS_HOME}/standalone/log/application.log"
                     filePattern="${env:JBOSS_HOME}/standalone/log/${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
          <PatternLayout>
            <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
          </PatternLayout>
          <Policies>
            <TimeBasedTriggeringPolicy />
            <SizeBasedTriggeringPolicy size="100 MB"/>
          </Policies>
        </RollingFile>
      </Appenders>
      <Loggers>
        <Root level="info">
          <AppenderRef ref="RollingFile"/>
        </Root>
      </Loggers>
 </Configuration> 

The above log4j2.xml file will create up to 7 archives on the same day (1-7) to be stored in $JBOSS_HOME directory based on the current year and month, and will compress each archive using gzip.

Let’s copy the log4j2.xml file into the $JBOSS_HOME/standalone/configuration:

cp log4j2.xml $JBOSS_HOME/standalone/configuration

Now we will instruct WildFly to pickup log4j configuration file in its “standalone/configuration” folder:

 /system-property=log4j.configurationFile:add(value=${env.JBOSS_HOME}/standalone/configuration/log4j2.xml)

We are done with WildFly configuration. Now let’s create a sample demo application which uses log4j2 from WildFly. The source code for this application is available at: https://github.com/fmarchioni/mastertheboss/tree/master/log/log4j2

As you can see, in the resources folder we have included a jboss-deployment-structure.xml which excludes the default org.apache.log4j and org.apache.commons.logging modules available in WildFly:

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2">
    <deployment>
        <exclusions>
            <module name="org.apache.log4j" />
            <module name="org.apache.commons.logging"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>
Please bear in mind that, for EAR deployments, you need to exclude the above packages from your deployment and all subdeployments. That means to specify the exclusion in the jboss-deployment-structure.xml at the EAR level, and then inside each WAR/JAR nested deployment.

Within our application, the following Servlet will use Log4j2 to output some logs:

@WebServlet(name = "hello", urlPatterns = { "/hello" })
public class HelloWorldServlet extends HttpServlet {


  Logger logger = LogManager.getLogger(HelloWorldServlet.class);

  public HelloWorldServlet() {
    super();

  }
  
  protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

    response.setContentType("text/html");
    PrintWriter writer = response.getWriter();
    writer.println("<h1>Hello World Servlet on WildFly</h1>");
    logger.info("Hello world Log4j2 on WildFly");
    logger.info("Request URI {} - Session Id {}.", request.getRequestURI(), request.getSession().getId());

    writer.close();
    }
    
  protected void doPost(HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException {
    doGet(request, response);
  }

}

Please notice we are using a sample of parameterized logging. This mechanism, introduced by SLF4J, uses ‘{}’ to indicate where placeholders occur and which are the String values uses to replace the parameters.

To be able to build and deploy the application, you will need to add the log4j api and core in your pom.xml file:

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.13.3</version>
    </dependency>

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.13.3</version>
    </dependency>

If using Gradle, the equivalent configuration is:

compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'

That’s it. Now deploy the application and invoke the example Servlet. You should see that the “application.log” has been created in the $JBOSS_HOME/standalone/log with some content in it:

2020-08-16 12:52:12,093 INFO c.m.s.HelloWorldServlet [default task-1] Hello world Log4j2 on WildFly
2020-08-16 12:52:12,100 INFO c.m.s.HelloWorldServlet [default task-1] Request URI /log4jdemo/hello - Session Id Xr1roaugFzX-mVE0rrDCZWqN09XiVqJRrT8Ca6r_.

Keeping your Log4j libs in sync

If you are using multiple libraries under the log4j2 umbrella, it is recommended to include a Bill of Material to keep them in synch:

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-bom</artifactId>
			<version>2.13.3</version>
			<scope>import</scope>
			<type>pom</type>
		</dependency>
	</dependencies>
</dependencyManagement>

With that in place, it is not required to specify the version of the single Log4j artifacts:

<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
</dependency>

Using an Async Log4j Logger

There are several Loggers available with Log4j. Here we will just show an example of a basic Async appender. Check the documentation at: https://logging.apache.org/log4j/2.0/manual/appenders.html#AsyncAppender to learn how to customize this appender:

<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <File name="MyFile" fileName="${env:JBOSS_HOME}/standalone/log/async_application.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
    <Async name="Async">
      <AppenderRef ref="MyFile"/>
    </Async>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Async"/>
    </Root>
  </Loggers>
</Configuration>

References: https://logging.apache.org/log4j/2.0/index.html