/usr/share/doc/liblogback-java/manual/loggingSeparation.html is in liblogback-java-doc 1:1.0.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<title>Chapter 9: Logging separation</title>
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<link rel="stylesheet" type="text/css" href="../css/screen.css" media="screen" />
<link rel="stylesheet" type="text/css" href="../css/_print.css" media="print" />
<link rel="stylesheet" type="text/css" href="../css/prettify.css" media="screen" />
</head>
<body onload="prettyPrint()">
<script type="text/javascript">prefix='../';</script>
<script type="text/javascript" src="../js/prettify.js"></script>
<script src="../templates/header.js" type="text/javascript"></script>
<div id="left">
<noscript>Please turn on Javascript to view this menu</noscript>
<script src="../templates/left.js" type="text/javascript"></script>
</div>
<div id="right">
<script src="menu.js" type="text/javascript"></script>
</div>
<div id="content">
<h1>Chapter 9: Logging separation</h1>
<div class="quote">
<p><em>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.</em></p>
<p>—KARL FRIEDRICH GAUSS, Letter to Bolyai, 1808.</p>
<p><em>Style, like sheer silk, too often hides eczema.</em></p>
<p>—ALBERT CAMUS, <em>The Fall</em></p>
</div>
<script src="../templates/creative.js" type="text/javascript"></script>
<script src="../templates/setup.js" type="text/javascript"></script>
<h2>The problem: Logging Separation</h2>
<p>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 to 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
<code>LoggerContext</code> reserved for its own use. Recall that
in logback, each logger object is manufactured by a
<code>LoggerContext</code> 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.
</p>
<h2><a name="easy" href="#easy">The simplest and easiest
approach</a></h2>
<p>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
<em>WEB-INF/lib</em> directory of the web-application is
sufficient to endow each web-application with a separate logging
environment. A copy of the <em>logback.xml</em> configuration file
placed under <em>WEB-INF/classes</em> will be picked up when
logback is loaded into memory.
</p>
<p>By virtue of class loader separation provided by the container,
each web-application will load its own copy of
<code>LoggerContext</code> which will pickup its own copy of
<em>logback.xml</em>.</p>
<p>Easy as pie.</p>
<p>Well, not exactly. Sometimes you will be forced to place SLF4J
and logback artifacts in a place accessible from all applications,
typically because a shared library uses SLF4J. In that case, all
applications will share the same logging environment. There are
various other scenarios where a copy of SLF4J and logback
artifacts is necessarily placed at a spot where it can be seen by
all applications making logging separation by class loader
separation impossible. All hope is not lost. Please read on.
</p>
<h2><a name="contextSelectors" href="#contextSelectors">Context
Selectors</a></h2>
<p>Logback provides a mechanism for a single instance of SLF4J and
logback classes loaded into memory to provide multiple logger
contexts. When you write:
</p>
<pre class="prettyprint source">Logger logger = LoggerFactory.getLogger("foo");</pre>
<p>the <code>getLogger</code>() method in
<code>LoggerFactory</code> class will ask the SLF4J binding for a
<code>ILoggerFactory</code>. When SLF4J is bound to logback, the
task of returning an <code>ILoggerFactory</code> is delegated to
an instance of <a
href="../apidocs/ch/qos/logback/classic/selector/ContextSelector.html">ContextSelector</a>. Note
that <code>ContextSelector</code> implementations always return
instances <code>LoggerContext</code>. This class implements the
<code>ILoggerFactory</code> interface. In other words, a context
selector has the option to returning any
<code>LoggerContext</code> instance it sees fit according to its
own criteria. Hence the name context <em>selector</em>.
</p>
<p>By default, the logback binding uses <a
href="../xref/ch/qos/logback/classic/selector/DefaultContextSelector.html">DefaultContextSelector</a>
which always returns the same <code>LoggerContext</code>, called
the default logger context.</p>
<p>You can specify a different context selector by setting the
<em>logback.ContextSelector</em> system property. Suppose you
would like to specify that context selector to an instance of the
<code>myPackage.myContextSelector</code> class, you would add the
following system property: </p>
<p class="source">-Dlogback.ContextSelector=myPackage.myContextSelector</p>
<p>The context selector needs to implement the
<code>ContextSelector</code> interface and have a constructor
method admitting a <code>LoggerContext</code> instance as its only
parameter.
</p>
<h3><a name="ContextJNDISelector"
href="#ContextJNDISelector">ContextJNDISelector</a></h3>
<p>Logback-classic ships with a selector called
<code>ContextJNDISelector</code> which selects the logger context
based on data available via JNDI lookup. This approach leverages
JNDI data separation mandated by the J2EE specification. Thus, the
same environment variable can be set to carry a different value in
different applications. In other words, calling
<code>LoggerFactory.getLogger()</code> from different applications
will return a logger attached to a different logger context, even
if there is a single LoggerFactory class loaded into memory shared
by all applications. That's logging separation for you.
</p>
<p>To enable <code>ContextJNDISelector</code>, the
<em>logback.ContextSelector</em> system property needs to be set
to "JNDI", as follows:</p>
<p class="source">-Dlogback.ContextSelector=JNDI</p>
<p>Note that the value <code>JNDI</code> is a convenient shorthand
for
<code>ch.qos.logback.classic.selector.ContextJNDISelector</code>.</p>
<h3><a name="settingJNDIVariables"
href="#settingJNDIVariables">Setting JNDI variables in
applications</a></h3>
<p>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 <em>web.xml</em>
file. If "kenobi" was the name of your application, you would add
the following XML element to kenobi's web.xml file:</p>
<pre class="prettyprint source"><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></pre>
<p>Assuming you have enabled <code>ContextJNDISelector</code>,
logging for Kenobi will be done using a logger context named
"kenobi". Moreover, the "kenobi" logger context will be
initialized by <em>convention</em> by looking up the configuration
file called <em>logback-kenobi.xml</em> as a <em>resource</em>
using the thread context class loader. Thus, for example for the
kenobi web-application, <em>logback-kenobi.xml</em> should be
placed under the <em>WEB-INF/classes</em> folder.
</p>
<p>If you wish to, you may specify a different configuration file
other than the convention, by setting the
"logback/configuration-resource" JNDI variable. For example, for
the kenobi web-application, if you wish to specify
<em>aFolder/my_config.xml</em> instead of the conventional
<em>logback-kenobi.xml</em>, you would add the following XML
element to web.xml
</p>
<pre class="prettyprint source"><env-entry>
<env-entry-name>logback/configuration-resource</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>aFolder/my_config.xml</env-entry-value>
</env-entry></pre>
<p>The file <em>my_config.xml</em> should be placed under
<em>WEB-INF/classes/aFolder/</em>. The important point to remember
is that the configuration is looked up as a Java resource using
the current thread's context class loader.
</p>
<h3><a name="jndiTomcat" href="#jndiTomcat">Configuring Tomcat for
ContextJNDISelector</a></h3>
<p>First, place the logback jars (that is
logback-classic-1.0.0.jar,
logback-core-1.0.0.jar and
slf4j-api-1.6.4.jar) in Tomcat's global (shared) class
folder. In Tomcat 6.x, this directory is
<em>$TOMCAT_HOME/lib/</em>.
</p>
<p>The <em>logback.ContextSelector</em> system property can be set
by adding the following line to the <em>catalina.sh</em> script,
catalina.bat in Windows, found under <em>$TOMCAT_HOME/bin</em>
folder.</p>
<p class="source">JAVA_OPTS="$JAVA_OPTS -Dlogback.ContextSelector=JNDI"</p>
<h3><a name="hotDeploy" href="#hotDeploy">Hot deploying
applications</a></h3>
<p>When the web-application is recycled or shutdown, we strongly
recommend that the incumbent <code>LoggerContext</code> be closed
so that it can be properly garbage collected. Logback ships with a
<code>ServletContextListener</code> called <a
href="../xref/ch/qos/logback/classic/selector/servlet/ContextDetachingSCL.html"><code>ContextDetachingSCL</code></a>,
designed specifically for detaching the
<code>ContextSelector</code> instance associated with the older
web-application instance. It can be installed by adding the
following lines into your web-applications <em>web.xml</em>
file.</p>
<pre class="prettyprint source"><listener>
<listener-class>ch.qos.logback.classic.selector.servlet.ContextDetachingSCL</listener-class>
</listener></pre>
<h3><a name="betterPerf" href="betterPerf">Better
performance</a></h3>
<p>When <code>ContextJNDISelector</code> 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 (a.k.a. instance) logger references. Logback ships with a
servlet filter named <a
href="../xref/ch/qos/logback/classic/selector/servlet/LoggerContextFilter.html">LoggerContextFilter</a>,
specifically designed to avoid the JNDI lookup cost. It can
be installed by adding the following lines to your application's
web.xml file.</p>
<pre class="prettyprint source"><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></pre>
<p>At the beginning of each http-request,
<code>LoggerContextFilter</code> will obtain the logger context
associated with the application and then place it in a
<code>ThreadLocal</code> variable. <code>ContextJNDISelector</code>
will first check if the <code>ThreadLocal</code> variable is
set. If it is set, then JNDI lookup will skipped. Note that at the
end of the http request, the <code>ThreadLocal</code> variable will
be nulled. Installing <code>LoggerContextFilter</code> improves
logger retrieval performance by a wide margin.
</p>
<p>Nulling the <code>ThreadLocal</code> variable allows garbage
collection of the web-application when it is stopped or
recycled.</p>
<h2><a name="tamingStaticRefs" href="#tamingStaticRefs">Taming
static references in shared libraries</a></h2>
<p><code>ContextJNDISelector</code> works nicely to create logging
separation when SLF4J and logback artifacts are shared by all
applications. When <code>ContextJNDISelector</code> is active, each
call to <code>LoggerFactory.getLogger()</code> will return a logger
belonging to a logger context associated with the calling/current
application.</p>
<p>The common idiom for referencing a logger is via a static
reference. For example,
</p>
<pre class="prettyprint source">public class Foo {
<b>static</b> Logger logger = LoggerFactory.getLogger(Foo.class);
...
}</pre>
<p>Static logger 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. If the host class belongs to
some application, say kenobi, then the static logger will be
attached to kenobi's logger context by virtue of
<code>ContextJNDISelector</code>. Similarly, if the host class
belongs to some other application, say yoda, then its static
logger reference will be attached to yoda's logger context, again
by virtue of <code>ContextJNDISelector</code>.
</p>
<p>If a class, say <code>Mustafar</code>, belongs to a library
shared by both <em>kenobi</em> and <em>yoda</em>, as long as
<code>Mustafar</code> has non static loggers, each invocation of
<code>LoggerFactory.getLogger()</code> will return a logger
belonging to a logger context associated with the calling/current
application. But if <code>Mustafar</code> has a static logger
reference, then its logger will be attached to the logger context of the
application that calls it first. Thus,
<code>ContextJNDISelector</code> does not provide logging
separation in case of shared classes using static logger
references. This corner case has eluded a solution for eons.</p>
<p>The only way to solve this issue transparently and perfectly
would be to introduce another level of indirection inside loggers
so that each logger-shell somehow delegated work to an inner
logger attached to the appropriate context. This approach would be
quite difficult to implement and would incur a significant
computational overhead. It is not an approach we plan to pursue.
</p>
<p>It goes without saying that one could trivially solve the
"shared class static logger" problem by moving the shared classes
inside the web-apps (unshare them). If unsharing is not possible,
then we can solicit the magical powers of <a
href="appenders.html#SiftingAppender"><code>SiftingAppender</code></a>
in order to separate logging using JNDI data as separation
criteria.
</p>
<p>Logback ships with a discriminator called <a
href="../xref/ch/qos/logback/classic/sift/JNDIBasedContextDiscriminator.html">JNDIBasedContextDiscriminator</a>
which returns the name of the current logger context as computed
by <code>ContextJNDISelector</code>. The
<code>SiftingAppender</code> and
<code>JNDIBasedContextDiscriminator</code> combination will create
separate appenders for each web-application.
</p>
<pre class="prettyprint source"><configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator class="ch.qos.logback.classic.sift.JNDIBasedContextDiscriminator">
<defaultValue>unknown</defaultValue>
</discriminator>
<sift>
<appender name="FILE-${contextName}" class="ch.qos.logback.core.FileAppender">
<file><b>${contextName}.log</b></file>
<encoder>
<pattern>%-50(%level %logger{35}) cn=%contextName - %msg%n</pattern>
</encoder>
</appender>
</sift>
</appender>
<root level="DEBUG">
<appender-ref ref="SIFT" />
</root>
</configuration></pre>
<p>If kenobi and yoda are web-applications, then the above
configuration will output yoda's log output to <em>yoda.log</em>
and kenobi's logs to <em>kenobi.log</em>; this even works for logs generated
by static logger references located in shared classes.</p>
<p>You can try out the technique just described with the help of the
<a
href="http://github.com/ceki/logback-starwars">logback-starwars</a>
project.
</p>
<p>The above approach solves the logging separation problem but is
rather complex. It requires the proper installation of
<code>ContextJNDISelector</code> and mandates that appenders be
wrapped by <code>SiftingAppender</code> which is a non-trivial beast
in itself.
</p>
<p>Note that each logging context can be configured using the same
file or alternatively different files. The choice is up to
you. Instructing all contexts to use the same configuration file
is simpler as only one file has to be maintained. Maintaining a
distinct configuration file for each application is harder to
maintain but allows for more flexibility.</p>
<p>So are we done yet? Can we declare victory and go home? Well, not
quite.</p>
<p>Let's assume the web-application <code>yoda</code> is initialized
before <code>kenobi</code>. To initialize <code>yoda</code>, visit
<code>http://localhost:port/yoda/servlet</code> which will invoke
the <code>YodaServlet</code>. This servlet just says hello and logs
message before calling the <code>foo</code> method in
<code>Mustafar</code> which not-surprisingly logs a simple message
and returns.
</p>
<p>After <code>YodaServlet</code> is called, the contents of
<em>yoda.log</em> file should contain</p>
<pre class="source">DEBUG ch.qos.starwars.yoda.YodaServlet cn=yoda - in doGet()
DEBUG ch.qos.starwars.shared.Mustafar cn=yoda - in foo()</pre>
<p>Note how both log entries are associated with the "yoda" context
name. At this stage and until the server stops, the
<code>ch.qos.starwars.shared.Mustafar</code> logger is attached to
the 'yoda' context and will remain so until the server is stopped.
</p>
<p>Visiting <code>http://localhost:port/kenobi/servlet</code> will
output the following in <em>kenobi.log</em>.</p>
<pre class="source">DEBUG ch.qos.starwars.kenobi.KenobiServlet <b>cn=kenobi</b> - in doGet()
DEBUG ch.qos.starwars.shared.Mustafar <b>cn=yoda</b> - in foo()</pre>
<p>Note that even if the
<code>ch.qos.starwars.shared.Mustafar</code> logger outputs to
<em>kenobi.log</em> it is still attached to 'yoda'. Thus, we have
two distinct logging contexts logging to the same file, in this
case <em>kenobi.log</em>. Each of these contexts reference
<code>FileAppender</code> instances, nested within distinct
<code>SiftingAppender</code> instances, that are logging to the same
file. Although logging separation seems to function according to
our wishes, FileAppender instances cannot safely write to the same
file unless they enable <span class="option">prudent</span>
mode. Otherwise, the target file will be corrupted.</p>
<p>Here is the configuration file enabling prudent mode:</p>
<pre class="prettyprint source"><configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator class="ch.qos.logback.classic.sift.JNDIBasedContextDiscriminator">
<defaultValue>unknown</defaultValue>
</discriminator>
<sift>
<appender name="FILE-${contextName}" class="ch.qos.logback.core.FileAppender">
<file>${contextName}.log</file>
<b><prudent>true</prudent></b>
<encoder>
<pattern>%-50(%level %logger{35}) cn=%contextName - %msg%n</pattern>
</encoder>
</appender>
</sift>
</appender>
<root level="DEBUG">
<appender-ref ref="SIFT" />
</root>
</configuration></pre>
<p>If you were able to keep up with the discussion thus far and
have actually tried the logback-starwars examples, then you must be
truly obsessed with logging. You should consider seeking <a
href="http://www.qos.ch/shop/products/professionalSupport">professional
help</a>.</p>
<script src="../templates/footer.js"
type="text/javascript"></script> </div>
</body>
</html>
|