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 sees a distinct
logback environment, so that the logback configuration of one
application does not interfere with the settings of another. In
more technical terms, each web-application has a distinct copy of
LoggerContext
reserved for its own use. Recall that
in logback, each logger object is manufactured by a
LoggerContext
to which it remains attached for as
long as the logger object lives in memory. A variant of this
problem is the separation of application logging and the logging
of the container itself.
The simplest and easiest approach
Assuming your container supports child first class loading, separation of logging can be accomplished by embedding a copy of slf4j and logback jar files in each of your applications. 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. A copy of the logback.xml configuration file placed under WEB-INF/classes will be picked up when logback is loaded into memory.
By virtue of class loader separation provided by the container,
each web-application will load its own copy of
LoggerContext
which will pickup its own copy of
logback.xml.
Easy as pie.
Well, not exactly. First, although most do, not all containers support child first class loading. Second, logging generated by shared libraries will not be separated. The common idiom for referencing a logger is via a static reference. For example,
public class Foo { static Logger logger = LoggerFactory.getLogger(Foo.class); ... }
Static references are both memory and CPU efficient. Only one logger reference is used for all instances of the class. Moreover, the logger instance is retrieved only once, when the class is loaded into memory. Static logger references are fine as long as they are used in classes loaded by different class loaders. However, when a class is loaded by a parent class loader common to multiple applications, then the shared class in question will be loaded once and for all applications. If the shared class contains a static logger reference, than the logger will be retrieved once, when the shared class is loaded into memory and initialized. Moreover, for the shared class to successfully load into memory, slf4j and logback classed must be resolvable by the parent class loader. This implies that slf4j and logback jar files must also be accessible to the parent class loader. Note that for this scenario to occur a class must be shared and use slf4j, which is somewhat uncommon.
However, if the container itself uses SLF4J and defaults to parent-first class loading, then you need context selectors. Read on.
Context Selectors
Logback provides a mechanism for a single instance of SLF4J and logback classes loaded into memory to provide multiple logger contexts. When you write:
Logger logger = LoggerFactory.getLogger("foo");
the getLogger
() method in
LoggerFactory
class asks the SLF4J binding for a
ILoggerFactory
. When SLF4J is bound to logback, the
task of returning an ILoggerFactory
is delegated to
an instance of ContextSelector. Note
that ContextSelector
implementations always return
instances of the LoggerContext
class. This class
implements the ILoggerFactory
interface. In other
words, a context selector has the option to returning any
LoggerContext
instance it sees fit according to its
own criteria. Hence the name context selector.
By default, the logback binding uses DefaultContextSelector
which always returns the same LoggerContext
, called
the default logger context.
You can specify a different context selector by setting the
logback.ContextSelector system property. Suppose you
would like to specify that context selector to an instance of the
myPackage.myContextSelector
class, you would add the
following system property:
-Dlogback.ContextSelector=myPackage.myContextSelector
ContextJNDISelector
Logback-classic ships with a selector called
ContextJNDISelector
which selects the logger context
based on data available in JNDI. This leverages JNDI data
separation mandated by the J2EE specification. The same
environment variable can be set to carry a different value in each
application.
To enable ContextJNDISelector
, the
logback.ContextSelector system property needs to be set
to "JNDI", as follows:
-Dlogback.ContextSelector=JNDI
Note that JNDI is a convenient shorthand for "ch.qos.logback.classic.selector.ContextJNDISelector".
Setting JNDI variables in applications
In each of your applications, you need to name the logging context for the application. For a web-application, JNDI environment entries are specified within the web.xml file. If "kenobi" was the name of your application, you would add the following XML element to kenobi's web.xml file:
<env-entry> <env-entry-name>logback/context-name</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>kenobi</env-entry-value> </env-entry>
Assuming you have enabled ContextJNDISelector
,
logging for Kenobi will be done using a logger context named
"kenobi". Moreover, the "kenobi" logger context will be
initialized by convention using the configuration file
called logback-kenobi.xml which should be packaged within
Kenobi web-application under the WEB-INF/classes folder.
Although not required, you may specify a different configuration file other than the convention, by setting the "logback/configuration-resource" JNDI variable. For example, if you wish to specify my_config.xml instead of the conventional logback-kenobi.xml, you would add the following XML element to web.xml
<env-entry> <env-entry-name>logback/configuration-resource</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>my_config.xml</env-entry-value> </env-entry>
Configuring Tomcat for ContextJNDISelector
First, place the logback jars (that is logback-classic-${project.version}.jar, logback-core-${project.version}.jar and slf4j-api-${slff4j.version}.jar) in Tomcat's global (shared) class folder. In Tomcat 6.x, this directory is $TOMCAT_HOME/lib/.
The logback.ContextSelector system property can be set by adding the following line to the catalina.sh script, catalina.bat in Windows, found under $TOMCAT_HOME/bin folder.
JAVA_OPTS="$JAVA_OPTS -Dlogback.ContextSelector=JNDI
Hot deploying applications
When the web-application is recycled or shutdown, we strongly
recommend that the older LoggerContext
be closed and
subsequently discarded. Logback ships with a
ServletContextListener
called
ContextDetachingSCL
, designed specifically for
detaching the ContextSelector
instance associated
with the older web-application instance. In order to install it,
add the following lines to your web-applications web.xml
file.
<listener> <listener-class>ch.qos.logback.classic.selector.servlet.ContextDetachingSCL</listener-class> </listener>
Better performance
When ContextJNDISelector
is active, each time a
logger is retrieved, a JNDI lookup must be performed. This can
negatively impact performance, especially if you are using
non-static (aka instance) logger references. Logback ships with a
servlet filter named LoggerContextFilter,
specifically designed to circumvent the JNDI lookup cost. It can
be installed by adding the following lines to your applications
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>
At the beginning of each http-request,
LoggerContextFilter
will obtain the logger context
associated with the application and then place it in a
ThreadLocal
variable. ContextJNDISelector
will first check if the said ThreadLocal
variable is
set. If it is set, then JNDI lookup will skipped. Note that at the
end of the http request, the ThreadLocal
variable will
be nulled. Installing LoggerContextFilter
improves
logger retrieval performance by a wide margin.
Nulling the ThreadLocal
variable allows garbage
collection of the web-application when it is stopped or
recycled.