Sooner or later every Java programmer realizes that using System.out.print
is neither effective nor efficient way of checking the current state of an application or the value of a variable. Of course, it is true that standard output is simple and apparently fast. But what if we want to have our traces written to a file instead of the console? Then, we would have to manually replace all the commands with the code. That is great, but what if need to change it again and use the console? Whoa.. it would require a lot of avoidable work. Can it be made simplier? Well, the answer for this question is really simple: Log4j! It is an multi-platform library written in Java by Ceki Gülcü and its beginnigs date back to the last century (no fear! I mean the '90s :) The library provides a way of logging in Java applications. Currently, it is supported by Apache foundation and a part of the Jakarta project. There are currently three versions available: 1.2 which is supposed to be the stable one, 1.3 whose development was abbandoned for some reasons and 2.0 which is, let's say, experimental version.
The first and obvious thing is the tracing of the application flow. We can write current values of variables or objects and various messages about errors or inform about business events that took place while application is running.
Well, it all depends on our imagination. To start with the real basics, there are three types of objects:
Loggers,
Appenders,
Layouts.
Loggers are provided with methods that create logs and set the proper priorities. The places where the logs can be sent are configured using Appenders. The form of the output messages is defined by Layout objects.
There are three types of Loggers:
NOPLogger,
RootCategory,
RootLogger.
The most important one is RootLogger
and I will use it in the following examples. The other two one can be omitted - RootCategory
is considered as obsolete and NOPLogger
stands for "No-operation logger".
As it regards log levels, the following levels can be distinguished:
FATAL: Severe errors that cause premature termination of an application;
ERROR: Execution errors;
WARN: The usage of obsolete components or unexpected conditions that do not crash of the application;
INFO: Level used to trace program execution;
DEBUG: More detailed information about program execution and stated, used for diagnostic purposes;
TRACE: The most detailed log level;
ALL / OFF: All levels / turn off logging.
When we choose a certain log level we should keep in mind that all log messages with lower level will not be printed. For instance, if we set log level to DEBUG (the default one), then all logs with TRACE level will never be visible.
In comparison to Loggers, there are numerous types of Appenders. Moreover everyone can create their own using AppenderSkeleton
class. Here are the most interesting Appenders:
ConsoleAppender
simply prints logs messages to the console. By default it works as System.out.
, but it can also function as System.Err
.
WriterAppender
prints logs to a file using Writer
class or OutputStream
.
JDBCAppender
allows to store log messages in a database.
LF5Appender
stores log messages in a Swing-based console.
SMTPAppender
sends an e-mail when a specific logging event occurs.
DailyRollingFileAppender
is an extension of FileAppender
class. Its underlying file is rolled over at a user chosen frequency. Besides that, it allows to divide messages into years, months, days, etc.
Assuming that you have chosen Appender that suits your needs, it is time to configure what should be printed into logs. It is done by Layouts:
HTMLLayout
produces HTML tables.
PatternLayout
defines a pattern of a message.
SimpleLayout
causes logs to be written in a form: level - message.
XMLLayout
sets XML as target format.
DateLayout
simplifies time adjustments that is put in logs.
According to my experience and other users’ opinions PatternLayout and HTMLLayout
are the most popular Layouts.
If we decide to use PatternLayout
, then we can define what a message should look like. How to do that? We define a kind of template in which we use following elements:
%% - single % character
%c - event category
%C - the name of a class that triggered an event
%d - the date when it happened
%m - message
%n - line separator
%p - event level
%r - number of miliseconds since the application was started
%t - name of a thread that sent the message
%x - diagnostic context that is bound to a thread
%M - method name
%L - line number where an event occured
%F - file name where log message was printed
%l - location that sent log message
It is advised not to overuse the last four elements, since they slow down an application.
The last thing to describe is configuration. It can be done in three different ways:
by using BasicConfigurator
object,
by using .properties file,
by using XML configuration file.
BasicConfigurator
should be used only if we want configure logs creation. It is the simplest way. It causes creation of PatternLayout
object whose ConversionPattern
property has the following value:
%-4r [%t] %-5p %c %x - %m%n
BasicConfigurator class has only three methods: configure()
, configure(Appender)
and resetConfiguration()
.
Configuration applied by .properties file is similiar to the configuration that uses XML file, however the syntax is different. This is the sample code that is invoked from static method main:
BasicConfigurator.configure(); Logger logger = Logger.getRootLogger(); logger.debug("Hello world");
The result is:
2 [main] DEBUG root - Hello world
This log message is quite easy to understand, because we are using the default pattern I have already mentioned a few lines earlier.
It is easy to define our own pattern, for example:
Layout lay1 = new PatternLayout("[%p] %c - %m - Data wpisu: %d %n"); Appender app1 = new ConsoleAppender(lay1); BasicConfigurator.configure(app1); Logger logger = Logger.getRootLogger(); logger.debug("Hello world");
The result you would get is:
[DEBUG] root - Hello world - Data wpisu: 2009-11-24 15:20:29,049
Why does it look like? Again, it's very simple: we declare a pattern and an appender whose purpose is to print logs to the console. Next, the appender is passed as a parameter to the configure()
function and that is all.
OK, what to do if we need to change logging level?
There is no problem with this - we should add the following line:
logger.setLevel(Level.TRACE);
Or we want to redirect logging messages to a file?
Then the line with appender construction (Appender app1 = new ConsoleAppender(lay1);
) should be, for instance, replaced with this code:
Appender app1 = null; try { app1 = new FileAppender(lay1,"C:/log_express.txt"); } catch(IOException ex) { }
As I have mentioned earlier configuration may also be applied in .properties or XML file. They both work according to the same principle: we put a file called log4j.properties or log4j.xml in a custom package (If you choose the default package, the system will automatically find the configuration file).
The sample content of .properties file:
log4j.appender.C=org.apache.log4j.ConsoleAppender log4j.appender.C.layout=org.apache.log4j.PatternLayout log4j.appender.C.layout.ConversionPattern=[%p] %c - %m - Data wpisu: %d %n log4j.rootLogger=DEBUG, C
The sample content of XML file:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%p] %c - %m - Data wpisu: %d %n"/> </layout> </appender> <root> <priority value ="debug" /> <appender-ref ref="console" /> </root> </log4j:configuration>
In both cases configuration is the same. The difference is only in a way it is expressed. You decide which should be used.
It is worth to mention that if we do not apply Log4j configuration, there would be a warning and the logs will not be displayed.
It can be done in a following way:
download .zip archive: http://apache.privatejetscharter.net/logging/log4j/1.2.15/apache-log4j-1.2.15.zip,
extract log4j-1.2.15.jar file,
create a new library using tabs Tools -> Library,
add the library to your project,
use appropriate imports, e.g. import org.apache.log4j.*;
.
I have heard opinions saying that "But there is something called Java Logging API which supported by SUN Microsystems"
It's true. Nevertheless, it does not mean that something is better just because it is considered as a standard. Java Logging API was introduced later than Log4j and is partially based on it. Although there are not many differences between these two libraries, there is a popular belief amongst Java developers that everything possible with Java Logging API is also possible with Log4j. And even more... I have put the advantages and disadvantages in the table below:
Log4j |
Java Logging API |
+ Supported by Apache Longer on the market Used by a great number of programmers - Rarely updated |
+ Supported by SUN It does not require any extra libraries - Supported by relatively small number of developers |
Besides the obvious advantages, the common drawback is the fact if the logs are very big, you may have problems with spotting the most relevant information. This phenomenon has even its name - it is called scrolling-blindness.
The potential offered by Log4j is huge. It is simple is usage and what is important - it is open source. It was optimalized in order to achieve the best performance. Also, it solves related to multi-threading. Intelligent usage of log messages considerably contributes to software development. In the next issue I am going to publish a few more sample pieces of code that show how effectively and impressively use Log4j in various types of applications. Please remember: no more System.out.print
:)
Source: http://www.javaexpress.pl/article/show/Log4j__czyli_jak_skutecznie_tworzyc_logi_w_aplikacjach_javowych