Apache Log4cxx
Version 1.3.0
|
Creating useful log information requires a fair amount of planning and effort. Observation shows that approximately 4 percent of code is dedicated to logging. Consequently, even moderately sized applications will have thousands of logging statements embedded within their code. Given their number, it becomes imperative to manage these log statements without the need to modify them manually.
Let us give a taste of how this is done with the help of an imaginary application MyApp that uses Log4cxx.
In order to start using Log4cxx, a simple example program is shown below. This program does nothing useful, but it shows the basics of how to start using Log4cxx. Using the BasicConfigurator class, we are able to quickly configure the library to output DEBUG, INFO, etc level messages to standard output.
The above application does nothing useful except to show how to initialize logging with the BasicConfigurator and do logging with different loggers. Note that file based configurations are also possible - see DOMConfigurator and PropertyConfigurator.
Configuring Log4cxx in the main function has the limitation that any logging statements in static initialization code will not generate output. Log4cxx must be configured before it is used and in this example Log4cxx is not configured until the main() function starts.
In this example we use a getLogger() wrapper function which configures Log4cxx on the first usage. The advantages of this approach are:
This program (MyApp) begins by including the file that defines the com::foo::getLogger() function. It obtains a logger named MyApp (which in this example is the fully qualified name) from the com::foo::getLogger() function.
MyApp uses the com::foo::Bar class defined in header file com/foo/bar.h.
The com::foo::Bar class is defined in header file com/foo/bar.h.
The com::foo::Bar class is implemented in the file com/foo/bar.cpp.
The header file com/foo/config.h defines the com::foo::getLogger() function and a LoggerPtr type for convenience.
The file com/foo/config1.cpp implements the com::foo::getLogger() function defines initAndShutdown as a static struct so its constructor is invoked on the first call to the com::foo::getLogger() function and its destructor is automatically called during application exit.
The invocation of the BasicConfigurator::configure method creates a rather simple Log4cxx setup. This method is hardwired to add to the root logger a ConsoleAppender. The output will be formatted using a PatternLayout set to the pattern %r [%t] %p %c %x - %m%n
.
Note that by default, the root logger is assigned a DEBUG level.
The output of MyApp is:
The Log4cxx environment is fully configurable programmatically. However, it is far more flexible to configure Log4cxx using configuration files. Currently, configuration files can be written in XML or in Java properties (key=value) format.
The previous example always outputs the same log information. Fortunately, it is easy to modify config.cpp so that the log output can be controlled at runtime. Here is a slightly modified version.
This version of config.cpp instructs PropertyConfigurator to use the MyApp.properties file to configure Log4cxx. A more realistic approach would (for example) use the current module name to select the configuration file (see the com/foo/config3.cpp file for how to do this).
Here is a sample MyApp.properties configuration file that results in exactly same output as the previous BasicConfigurator::configure based example.
It can be noticed that the PropertyConfigurator file format is the same as log4j.
Suppose we are no longer interested in seeing the output of any component belonging to the com::foo package. The following configuration file shows one possible way of achieving this.
The output of MyApp configured with this file is shown below.
As the logger com.foo.Bar does not have an assigned level, it inherits its level from com.foo, which was set to WARN in the configuration file. The log statement from the Bar::doIt method has the level DEBUG, lower than the logger level WARN. Consequently, doIt() method's log request is suppressed.
Here is another configuration file that uses multiple appenders.
Calling the enhanced MyApp with the this configuration file will output the following on the console.
In addition, as the root logger has been allocated a second appender, output will also be directed to the example.log file. This file will be rolled over when it reaches 100KB. When roll-over occurs, the old version of example.log is automatically moved to example.log.1.
Note that to obtain these different logging behaviors we did not need to recompile code. We could just as easily have logged to a UNIX Syslog daemon, redirected all com.foo output to an NT Event logger, or forwarded logging events to a remote Log4cxx server, which would log according to local server policy, for example by forwarding the log event to a second Log4cxx server.