Apache TomEE is a great EE container and provides a thin logging layer allowing you to switch between Java Util Logging or JUL (the default solution), SLF4J, Log4j2 or even Log4J1 if desired. This bridge has however few drawbacks even if working most of the time:

  • it is active a little bit after Tomcat started - when OpenEJB starts actually - which means first logs don't benefit from it
  • it is only usable for libraries supporting to not be in the classpath (common.loader of Tomcat)

So this solution which consist of customizing the system property openejb.log.factory is easy but doesn't give you the full control of the container logging.

How to go further?

Since TomEE default setup is done for JUL why not trying to bridge JUL to the framework you want? In the context of this post the shining Log4j2.

This is actually a good idea but often it ends up configuring some specific handlers doing the bridge and after some efforts....it loops. Well in short such configurations are quickly a mess to maintain and share.

Log4j2 propose an alternative which is really more interesting and powerful in my opinion: a custom LogManager.

If you are not familiar with JUL, the JVM gets a LogManager which will be the "factory" of loggers. This looks like an awesome extension point but the drawback - and why it is often done through handlers - is it is global on the JVM and needs to be in the classpath.

But this is not a big deal if you own TomEE instance.

What are the needed steps?

  • add log4j-api, log4j-jul and log4j-core jar in your classpath
  • add a log4j2.xml in conf/
  • set the java.util.logging.manager to org.apache.logging.log4j.jul.LogManager and log4j.configurationFile to conf/log4j2.xml

Adding jars to the classpath

To add jar to Tomcat JVM classpath you can use bin/setenv.sh (setenv.bat for windows) and just set the classpath there - next snippet assume you copied log4j jars in a boot folder under tomee/tomcat root and that you stripped te version:

export CLASSPATH="$CATALINA_BASE/boot/log4j-api.jar:$CATALINA_BASE/boot/log4j-jul.jar:$CATALINA_BASE/boot/log4j-core.jar"

Adding system properties

For system properties we can use the same file but we need to initialize the CATALINA_OPTS variable:

export CATALINA_OPTS="-Dlog4j.configurationFile=$CATALINA_BASE/conf/log4j2.xml -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"

Alternative setup: tomee-maven-plugin

Two previous parts were explaining how to do the setup manually but you can also use tomee maven plugin to do that.

Note that it needs at least the version 7.0.2 of the plugin (so today it is a snapshot):

<plugin>
  <groupId>org.apache.tomee.maven</groupId>
  <artifactId>tomee-maven-plugin</artifactId>
  <version>7.0.2-SNAPSHOT</version> <!-- maven coordinate as classpath support -->
  <configuration>
    <classpaths>
      <classpath>org.apache.logging.log4j:log4j-api:${log4j2.version}</classpath>
      <classpath>org.apache.logging.log4j:log4j-jul:${log4j2.version}</classpath>
      <classpath>org.apache.logging.log4j:log4j-core:${log4j2.version}</classpath>
    </classpaths>
    <systemVariables>
      <log4j.configurationFile>${openejb.base}/conf/log4j2.xml</log4j.configurationFile>
      <java.util.logging.manager>org.apache.logging.log4j.jul.LogManager</java.util.logging.manager>
    </systemVariables>
  </configuration>
</plugin>

This will automatically copy log4j jars in boot folder as we did previously and set the needed system properties when launching TomEE.

The log4j2.xml file needs to be in src/main/tomee/conf.

Now you can launch tomee and check your logs are using log4j2.xml:

mvn package tomee:run

Sample log4j2.xml

Since you are impatient here is a log4j2.xml you can copy if you don't have one already to test this setup:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
  <Appenders>
    <Console name="stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="[%d{HH:mm:ss.SSS}][%t][%-5level][%logger{36}] %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="INFO">
      <AppenderRef ref="stdout" level="INFO"/>
    </Root>
  </Loggers>
</Configuration>

Almost perfect

This setup works very well and if you start TomEE but switching to log4j2 you now use log4j2 registry. Remaining question is: does it handle logger per application or globally?

If you check the implementation you will identify it relies on org.apache.logging.log4j.spi.AbstractLoggerAdapter#registry to cache the logger context. The context will by default use Log4jContextFactory which will delegate the finding/caching of the loggers to a ContextSelector. This is the one we need to ensure is by classloader/application.

If not configured you will use ClassLoaderContextSelector flavor which is exactly what we desire: (almost) by application.

In conclusion, even if using log4j2 in TomEE needs a small setup it really does worth it if you are not alone managing the application cause you then get a modern implementation handling easily a bunch of logs without any issue, you can use a single log framework in the whole container/application area and it is very pluggable and has several interesting extensions JUL doesn't have yet. The only cost is to add 3 jars to the classpath and one configuration file so why not doing it?

 

From the same author:

In the same category: