Chapter 9

Logging Classes


CONTENTS


log \'log\ n: any record of performance

Introduction

The output of your programs is important to you, especially if there are errors. The standard Java outputting mechanism is just fine for UNIX; it can be captured and redirected to files easily. But for Windows and Macintosh, it doesn't cut the mustard.

The biggest problem with output on the Windows and Macintosh platforms is that once your application has finished running, the output disappears. This is very annoying and does not help you in any way. Your intranet applications require much better log facilities than are available. You'll need nice logs that are easy to search. These logs are important for tracking errors and finding problems with your intranet and the applications that run on it.

This chapter will present two classes that implement a log file strategy that can be used in all of your applications. This strategy includes a common log file format and centralized log location. Finally, you'll take this log strategy even farther and apply it to Java's own output mechanism. This can be useful for Java applets or for running your applications on diskless workstations.

The Log

Logs and logging have been around since prehistoric times. The drawings left on cave walls by early man are logs of his day. After that, logs were used to record daily life in many ancient civilizations. Then, logs were used to record the voyages of sea captains. The versatility of the log is unbelievable, so why not utilize some of its charm in your own software?

You should, and you can. But writing log files with Java can be a hassle. If every time you create an application you have to perform the same initialization for logging, you'll soon not use your log for quick-and-dirty programs. But by making it simple, and providing an interface to the logging mechanism that is easy to use, logging becomes automatic and painless.

But what should you put into your log? How can you make it easy to read and search? Read on.

The Log Entry

A log entry is nothing more than a line of text documenting an event. The event might be a debug message or perhaps an error. It really doesn't matter what you're documenting because it is probably important to you. But your only real log choice with Java is writing information to the screen.

But writing information to the screen in Java is not the best solution for logs. The main reason for this is that if you are running from a window, the output disappears upon completion of your program. This is where the log classes come in. If you can use these classes to write your output, it will not disappear or be lost. It will be stored forever in a disk file.

The classes write log entries in a standard format. The format is as follows:

appName|date|user|level|text

where

appName is the name of the application that created the entry.
date is the date of the entry.
user is the user running the application.
level is an indicator of the severity of the log entry.
text is the text of the log entry.

Listing 9.1 lists a sample log file output.


Listing 9.1. Sample log file entries.
Employee|960609|munster|I|Application [Employee] started at Sun Jun 9 11:22:07
Employee|960609|munster|I|Application [Employee] ended at Sun Jun 9 11:22:18
Employee|960609|munster|I|Application [Employee] started at Sun Jun 9 12:24:57
Employee|960609|munster|I|Application [Employee] ended at Sun Jun 9 12:25:15

Each portion of the log entry is separated by the pipe character (|). This is a standard UNIX delimiter. It doesn't appear often in normal text and is easily spotted.

Nothing else in the entry needs explaining except the level indicator. This indicator allows you to place an importance level on your log entries. Afterwards, you can filter out the levels that you are interested in.

The levels and the interface to the logging mechanism are defined by the Log interface.

The Log Interface

The Log interface provides the template or pattern for all logging objects. There are two important parts to the Log interface. The first is the level indicators.

Log Level Indicators

There are five defined log level indicators. They are as follows:

Each indicates the level of importance, or severity, of the log entry it is applied to. Debug is, of course, the least severe of all entries. Fatal is the most severe. The rest are all in between. As you code, choose the severity level that fits the situation.

Note
The log levels mean absolutely nothing to the program. They are simply a way for you, the programmer, to identify the severity of an event.

The severity levels are defined as chars and are declared static final. Here is the definition of the log level indicators as found in the Log interface:

public static final char     DEBUG = 'D';
public static final char     INFO = 'I';
public static final char     WARNING = 'W';
public static final char     ERROR = 'E';
public static final char     FATAL = 'F';

The uppercased word is the constant value that you use when indicating log levels. These levels are used by passing it along to the logging method. The logging method will then store it into the log file. The logging method is the second part of the Log interface.

The log Method

The Log interface defines a single method called log. It is as follows:

//****************************************************************************
//* log                                          & nbsp;                           *
//****************************************************************************

    public void
    log( char logLevel, String logEntry )
    throws IOException;

This method must be implemented by users of this interface. The log method accepts, as arguments, a log level and a log entry. The rest of the log implementation is up to the implementor of this interface. I've chosen to implement this method to produce log files with entries such as the ones shown in Listing 9.1.

The Logging Classes

There are two classes that provide standard logging, as described earlier in this chapter. They are DiskLog and ScreenLog. DiskLog writes all log entries to a disk file. The ScreenLog writes all the log entries to the screen or a window. The ScreenLog is useful in cases where perhaps a DiskLog cannot be created, or is not needed.

