Chapter 9: Context Selectors

It is not knowledge, but the act of learning, not possession but the act of getting there, which grants the greatest enjoyment. When I have clarified and exhausted a subject, then I turn away from it, in order to go into darkness again; the never-satisfied man is so strange if he has completed a structure, then it is not in order to dwell in it peacefully, but in order to begin another. I imagine the world conqueror must feel thus, who, after one kingdom is scarcely conquered, stretches out his arms for others.

—KARL FRIEDRICH GAUSS, Letter to Bolyai, 1808.

Style, like sheer silk, too often hides eczema.

—ALBERT CAMUS, The Fall

The problem: Logging Separation

The chapter deals with a relatively difficult problem of providing a separate logging environment for multiple applications running on the same web or EJB container. In the remainder of this chapter the term "application" will be used to refer either a web-application or a J2EE application interchangeably. In a separated logging environment, each application can configure logback in different ways such that the settings of one application does not interfere with the settings of another. A variant of this problem is the separation of application logging and the logging of the container itself.

A simple and easy approach

Assuming you container supports child first class loading, separation of logging can be accomplished by embedding a copy of slf4j and logback jar files in each application. For web-applications, placing slf4j and logback jar files under the WEB-INF/lib directory of the web-application is sufficient to endow each web-application with a separate logging environment.

Logback provides a mechanism for dealing with multiple contexts, without corruption of data, nor collision between logger context instances.

One thing we know is that JNDI environments are independent. Thus, setting environment variables in each application will allow a given component to know which application it is dealing with at the moment. This is basically the mechanism that uses logback to provide easy access to the right LoggerContext instance.

The component that manages the different contexts is a ContextSelector implementation. The JNDI-specific implementation is called ContextJNDISelector.

Each Web application provides two environment variables. One that specifies the application's LoggerContext name, and one that provides the path to the xml file that will be used to configure the context.

The server side

Configuring Tomcat

First, place the logback jars (that is logback-classic-VERSION.jar, logback-core-VERSION.jar and slf4j-api-VERSION.jar) in the server's shared class directory. In Tomcat, this directory is TOMCAT_HOME/common/lib/.

The next step is to let logback know that it will have to use JNDI to manage the context instances. This is done thanks to a System Property. When launching Tomcat, make sure that the logback.ContextSelector property is set with the JNDI value. This can be done by editing the TOMCAT_HOME/bin/catalina.sh or TOMCAT_HOME/bin/catalina.bat file, and adding the following line to the java options:

-Dlogback.ContextSelector=JNDI

Configuring Jetty

Configuring Jetty requires first to enable the use of JNDI. This is not a big deal, since the Jetty distribution provides the configuration files needed to achieve this task. The only thing to do is launch Jetty with the following command:

java -jar start.jar etc/jetty.xml etc/jetty-plus.xml

Note that you will need to install your appplications in the JETTY_HOME/webapps-plus directory.

In Jetty, the server shared class directory is JETTY_HOME/lib/. This is where you will need to place the logback jars (that is logback-classic-VERSION.jar, logback-core-VERSION.jar and slf4j-api-VERSION.jar).

The next step is to let logback know that it will have to use JNDI to manage the context instances. This is done thanks to a System Property. In Jetty, adding an environment variable is done by adding the following xml element in the JETTY_HOME/etc/jetty.xml configuration file, nested in a Configuration element:

<Call class="java.lang.System" name="setProperty"> <Arg>logback.ContextSelector</Arg> <Arg>JNDI</Arg> </Call>

Be aware that adding a -Dlogback.ContextSelector=JNDI to the java command when starting the server will not work. By doing this, the LoggerFactory instanciated by the server for its internal logging will try to use JNDI, when only the Web applications should attempt to retrieve their LoggerContext this way.

Configuring each Web application

While each Web application will need the logback jars to compile, they need not nor should be placed within the Web application's WAR file, except if you are using Jetty.

This is due to Jetty's internal Classloading mechanism. Consequently, the logback-classic-VERSION.jar and slf4j-api-VERSION.jar files should also be placed in the WEB-INF/lib/ directory of your webapps when running Jetty.

In each Web application's web.xml file, two JNDI environment entries are needed. The first one specifies the desired name of the application's LoggerContext. It takes the following form:

<env-entry>
  <description>JNDI logging context for this app</description>
  <env-entry-name>logback/context-name</env-entry-name>
  <env-entry-type>java.lang.String</env-entry-type>
  <env-entry-value>ContextApp-A</env-entry-value>
</env-entry>

The second JNDI entry will lead logback to the application's own xml configuration file. It can be declared as shown below:

<env-entry> <description>URL for configuring logback context</description> <env-entry-name>logback/configuration-resource</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>logback-app-A.xml</env-entry-value> </env-entry>

Specifying only the name of the file will lead logback to search for it in the Web application's WEB-INF/classes/ directory.

When the Web application is recycled or shutdown, it is very often useful to recycle the associated LoggerContext. This can be done by installing a ServletContextListener which will detach the context from the ContextSelector and shut it down.

The ContextDetachingSCL class which ships with logback does exactly that. To use it, add the following lines to your Web application's web.xml file.

<listener> <listener-class>ch.qos.logback.classic.selector.servlet.ContextDetachingSCL</listener-class> </listener>

Using the ContextJNDISelector might slow down your application, because of the JNDI call that is issued each time a LoggerContext is required. To prevent the cost of this call, logback ships with a LoggerContextFilter component. This filter is a javax.servlet.Filter implementation that gets the environment-specific LoggerContext and sets it in a ThreadLocal variable. Each time the ContextSelector will be called to provide the Web application's own LoggerContext, it will first check if the ThreadLocal variable is set. If it is, then the call to the JNDI environment will not be issued. The LoggerContextFilter class increases the performances by a wide margin.

Like all servlet filters, the LoggerContextFilter can act before and after the Web application's process. This allows the filter to set the ThreadLocal variable at the beginning of the process and to remove it once the Web application has finished processing the request. This behaviour permits the thread to be recycled for use by another Web application and still provide the correct LoggerContext.

The LoggerContextFilter can be used by adding the following lines to your Web application's web.xml file.

<filter> <filter-name>LoggerContextFilter</filter-name> <filter-class>ch.qos.logback.classic.selector.servlet.LoggerContextFilter</filter-class> </filter> <filter-mapping> <filter-name>LoggerContextFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

Some recommandations

To avoid confusion, it is prudent to name each Web application in the web.xml file, as in:

<display-name>Name_Of_My_WebApp</display-name>

We recommend that you name logback configuration resources uniquely. In particualar, avoid naming the logback configuration resource as logback.xml for a non-default logger context.

While trying to configure the Web application logback would search for the resource logback.xml using the thread context classloader. Thus, it would first attempt to locate logback.xml file using the classloader specific to the Web application. However, if the file logback.xml did not exist there (if you forgot to put a custom one in WEB-INF/classes), and if the file logback.xml existed higher up in the classloader tree, we could end up in a situation where the logger context for your Web application would be configured using the same file as that used to configure the default context. Such involuntary sharing of the same configuration by multiple repositories will result in corrupt log output.