Creating JDK 1.4 Logging Handlers
Date: Jan 3, 2003
Article is provided courtesy of Sams.
The logging API that is included in JDK 1.4 is intended to be very flexible. This allows you to easily extend the logging API without having to completely replace it. One of the most common ways to extend the logging API is by writing your own handler. In this article, I will show you how to create a handler that allows you to change the logging style of the JDK logging API.
A handler does not just allow you to change the format that the log information is written to. A handler also allows you to specify where the logging information is stored. For example, you could easily create a handler that writes the logging information to a JDBC data source, rather than to the files that logs are generally written to.
Understanding the Structure of the Logging API
Before I show you how to create your own custom handler, I will review the basic structure of the logging API. I will begin by showing you a simple program that makes use of the logging API. TO start with, any class in your program that is going to make use of the logging API must import it. This is done with the following line:
import java.util.logging.*;
This import gives you access to all of the classes in the logging API. To begin using the logging API, your program first must create a new Logger object. The Logger class is provided to you by the logging API. This is done by the following lines of code:
// Create a new logger object Logger logger = Logger.getLogger( "com.heaton.articles.logger"); logger.setLevel(Level.ALL);
This creates a new logger object that will log under the name of "com.heaton.articles.logger". This is just a hierarchical name that specifies the log's name; every log will have a name similar to this one. The hierarchical nature allows applications to see the logs from several different applications. For example, the log name "com.heaton" would specify every log that began with the levels "com" and "heaton". The setLevel command specifies that we want to record all levels of severity.
Now that the Logger object has been created, you should write some data to it. The following lines of code show you how a typical log is written to. As you can see, the logging API can accept logged data in a variety of formats:
// try some logging logger.info("This is how you write a regular entry"); logger.warning("This is how you write a warning"); try { int i=0/0; } catch ( Exception e ) { logger.log(Level.WARNING, "This logs an exception:", e); }
The purpose of this article is to show you how to create your own log handler. Notice that this code does not specify a handler to use. By default, the logging API will write the logged information to the console if no additional handlers are specified. To specify an alternate handler to use, the following code should be used:
logger.addHandler( myhandler );
Creating a Handler Class
Now, you will be shown how to create a handler class. Any handler class must subclass the Handler class provided by the logging API. The class Handler is declared abstract. To create a functional Handler class, you must override the following three abstract methods: close, flush, and publish. By implementing these three methods, you will be passed all of the logging information that your handler is expected to log. The signatures for these three methods are shown here:
abstract public void close(); abstract public void flush(); abstract public void publish(LogRecord record);
Obviously, these are not the only methods that are allowed in your handler. You may add any other methods or properties that are needed to properly implement your handler. One such method is the constructor. You can use the constructor to accept information about where your handler will be storing its data. One example would be a handler that logged to a JDBC data source. You could use the constructor to receive information about the database connection.
To properly implement a handler, you must implement the close method, which should close whatever medium was opened in the constructor. This is important so that system resources are properly closed. This is particularly important when files are used, so that the files are properly closed and do not lose any data.
In addition to the close method, you must implement a flush method. The flush method is called to ensure that all data up to this point has been written to whatever medium the handler is storing it to. For example, if you were logging to a file, you would simply call the flush method of the output stream that you were logging to. The flush method does not always make sense to implement.
Often, you may find that you have no need of a flush method. This is particularly true if you have implemented your handler so that the data logged is immediately written. If data is immediately written, there are no temporary buffers to flush. Regardless if you immediately write your data or not, you still must implement a flush method. If you are not going to use the flush method, simply create an empty method.
Finally, we must create the publish method, which is the one that does most of the work. The publish method is called for each logging event. The publish method accepts a single parameter, of type LogRecord. The LogRecord object contains all of the information that is to be logged for this event. A publish method will generally call all of the "get" methods contained in the LogRecord and write the data to whatever medium this log handler is using.
The LogRecord object passed to a publish method contains a variety of information. A level is used to indicate how severe the log event was. This level allows certain parts of the program to filter out unimportant log events. The LogRecord also contains the source class and method to allow you to trace what part of the program generated the log event. The log event also contains the actual text of the log entry. There are also other, less-frequently used pieces of information that are also passed with the LogRecord.
Constructing your Publish Method
What is done by your publish method is determined by what sort of a handler you are creating. There are many different tasks that handlers can be used for. Some examples include the following:
Log data to a JDBC data source
Log data to a remote socket
Page a user when a specific error occurs
Send a status email with a specific error occurs
I will now show you an example of exactly how a handler class would be constructed. Listing 1 shows the structure that your handler class would have.
Listing 1[em]A Simple Log Handler
import java.util.logging.*; /** * JDBC Logging Article * * This is a reusable class that implements * a JDK1.4 log handler. * * ©author Jeff Heaton (http://www.jeffheaton.com) * ©version 1.0 * ©since November 2002 */ public class EMailLogHandler extends Handler { /** * Overridden method used to capture log entries and put them * into a JDBC database. * * @param record The log record to be stored. */ public void publish(LogRecord record) { // first see if this entry should be filtered out // the filter should keep anything if ( getFilter()!=null ) { if ( !getFilter().isLoggable(record) ) return; } // send an email indicating that problem has occurred } /** * Called to close this log handler. */ public void close() { // not used } /** * Called to flush any cached data that * this log handler may contain. */ public void flush() { // not used } }
This handler is designed to send an email message for each log entry that is received. The code is not provided to actually send the email or page a user. You will need to implement this functionality in a way that is supported by your email or paging systems.
Notice that no filtering is done inside of the handler. Any LogRecord that is sent to the handler will be logged. A filter will prevent the user from getting an email or page for each log entry that is made.
Conclusions
As you can see, you can easily create your own handlers to be used in conjunction with JDK 1.4 logging. You handlers can be used by themselves or in addition to the handlers that are already provided by JDK. This allows a great deal of flexibility and extensibility from the JDK 1.4 logging API.