http://www.developer.com/

Back to article

Creating a JDBC Log Handler for JDK 1.4


September 23, 2002

One of the features that JDK 1.4 introduced is the Logging API. This is a set of objects that allows applications to create consistent, expandable logs. One important feature of the Logging API is the ability to create your own handler classes. A handler class gives you the ability to abstract where the logged data will go.

There are many eventual destinations for logged data. Programs commonly write the contents of an application log to files, the system console, or even to memory. If you want to log to one of these mediums, the Logging API provides several handler classes to allow it. If you want to log to an alternate medium, you must write your own handler class.

Writing a handler class is the subject of this article. This article shows you how to write a handler class that allows the logged information to be written to a database. The reusable class presented in this article will allow a standard JDK 1.4 log to be written to any JDBC-compatible database.

The Structure of the Logging API

It is important to review the basic structure of the Logging API before you are shown how to create your own handler class. First, you will be shown a simple Java program that uses the Logging API. 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. 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("Sample log entry");
logger.warning("Sample warning");

try {
  int i=0/0;
  } catch ( Exception e ) {
  logger.log(Level.WARNING, "This is what an exception looks
                             like", e);
}

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. Later in this article, we will see how to add the JDBC Logging handler to the Logger.

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.

In addition to these three required methods, you can implement any methods of your own that are needed to log incoming information. One such method is almost always a constructor. Your constructor allows you to prepare whatever storage medium you are using. This article creates a handler that can log data to a JDBC database. The constructor for this handler accepts information necessary to open the database, and then opens the database connection.

To properly implement a handler, you must implement the close method. The close method 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. The JDBC logging handler that you will be shown in the next section has no need of a flush method. However, because the flush method is declared abstract, you must implement a flush method. If you have no use for the flush method, just implement an empty method.

The last required method 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.

Creating a JDBC Handler

Now you have seen what is necessary to create a log handler class. This section will show you how to create a log handler, named JDBCLogHandler, that will log information to a JDBC data source. The complete listing for the JDBCLogHandler class is shown in Listing 1. Before you are shown how this class was constructed the database connection must be explained. This class uses a JDBC database connection. Any JDBC-compatible database should work. For the purposes of this article, I will assume you are using the MySQL database. The MySQL database is a freely available, open source database that can be downloaded from http://www.mysql.org. All log information is written to a single table. This table, which is named log, can be created using the following SQL command.

CREATE TABLE log (
  level integer NOT NULL,
  logger varchar(64) NOT NULL,
  message varchar(255) NOT NULL,
  sequence integer NOT NULL,
  sourceClass varchar(64) NOT NULL,
  sourceMethod varchar(32) NOT NULL,
  threadID integer NOT NULL,
  timeEntered datetime NOT NULL)

As to the name of the database and how to access it, the JDBCLogHandler class remains oblivious to this information so that it can be used with any sort of database. This connection information is passed into the JDBCLogHandler class through the constructor.

For the sake of efficiency, this class uses prepared SQL. Prepared SQL, stored in PreparedStatement objects, compiles a SQL command so that it can be executed repeatedly as fast as possible. The JDBCLogHandler class uses two such SQL commands. The first, stored in the prepInsert variable, is used to insert a single row into the log table. The second, stored in the prepClear variable, is used to clear all rows from the log table. When the constructor is called, these two prepared statements are allocated.

The constructor handles opening the database. The constructor is passed the name of the JDBC driver, and a connection string that is used to define what database is to be used. The following lines of code are used, inside the constructor, to open the database connection and create the two prepared SQL commands.

Class.forName(driverString);
Connection=
  DriverManager.getConnection(
    connectionString);
prepInsert=
  connection.prepareStatement(
    insertSQL);
prepClear =
  connection.prepareStatement(
    clearSQL);

As you can see, the actual text of the SQL commands is stored in the insertSQL and clearSQL Strings. These values can also be found in Listing 1. The connection that is opened, by the above lines, is used through the lifetime of this handler object. The connection is ultimately closed when the user calls the handler's close method. The flush method is implemented only because it is required. As you can see in Listing 1, nothing is done by the flush method. The flush method normally forces any recent log entries to be stored to the underlying medium. This handler stores the rows as soon as they are received, so nothing is gained by calling flush.

The publish method is the heart of the handler. It is the publish method that writes each of the log entries to the database. When you instruct a Logger to use this handler, each entry will be sent to the handler. The next section will show you how to actually instruct the Logger to use this handler.

The handler itself is a very simple method. All that it does is copies each of the entries in the LogRecord to a parameter for the SQL INSERT statement. After all of the values are copied, the insert is executed. This command completes the process of inserting the row, and there is no reason to flush. Of course, close should be called when you are finished with the handler.

Using the JDBC Handler

Now that you have created a handler class that will store information to a database, you will be shown how to use it. Listing 2 shows a very simple Java application that makes use of the JDBC Log handler class. First, you must construct the JDBCLogHandler class, as seen here.

// set up the JDBCLogger handler
JDBCLogHandler jdbcHandler
  = new JDBCLogHandler(driver,connection);
jdbcHandler.clear();

As you can see, a driver name and connection are passed to the JDBCLogHandler class's constructor. This will be dependant upon what database your using. Here I am using a MySQL database named "logging", with a user name of "logger" and a password of "logpass". Consult your database's documentation for more information on what you should put here. After the JDBCLogHandler class has been instantiated, you must add a handler to the logger. This is done with the following command.

logger.addHandler(jdbcHandler);

Of course, you can have multiple handlers on one Logger. If you add additional handlers, the logging information will be sent to all of your handlers.

Conclusions

The Logging API of JDK 1.4 provides many new features that will help standardize the way Java applications store logging information. Because this API is standard to the Java platform, logging utilities can be used by multiple applications. The JDBC handler presented in this article could easily be incorporated into any project that makes use of JDK 1.4's Logging API.

Listing 1: A JDBC log handler

import java.util.logging.*;
import java.sql.*;

/**
 * JDBC Logging Article
 *
 * This is a reusable class that implements
 * a JDK1.4 log handler that will write the
 * contents of the log to a JDBC data source.
 * 
 * ©author Jeff Heaton (http://www.jeffheaton.com)
 * ©version 1.0
 * ©since January 2002
 */
public class JDBCLogHandler extends Handler {

  /**
   * A string that contains the classname of the JDBC driver.
   * This value is filled by the constructor.
   */
  String driverString;

  /**
   * A string that contains the connection string used by the
   * JDBC driver. This value is filled by the constructor.
   */
  String connectionString;

  /**
   * Used to hold the connection to the JDBC data source.
   */
  Connection connection;

  /**
   * A SQL statement used to insert into the log table.
   */
  protected final static String insertSQL=
  "insert into log (level,logger,message,sequence,"
  +"sourceClass,sourceMethod,threadID,timeEntered)"
  +"values(?,?,?,?,?,?,?,?)";

  /**
   * A SQL statement used to clear the log table.
   */
  protected final static String clearSQL=
  "delete from log;";

  /**
   * A PreparedStatement object used to hold the main
   * insert statement.
   */
  protected PreparedStatement prepInsert;

  /**
   * A PreparedStatement object used to hold the clear
   * statement.
   */
  protected PreparedStatement prepClear;


  /**
   * @param driverString The JDBC driver to use.
   * @param connectionString The connection string that
   * specifies the database to use.
   */
  public JDBCLogHandler(String driverString,
                        String connectionString)
  {
    try {
      this.driverString = driverString;
      this.connectionString = connectionString;

      Class.forName(driverString);
      connection = DriverManager.getConnection(connectionString);
      prepInsert = connection.prepareStatement(insertSQL);
      prepClear = connection.prepareStatement(clearSQL);
    } catch ( ClassNotFoundException e ) {
      System.err.println("Error on open: " + e);
    } catch ( SQLException e ) {
      System.err.println("Error on open: " + e);
    }
  }

  /**
   * Internal method used to truncate a string to a specified width.
   * Used to ensure that SQL table widths are not exceeded.
   * 
   * @param str The string to be truncated.
   * @param length The maximum length of the string.
   * @return The string truncated.
   */
  static public String truncate(String str,int length)
  {
    if ( str.length()<length )
      return str;
    return( str.substring(0,length) );
  }

  /**
   * 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
    if ( getFilter()!=null ) {
      if ( !getFilter().isLoggable(record) )
        return;
    }

    // now store the log entry into the table
    try {
      prepInsert.setInt(1,record.getLevel().intValue());
      prepInsert.setString(2,truncate(record.getLoggerName(),63));
      prepInsert.setString(3,truncate(record.getMessage(),255));
      prepInsert.setLong(4,record.getSequenceNumber());
      prepInsert.setString(5,truncate
                          (record.getSourceClassName(),63));
      prepInsert.setString(6,truncate
                          (record.getSourceMethodName(),31));
      prepInsert.setInt(7,record.getThreadID());
      prepInsert.setTimestamp(8,
                              new Timestamp
                                  (System.currentTimeMillis()) );
      prepInsert.executeUpdate();
    } catch ( SQLException e ) {
      System.err.println("Error on open: " + e);
    }

  }

  /**
   * Called to close this log handler.
   */
  public void close()
  {
    try {
      if ( connection!=null )
        connection.close();
    } catch ( SQLException e ) {
      System.err.println("Error on close: " + e);
    }
  }

  /**
   * Called to clear all log entries from the database.
   */
  public void clear()
  {
    try {
      prepClear.executeUpdate();
    } catch ( SQLException e ) {
      System.err.println("Error on clear: " + e);
    }
  }


  /**
   * Not really used, but required to implement a handler. Since 
   * all data is immediately sent to the database, there is no 
   * reason to flush.
   */
  public void flush()
  {
  }
}

Listing 2: Example of using the JDBCHandler class

import java.util.logging.*;

public class TestLog {

  /**
   * Set the following string to whatever JDBC
   * driver you wish to use. I have it set to
   * use the MM driver of MySQL. You can get the
   * MM driver from: 
   * http://mmmysql.sourceforge.net/.
   */
  static public final String driver
  = "org.gjt.mm.mysql.Driver";

  /**
   * Set this to your connection string to access
   * your database. Refer to your database
   * documentation on how to set this. The one
   * I have below logs into a MySQL database.
   */
  static public final String connection
  = "jdbc:mysql://192.168.1.100/logging"
    +"?user=logger&password=logpass&database=logging";
  /**
   * Main function. Performs some basic
   * log testing.
   * 
   * @param argv Arguments are not used.
   */

  public static void main(String argv[])
  {
    // set up the JDBCLogger handler
    JDBCLogHandler jdbcHandler 
    = new JDBCLogHandler(driver,connection);
    jdbcHandler.clear();


    // setup
    Logger logger
    = Logger.getLogger("com.heaton.articles.logger");
    logger.addHandler(jdbcHandler);
    logger.setLevel(Level.ALL);

    // try some logging

    logger.info("Sample log entry");

    logger.warning("Sample warning");

    try {
      int i=0/0;
    } catch ( Exception e ) {
      logger.log(Level.WARNING, 
        "This is what an exception looks like", e);
    }
  }
}

Author Bio

Jeff Heaton is the author of the upcoming JSTL: JSP Standard Tag Library (Sams, 2002). Jeff works as a software designer for Reinsurance Group of America. Jeff has written three books and numerous magazine articles about computer programming. Jeff may be contacted through his Web site, http://www.jeffheaton.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date