commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Apache Wiki <wikidi...@apache.org>
Subject [Jakarta-commons Wiki] Update of "Logging/StaticLog" by SimonKitching
Date Sun, 05 Mar 2006 10:27:10 GMT
Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Jakarta-commons Wiki" for change notification.

The following page has been changed by SimonKitching:
http://wiki.apache.org/jakarta-commons/Logging/StaticLog

The comment on the change is:
New page about static Log issue

New page:
== When Static References to Log objects can be used ==

There are two very common patterns used with logging:
{{{
public class Foo {
  private Log log = LogFactory.getLog(Foo.class);
  ....
}
}}}
and 
{{{
public class Foo {
  private static Log log = LogFactory.getLog(Foo.class);
  ....
}
}}}

The use of the static qualifier can be beneficial in some circumstances. However in others
it is a very bad idea indeed,
and can have unexpected consequences. This page describes when each solution is appropriate.

== The Static Problem ==

The technical result of using static is obvious: there is only one Log reference shared across
all instances of the class.
This is clearly memory efficient; only one reference(4 or 8 bytes) is needed no matter how
many instances are created. It is also
CPU-efficient; the lookup required to find the Log instance is only done once, when the class
is first referenced.

When writing stand-alone application code the use of static is a good idea.

However when the java code concerned is a '''library''' that may be deployed within a '''container'''
of some sort (such as a J2EE server)
then problems can occur. It is common for containers to create a hierarchy of java !ClassLoader
objects such that each "application"
deployed within the container has its own !ClassLoader but where there is some '''shared classloader'''
that is in the ancestry of all
the deployed "applications". In this case, when a class holding a static reference to a Log
instance is deployed at the
"application" level (eg at the "webapp" level in a servlet or j2ee container) there is also
no problem; if multiple applications
deploy the class in question then they each have their own copy of the class and there are
no interactions with other applications. 

However consider the case when a class using "private static Log log = ..." is deployed via
a !ClassLoader that is in the ancestry of ''multiple''
supposedly independent "applications". In this case, the log member is initialised only once,
because there is only one copy of the class. That 
initialisation (typically) occurs the first time any code tries to instantiate that class
or call a static method on it. When initialisation of
the class occurs, what should the log member be set to? The options are:
 * To reference an underlying Log object which is part of a hierarchy configured from information
at the "container" level, ie not associated with any particular "application"
 * To reference an underlying Log object which is part of a hierarchy configured from information
somehow related to the current application 
 * To reference a "proxy" object which will determine what the "current application" is each
time a log method is invoked, and delegate to the appropriate underlying Log object at that
time.

The first option means that logging cannot be configured at a per-application level. Whatever
logging configuration is set up
will apply equally to every application within the container, and all the output from those
applications will be mixed together.
This is a major issue.

The second option means that the Log object will be configured according to the ''first''
application that called it. Other
applications within the container will have their output sent to the destination configured
for that first application. Obviously
this is a major issue.

The third option allows per-application logging configuration, and correctly filters and outputs
the required log messages. 
However the performance penalty paid is very large; this isn't an acceptable solution either.

Note that this discussion hasn't talked about "Thread Context !ClassLoaders" or other technical
points. The problem is not with
the details but with the general concept: when the Log object is shared among multiple applications
it isn't possible to
have per-application configuration '''and''' reasonable performance.

The real root cause of this problem is that SHARED data (static members on a class) is being
shared across supposedly
independent "applications". If no classes are "shared" between applications then the problem
does not exist. However it
appears that (to my personal frustration) container vendors continue to encourage the use
of shared classes, and 
application developers continue to use it.

The alternative solution is: avoid the use of "static" references to Log objects in any code
that might be deployed into
a shared classpath.

== Does SLF4J have this problem? ==

Yes. SLF4J does suffer from exactly the issues listed above, and the recommendation is the
same:
avoid static references to log objects from code that might be deployed in a shared classpath.

There are a number of possible scenarios here. First however we need to define a term "TCCL-aware".
A logging library is "TCCL-aware" when its initialisation code uses the Thread Context !ClassLoader
to attempt to locate its configuration files, rather than the !ClassLoader of the classes
in the
logging library itself. As an example, in its standard form, log4j is not "TCCL-aware". However
it
can be made "TCCL-aware" via a "Contextual !RepositorySelector" as described here:
  http://www.qos.ch/logging/sc.jsp

