23 November 2012

Logging with SLF4J and Logback in Tomcat and TomEE

Logging in Java would be a lot easier if every open source project out there would use SLF4J logging API, but of course this will never happen.

The fact that java.util.logging made it into the JRE does not make it any better than it is, so there's a good reason to hide it behind a facade and never use it directly. But there is no reason for everyone to invent their own logging facades instead of simply using SLF4J.

In my own applications, I always use SLF4J with Logback. Working with Tomcat, this means I'm getting an ugly mixture of log messages in different formats from my web application logging to System.out with Logback, and from Tomcat itself logging to System.err via JULI and java.util.logging, not to mention any third-party libraries contained in my application which use log4j, Apache Commons Logging or whatever.

There are two independent but similar approaches of replacing the official Tomcat JULI libraries by an SLF4J or Logback adapter: tomcat-slf4j and tomcat-slf-logback.

I've tried both, but in the end, I think it's easier to use nothing but the JUL-to-SLF4J bridge and other standard artifacts.


Setup for Tomcat


Get the following artifacts from Maven Central and copy them to $CATALINA_HOME/bin:
  • slf4j-api-1.7.2.jar
  • jul-to-slf4j-1.7.2.jar
  • logback-classic-1.0.6.jar
  • logback-core-1.0.6.jar

Add the following line to $CATALINA_HOME/conf/logging.properties or replace the entire file contents by

handlers = org.slf4j.bridge.SLF4JBridgeHandler

Create a Logback configuration file in $CATALINA_HOME/bin/logback-config/logback.xml, e.g.

<configuration debug="false">
 
 <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>

 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
     <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
   </encoder>
 </appender>
 
  <appender name="LOGFILE" class="ch.qos.logback.core.FileAppender">
    <file>${catalina.base}/logs/tomcat.log</file>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="STDOUT" />
    <appender-ref ref="LOGFILE" />
  </root>

</configuration>

Create or edit $CATALINA_HOME/bin/setenv.sh:

CLASSPATH=$CATALINA_HOME/bin/jul-to-slf4j-1.7.2.jar:\
$CATALINA_HOME/bin/slf4j-api-1.7.2.jar:\
$CATALINA_HOME/bin/logback-classic-1.0.6.jar:\
$CATALINA_HOME/bin/logback-core-1.0.6.jar:\
$CATALINA_HOME/bin/logback-config/

Now start Tomcat via

bin/catalina.sh run

and see it logging via Logback:

Setup for TomEE


The same procedure also works for TomEE if you delete the slf4-api-*.jar and slf4j-jdk14-*.jar libraries that come with TomEE in CATALINA_HOME/lib for the stand-alone distribution, or in CATALINA_HOME/webapps/tomee/lib for the drop-in WAR.

With this approach, all log messages are redirected, not only from Tomcat itself, but also from OpenWebBeans, OpenEJB and other components bundled with TomEE. The JULI replacements mentioned above only redirect Tomcat's own messages logged via JULI, since OpenWebBeans and OpenEJB have logging facades of their own.

8 comments:

Michael Wiles said...

I think you omitted the piece that needs to be added to logging.properties to activate the jul-slf4j bridge...

handlers = org.slf4j.bridge.SLF4JBridgeHandler

Harald Wellmann said...

Thanks, you're absolutely right. I've added the missing bit to my post.

Marcin Koziarski said...

Anybody tried this with Groovy config file? This works fine with XML but I had few issues using Groovy.

Here is logback.groovy:
------------------------------------
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.FileAppender

import static ch.qos.logback.classic.Level.INFO

appender("STDOUT", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
}
}
appender("LOGFILE", FileAppender) {
file = "${catalina.base}/logs/tomcat.log"
encoder(PatternLayoutEncoder) {
pattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
}
}
root(INFO, ["STDOUT", "LOGFILE"])
----------------------------------

Harald Wellmann said...

Marcin, I've never used a Groovy config file for logback myself, but I suppose it requires a Groovy runtime.

Did you add Groovy (and any transitive dependencies) to Tomcat's classpath, in addition to the SLF4J and Logback JARs?

Marcin Koziarski said...

yes, I did put all jars in classpath.

Dolphy Fernandes said...


Thanks for the tutorial. I am currently facing a Handler error issue. Could you please let me know what I could be missing?

Here is the SO link http://stackoverflow.com/questions/16472801/handler-error-in-slf4jbridgehandler

modprobe said...

Looks interesting, I'm going to give it a try.

Thanks for sharing

Mircea M said...

Thank you!