When a DiskLog object is created, it opens the log file and moves to the end of the file, never overwriting what is currently stored inside. This gives you a persistent log file. It also allows several processes to share the same log file. Instead, the DiskLog object by default creates a new log file each day.

Take a closer look at the two classes.

DiskLog

The DiskLog class extends Java's RandomAccessFile class. The RandomAccessFile class is the only Java file class capable of not deleting the contents of a file when opened. This allows you to create a persistent disk log file. The declaration of the DiskLog class is as follows:

//****************************************************************************
//* DiskLog                                         &nb sp;                        *
//****************************************************************************

public class
DiskLog
extends RandomAccessFile
implements Log

As you can see, you extend RandomAccessFile and implement the Log interface.

Log File Names

Log file names can be anything you want. However, because you want your applications to share a common log file, a single log file should be used. To achieve this, the DiskLog class names its log files syslog.YYMMDD where YYMMDD is the date of the log file. This is the default name. If you do not choose a name, this is the one that will be used. You can always override the default and select your own log file name.

By default, this log file is stored in the directory where your program is located. There are ways to override these mechanisms in the constructors.

DiskLog has three constructors. All of them require the name of the application that is going to be using it. This is because the application name is the first thing the log entry contains. The other two constructors accept a filename, overriding the default name, and a path name, overriding the default location of the log file. I'll discuss each one in detail.

The first constructor creates a default log filename in the current directory. Here is the call:

public
DiskLog( String name )
throws IOException

The second constructor accepts, as input, the log filename and the application name. This is one way to override the default name given to a log file. The file, however, still resides in the current directory. Here is the constructor call:

public
DiskLog( String logName, String name )
throws IOException

The last constructor accepts the path to the log file, the name of the log file, and the application name. It combines the path- and filenames together to make a complete path to the file. Here is the constructor code:

public
DiskLog( String logDir, String logName, String name )
throws IOException

Tip
Note that all three constructors throw the IOException. This is because you've extended the RandomAccessFile class. In your constructor, you call the base class's constructor. Whenever you override a constructor of a base class, you must throw the same exceptions that the base class throws.

Making a Log

Opening a log file is as simple as constructing an object in Java. The following code example from the LogTester sample program illustrates the construction technique for the DiskLog class:

//    Create a disk log...
try
{
    dLog = new DiskLog( "Log Tester" );
}
catch ( IOException e )
{
    sLog.log( Log.ERROR, "Could not create the DiskLog object [" +
        e.toString() + "]" );
}

First you try to create a new DiskLog and assign it to the variable dLog. If it fails and IOException is thrown, the sLog (an instance of ScreenLog) shows the error.

Now all you have to do is write to your new log file.

Writing to the Log

To write an entry to the log, DiskLog provides a single method: log. This method is the implementation of the Log interface's abstract method log. It accepts two parameters as input: a
logging level and the text of the log entry. The log method constructs a log entry from the information passed and what it has, and writes it out to the log file.

There is a method available for you to specify what to do with log entries should the primary log facility fail, such as a log entry on another disk drive, or a log on the screen. Whatever the case, the setBackupLog() method accepts any class that implements the Log interface as input and stores this when writing to the primary log fails. You'll see this implemented below. The following is the source code for the log method:

//****************************************************************************
//* log                                          & nbsp;                           *
//****************************************************************************

    public void
    log( char logLevel, String logEntry )
    {
        String    logLine = appName + "|";

        //    Use the jif.util.FileDate for this new method...
        logLine += ( new FileDate() ).toFileString() + "|";
        logLine += System.getProperty( "user.name" ) + "|";
        logLine += logLevel + "|";
        logLine += logEntry;

        //    Let the system define how lines are terminated...
        logLine += System.getProperty( "line.separator" );

        //    Write it out to disk...
        try
        {
            writeBytes( logLine );
        }
        catch ( IOException e )
        {
            if ( backupLog != null )
            {
                try
                {
                    //    Write to backup...
                    backupLog.log( Log.ERROR, "Error [" + e.toString() +
                        "] writing the following entry to the log file:" );
                    backupLog.log( logLevel, logEntry );
                }
                catch ( IOException e2 )
                {
                    //    Write to screen
                    ScreenLog sl = new ScreenLog();
                    sl.log( Log.ERROR, "Backup log failed [" +
                        e2.toString() + "] writing the following entry:" );
                    sl.log( logLevel, logEntry );
                }
            }
            else
            {
                //    Write to screen!
                ScreenLog sl = new ScreenLog();
                sl.log( Log.ERROR, "Disk log failed [" + e.toString() +
                    "] writing the following entry:" );
                sl.log( logLevel, logEntry );
            }
        }
    }