Now onto some possible scenarios:

Suppose SLF4J is deployed at a shared level, that there is a class in the shared classpath
with a static Logger
reference, and that there is a class in the application classpath with a static logger. When
the underlying
logging library is not TCCL-aware then logging configuration is only read from the "shared"
classpath, ie applies
globally to all output from all applications deployed within the container. Output is correct,
but there is no
ability to enable debug logging for just one application, and all output from all applications
is mixed together.
When the underlying logging library is TCCL-aware, then there ''is'' per-application logging
configuration. In
addition, output from those classes deployed at the application level is correct. However
the class deployed
at the shared level will have its Log object initialised using the configuration for the very
first application
that calls that class; when other applications call that same class the logging output will
go to the destination
of the ''first'' application. Not good.

If SLF4J is deployed at both the shared and the application level, and parent-first classloading
is selected for
an application then behaviour is identical to the above. Even when child-first classloading
is selected, if
the underlying logging library is TCCL-aware then things also go poorly. However if child-first
classloading is
selected AND the underlying logging library is not TCCL-aware then the results are almost
bearable: output from
the shared class can only be configured globally, ie all output goes to the same destination
regardless of which
application made the call.

Unfortunately in most cases the authors of library code are not in the position of being able
to specify
to the library users where the code will be deployed, what classloading order will be selected,
or what the
behaviour of the underlying logging library should be. Therefore it ''is'' necessary to avoid
the use of
static references to SLF4J loggers for exactly the same reasons as commons-logging.

SLF4J ''does'' have less probability of encountering problems than commons-logging when used
incorrectly,
however. The reasons are:
 * Currently not a lot of logging libraries are "TCCL-aware", and
 * Very little library code of the type deployed into shared classpath locations currently
uses SLF4J.

Commons-logging is itself TCCL-aware specifically to enable application-level configuration
of logging
libraries that are not TCCL-aware; effectively therefore every logging library qualifies as
"TCCL-aware"
when used with commons logging. Commons-logging is also very widely used by many libraries
that ''are''
frequently deployed via shared classloaders.

Note that this is section is NOT intended as criticism of SLF4J. As described earlier, the
problem is due to the
fundamental conflict between isolating logging between applications while sharing a static
Log reference,
and therefore SLF4J faces the same constraints as commons-logging in these scenarios.

== Alternatives to static loggers ==

In most cases, simply leaving out the "static" qualifier from the Log reference is the correct
solution.
Even when a Class is shared between supposedly-independent applications (because the .class
file is deployed
via a shared classloader), instances of that class are not shared. The TCCL active when the
instance is created 
and its Log member initialised will therefore be the same TCCL active when any of its methods
which generate
log output are called. Note that in the case of commons-logging this does NOT increase the
number of Log objects
created; each instance will have a reference to the same Log object. 

However the "lookup" of that Log object does need to be performed each time an instance is
created; for objects
which have short lifetimes this may be undesirable. In this situation, if the short-lived
object has a reference
to some longer-lived one then the longer-lived one can host the Log object, eg
{{{
  public ShortLiver {
    private Owner owner;
    ShortLiver(Owner owner) {
        this.owner = owner;
    }
    public void doSomething() {
      owner.getShortLiverLog().debug("doSomething called");
    }
  }
}}}

Alternatively, the Log object can be retrieved when necessary:
{{{
  public ShortLiver {
    public void doSomething() {
      Log log = LogFactory.getLog(ShortLiver.class);
      log.debug("doSomething called");
    }
  }
}}}

Note that this applies only to code that may be deployed in a shared !ClassLoader. Normal
application code
need not be concerned with this issue at all.

== Container-assisted logging ==

A few containers have tried to work around the problems listed above. In particular, JBoss
provides
a custom log4j "filter" that uses internal knowledge about the container to make configuration
at
the container level work in a somewhat acceptable manner. As described above, deploying commons-logging
(or SLF4J) at the shared level, and deploying a non-TCCL-aware underlying library has the
undesirable
effect of imposing a single logging configuration on all contained applications. However the
jboss
custom filter then allows log messages flowing through that single logging instance to be
separated
out again into different destinations according to the originating application. This has the
desired
effect in the end, although it is somewhat inefficient; turning up logging to debug on one
application
actually turns up logging to debug on ''all'' applications, though output from applications
other than
the one of interest is then suppressed again before being output.

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message