I consider it quite distasteful to criticize other people's work, especially in public. However, since the logging API included in JDK 1.4 will be considered by many as the "standard", I feel compelled to react. I am not alone in my criticism of JSR47, Greg Davis has his own set of comments.
The JDK 1.4 logging API is a result of the JSR47 effort, led by Graham Hamilton.
Before delving into the details, some historical perspective is in order. I am the founder of the log4j project. I participated in the specification of the JSR47 API, although not as an expert. In 1999, I was still working for IBM and could not join the experts group because big blue had already Chris Barlock as a member in the JSR47 experts group. Chris is the author of IBM's logging toolkit for Java.
On the surface, his toolkit has heavily influenced the JSR47 API. In particular, the two share the same basic components, namely loggers, levels, handlers and formatters. In log4j, these components are called categories, priorities, appenders and layouts respectively. Pairwise, they are identical in purpose. As such, the terms logger and category, level and priority, handler and appender, formatter and layout will be used interchangeably in the remainder of this document.
Even after a casual review it should be apparent that the log4j and JSR47 APIs are very similar. For one, they are the only logging APIs which are based on a named hierarchy. If you understand one API, then understanding the concepts of the other should be a breeze. There are differences however.
In JSR47, a parent logger knows about its children but not the
other way around. Children do not have links to their parent. For
example, the logger named "foo"
knows about
"foo.bar1"
and "foo.bar2"
. However,
"foo.bar1"
has no links to its parent "foo"
.
In log4j, it is exactly the other way around. A log4j category contains a link to its parent but a parent does not have links to its children.
At first glance, this might look like a mundane implementation detail but it is actually quite fundamental.
In JSR47, when you set the level of a logger, say
wombat
, JSR47 traverses the tree below
wombat
. In other words, the levels for all the loggers
descending from wombat
are overwritten. This can be a
very expensive operation for large trees. In particular, for the most
common case where one sets the level of the root logger. However,
performance is not the point I am trying to make.
In log4j, changing the priority of a category involves the change of a single field. Children categories dynamically inherit the priority of their parent by traversing the hierarchy tree upwards.
It follows that with JSR47 if you configure the level for logger "foo.bar1" before configuring the level for "foo", then the latter instruction will overwrite the first exactly as if the first instruction for configuring "foo.bar1" had never existed. Configuration order dependence is not a show stopper but it is something that will bite you time and again.
In contrast, in log4j categories can be configured in any order. You never have to worry about configuration order.
In JSR47, a logger does not walk the hierarchy to inherit its level but possesses a copy of it.
Unfortunately, in the JSR47 API, handlers cannot be inherited because it would be prohibitively expensive to let each logger to contain a distinct Vector of all inherited handlers, especially in large trees.
To circumvent this problem by JSR47 defines global handlers. A logger logs to global handlers and to the handlers attached to itself directly. It does not inherit any handlers from the hierarchy.
In log4j, appenders are inherited additively from the hierarchy. A category will log to the appenders attached to itself as well as the appenders attached to its ancestors. This might not seem like much until the day you need handler inheritance; probably a week after you decide to adopt a logging API.
Similarly, in log4j resource bundles are inherited from the hierarchy. In JSR47, a resource bundle must be attached to each logger individually. There is no resource bundle inheritance in JSR47. In practice, this means that you have to choose between internationalization and the benefits of the named logger hierarchy. It's one or the other. This limitation is particularly surprising because support for internationalization is advocated as one of the primary advantages of the JSR47 API.
JSR 47 defines the levels ALL
, SEVERE
,
WARNING
, INFO
, CONFIG
,
FINE
, FINER
, FINEST
and
OFF
. Experience shows that the levels ALL
and OFF
are never needed. The SEVERE
and
CONFIG
levels are unique to JSR47.
Having three debugging levels FINE
,
FINER
, FINEST
could seem like a good
idea. However, you will soon discover that even when by yourself, it
is hard to decide when to use which level. It is plain impossible in
groups.
Log4j in contrast has a limited but self-evident set of priorities:
FATAL
, ERROR
, WARN
,
INFO
and DEBUG
.
Both JSR47 and log4j allow the user to extend the set of priorities. Log4j supports subclasses of priorities in configuration files as well as across the wire. JSR47 does not.
Log4j has appenders capable of logging to the console, to files, to Unix Syslog daemons, to Microsoft NT EventLoggers, remote servers, to JMS channels, automatically generate email etc. It can roll log files by size or date and log asynchronously.
JSR47 can log to the console, to files, to sockets and to a memory buffer.
Log4j has an extensible and powerful layout called the
PatternLayout
. JSR47 offers the much weaker
SimpleFormatter
as an alternative.
Log4j supports configuration through property files as well as XML documents. JSR47 currently admits only property files. Moreover, the language of JSR47 configuration files is very weak. In particular, you can only configure one instance of a given handler class. This means that you can log to just one file at a time.
There are many other details in which log4j differs from JSR47. Even if the log4j core is small, the project contains a total of over 30'000 lines of well-tested code. JSR47 contains about 5'000 lines of code.
Log4j has been around for a number of years, enjoys the support of five active developers (committers) and is being used in thousands of projects. Our site gets over 500 downloads each and every day, and the numbers are on the rise. Log4j has been ported to C++ and Python. Companies are also offering commercial products extending log4j.
Here is a short list of opensource projects or sites that are known to use log4j.
By the way, log4j runs fine under JDK 1.1 and above. JSR 47 will run under JDK 1.4 and only under JDK 1.4. Interestingly enough, no package shipped with JDK 1.4 is using the JSR47 API.
Brian R. Gilstrap has re-written JSR47 API to
run under JDK 1.2 and 1.3. He has also published an article
in JavaWorld. This is all very promising but since
java.util.logging
is under the java.*
namespace, when running under JDK 1.3, you will systematically
encounter:
Exception in thread "main" java.lang.ExceptionInInitializerError: java.lang.SecurityException: Prohibited package name: java.util.logging at java.lang.ClassLoader.defineClass(ClassLoader.java:477) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:111) at java.net.URLClassLoader.defineClass(URLClassLoader.java:248) at java.net.URLClassLoader.access$100(URLClassLoader.java:56) at java.net.URLClassLoader$1.run(URLClassLoader.java:195) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:188) at java.lang.ClassLoader.loadClass(ClassLoader.java:297) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:286) at java.lang.ClassLoader.loadClass(ClassLoader.java:253) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:313)
Jochen Hiller had observed this problem in early 2001 when he implemented the JSR47 API by wrapping log4j.
Note that any third-party implementation using the
java.*
or javax.*
namespaces violates Sun's
trademark on Java. Without explicit permission from Sun, such software
remains under the threat of litigation.
RunTimeException
is thrown to the user or (in handlers
only) an internal field is set. In the first case, the
RunTimeException
will cause your application to crash. In
the latter case, you can retrieve the last caught exception in the
handler by querying the getException
method of that
handler. The former is totally unacceptable while the latter is
impractical.
In log4j, under no circumstances are exceptions thrown at the
user. However, all appenders have an associated
ErrorHandler
. This ErrorHandler
is invoked
by the appender whenever a handler-specific error occurs. By default,
log4j appenders are associated with an
OnlyOnceErrorHandler
which emits a message on the console
for the first error in an appender and ignoring all following errors.
An ErrorHandler
can implement an arbitrary error
handling policy. For example, after a failure to write to a database a
JDBCAppender
can be redirected to fall back on a
FileAppender
. This functionality is supported in XML
configuration files. You do not need to change a single line of client
code.
But again who cares about errors, right?
Logging performance must be studied in three distinct cases: when logging is turned off, when turned on but due to priority comparison logic not enabled, and when actually logging. Please refer to the log4j manual for a more detailed discussion of logging performance.
When logging is turned on, log4j will be about two to three times slower to decide whether a log statement is enabled or not. This is due to the dynamic nature of log4j which requires it to walk the hierarchy. To give you an idea about the figures involved, under JDK 1.4 beta, we are talking about 90 nanoseconds instead of 30 nanoseconds on a 800Mhz Intel processor. In other words, one million disabled logging requests will cost under a second in both environments.
In a shipped binary, you can turn off logging entirely and both APIs will perform identically. Note that if one is not careful, the cost of parameter construction before invoking a disabled log statement will overwhelm any other performance consideration. Regardless of the API you decide to use, logging statements should never be placed in tight loops, for example, before or after an element swap instruction in a sort algorithm.
In log4j, caller localization information is optional whereas in JSR47 it is always extracted. Since the extraction of caller localization is a very slow operation, in the common case where caller information is not needed, log4j will log the same information 4 to 100 times faster.