Basically, you construct a string called logLine using the input received plus some additional system information. The log method then throws a linefeed or carriage return/linefeed on the end and writes it out to the file. Don't forget that the DiskLog extends the RandomAccessFile class; therefore, the writeBytes() method is inherited.

Around the writeBytes() method you wrap a try/catch clause. You do this so the user of this class doesn't have to. If an error occurs while writing a log entry to disk, try to write the entry to the backup log. If writing to the backup fails, write it to the screen. If no backup log is defined, write the error to the screen. No matter what, the log entry will be shown somewhere.

Closing the Log

The log file closes by itself at shutdown. However, you might want to close the log a little sooner. There is no specialized code in the DiskLog object to close a disk log. However, you might call the close method of the RandomAccessFile class to close a disk log.

ScreenLog

The ScreenLog class is identical to the DiskLog class in output. However, there are a few slight differences. The first variation of the ScreenLog writes standard log lines to the screen. Here is the constructor:

public
ScreenLog( String name )

This takes the name of the application for the log entries as input. Because this log type displays to the screen, there is no need for a disk filename or path. Figure 9.1 shows the output of a ScreenLog.

Figure 9.1 : The output of the ScreenLog class to the screen

The second type of ScreenLog creates a Frame window and displays all of the log entries in it. The constructor for this type takes two input arguments. The first argument is the title to show on the log's window. The second argument is a Boolean value that denotes whether or not to show the window.

Here is that constructor:

public
ScreenLog( String windowTitle, boolean popup )

The output is written to a Frame window. The output is shown in Figure 9.2.

Figure 9.2 : The output of the ScreenLog class to a window.

A Sample Logging Program

The following is the LogTester sample program. It is also included on the CD-ROM.

//****************************************************************************
//* Imports                                         &nb sp;                        *
//****************************************************************************

//    JIF Imports...
import                             jif.util.*;
import                             jif.awt.*;
import                            jif.log.*;

//    Java Imports...
import                            java.io.*;
import                            java.awt.*;

//****************************************************************************
//* LogTester                                         & nbsp;                      *
//****************************************************************************

public class
LogTester
extends Frame
{

//****************************************************************************
//* Members                                         &nb sp;                        *
//****************************************************************************

    ScreenLog                    sLog;
    ScreenLog                    wLog;
    DiskLog                        dLog;

//****************************************************************************
//* main                                                                      *
//****************************************************************************

    public static void
    main( String args[] )
    {
        new LogTester( args );
    }

//****************************************************************************
//* Constructor                                          ;                     *
//****************************************************************************

    public
    LogTester( String args[] )
    {
        super( "Log Tester!" );

        //    Create a screen log...
        sLog = new ScreenLog( "Log Tester", true );
        wLog = new ScreenLog( "Log Tester", false );

        //    Create a disk log...
        try
        {
            dLog = new DiskLog( "Log Tester" );
        }
        catch ( IOException e )
        {
            sLog.log( Log.ERROR, "Could not create the DiskLog object [" +
                e.toString() + "]" );
        }

        //    Tell our disk log that the screen is the backup...
        dLog.setBackupLog( sLog );

        sLog.log( Log.INFO, "Log object created" );
        dLog.log( Log.INFO, "Log object created" );
        wLog.log( Log.INFO, "Log object created" );

        //    Pack the panels...
        pack();

        sLog.log( Log.INFO, "Layout packed" );
        dLog.log( Log.INFO, "Layout packed" );
        wLog.log( Log.INFO, "Layout packed" );

        show();
        resize( 100, 100 );

        sLog.log( Log.INFO, "Running!" );
        dLog.log( Log.INFO, "Running!" );
        wLog.log( Log.INFO, "Running!" );
    }

//****************************************************************************
//* handleEvent                                          ;                     *
//****************************************************************************

    public boolean
    handleEvent( Event anEvent )
    {
        if ( anEvent.id == Event.WINDOW_DESTROY )
        {
            hide();
            dispose();
            sLog.log( Log.INFO, "Exiting!" );
            dLog.log( Log.INFO, "Exiting!" );
            wLog.log( Log.INFO, "Exiting!" );
            System.exit( 0 );
        }

        //    I didn't handle it, pass it up...
        return( super.handleEvent( anEvent ) );
    }

}

The program is quite simple. You create a log of each possible type: a screen log in a window, a screen log that goes to the screen (stdout), and a disk log. Various things are written to each of the logs. The output of this program is shown in Figures 9.1 and 9.2.

Summary

You've come to the end of your second extension discussion. By now you should have a good understanding of what a log is and how it can help you in your programming. You've been introduced to the DiskLog and the ScreenLog classes, and given a little background on how they work.

In Chapter 10, "Database Classes," you will find information about extending Java to communicate with database servers, as well as reading and writing data.