Skip to main content

Magic with Merlin: Exceptions and logging

Root out your problems and respond appropriately with these useful changes to the J2SE

John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.
Author photo
John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and serves as the resident guru for a number of jGuru's community-driven Java FAQs. His latest books are Java Collections and Definitive Guide to Swing for Java 2 (2nd ed) from Apress. Reach him at jaz@zukowski.net.

Summary:  The Merlin release adds several exception-handling-related features for understanding the root cause of a problem and responding appropriately. You can now examine a stack trace without having to manually parse the stack dump, and you can daisy chain exceptions, which allows you to attach the cause of an exception when you rethrow it, greatly enhancing debugging. Also, there is now a built-in logging facility to log different levels of messages. In this installment of Magic with Merlin, John Zukowski demonstrates how these new logging and exception features work and provides an example program for review and download.

View more content in this series

Date:  01 Dec 2001
Level:  Introductory
Activity:  2296 views

Many of the features new to the Merlin release, like exception handling and logging, aren't as visible or exciting as some others, but they are useful and merit our attention. All Java developers should be familiar with the basic structure for performing exception handling: you place the code that might throw an exception within a try block and then have catch clauses at the end of the block to handle if and when exceptions do get thrown within the block. This basic structure doesn't change with the Merlin release. What's new in 1.4 is that if you rethrow an exception from the catch clause, you can attach the original cause of the exception. A real coup for debugging! And, if you want to log where the exception happened, you don't have to manually parse the stack trace. There's now support for programmatically accessing the stack trace data and a Logging API for logging that data (or anything else).

Here's a list of the new features we'll cover this month:

  • The chained exception facility
  • Programmatic access to the stack trace information
  • The Logging API

Getting started

The basic program in Listing 1 contains three methods that can all throw an exception. Each exceptional condition is handled by displaying a message. In the first case, the exception is rethrown for a second message for that problem to be displayed.


Listing 1. Methods that throw an exception


import java.io.*;

public class Exceptions {

  private static void fileAccess() throws IOException {
    // Fails because prefix is too short
    File f = File.createTempFile("x", "y");
  }

  private static void divZero() {
    System.out.println(1/0);
  }

  private static void arrayAccess(String array[]) {
    System.out.println("First: " + array[0]);
  }

  public static void main(String args[]) {
    try {
      try {
        fileAccess();
      } catch (Exception e) {
        System.err.println("Prefix too short");
        throw e;
      }
    } catch (Exception cause) {
      System.err.println("Cause: " + cause);
    }
    try {
      divZero();
    } catch (Exception e) {
      System.err.println("Division by Zero");
      e.printStackTrace();
    }
    try {
      arrayAccess(args);
    } catch (Exception e) {
      System.err.println("No command line args");
    }
  }
}


Chained exceptions

A chained exception is one that allows you to attach a "causative" exception to the exception being thrown. In essence, you are creating a daisy chain of exceptions. For instance, while throwing (and catching) your custom exception, you can say that the cause of your exception is an I/O exception. Support for chained exceptions starts with the java.lang.Throwable class. Now, instead of having only two constructors, one of the no-arg variety and one accepting a detail message, there are four:

  • Throwable()
  • Throwable(String message)
  • Throwable(Throwable cause)
  • Throwable(String message, Throwable cause)

When creating your own exception types, you should add the extra two constructors also. That way, you can easily pass along the cause of an exception when it is created. Even if you don't change your Exception subclasses, you can still chain them. This approach simply requires that you call the initCause(Throwable cause) method of your subclass.

To demonstrate chaining, Listing 2 should replace the first two try blocks in the Exceptions class. It defines a custom exception class, attaching the cause, and throws it instead of the original exception for the fileAccess() exception handling:


Listing 2. Custom exception class

try {
  try {
    fileAccess();
  } catch (Exception e) {
    class TheException extends Exception {
      public TheException() {
      }
      public TheException(String message) {
        super(message);
      }
      public TheException(Throwable throwable) {
        super(throwable);
      }
      public TheException(String message, Throwable throwable) {
        super(message, throwable);
      }
    }
    TheException theExc = new TheException("Prefix too short", e);
    throw theExc;
  }
} catch (Exception cause) {
  System.err.println("Cause: " + cause);
  System.err.println("OriginalCause: " + cause.getCause());
}


Stack access

Now we'll add a bit of complexity by accessing the stack trace of an exception. As demonstrated in the second method call in Listing 2, you can call printStackTrace() to display a stack dump of the invocation sequence that led to the line that threw the exception. The printStackTrace() method can accept either a PrintStream or a PrintWriter as an argument, and will send output to System.err if no destination is provided.

If instead of dumping the stack trace in the default format you want to display it in your own format, you can call the getStackTrace() method, which returns an array of StackTraceElement objects. You can find out many different features of each element:

  • getClassName()
  • getFileName()
  • getLineNumber()
  • getMethodName()
  • isNativeMethod()

By calling the various methods of each element, you can display the stack dump in any format you'd like. Replacing the printStackTrace() call with Listing 3 will cause the program to display the filename, line number, and method name for each stack element.


Listing 3.

StackTraceElement elements[] = e.getStackTrace();
for (int i=0, n=elements.length; i<n; i++) {
  System.err.println(elements[i].getFileName() + ":" + 
    elements[i].getLineNumber() + " ==> " +
    elements[i].getMethodName()+"()");
}

One thing to note: the first element of the arrray is the top of the call trace, not the last.


Logging

Instead of sending the stack trace to System.err, you can use the Java platform's logging facility found in the new java.util.logging package. While there are many configuration options available through XML and filtering, the basic structure requires getting a Logger object and logging methods with the generic log(Level level, String message) method, or calling a method for the specific log level, like fine(). There are seven different levels plus two to indicate all or none:

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST
  • ALL
  • NONE

Listing 4 adds logging to the third try block, logging just the method name for each part of the stack trace.


Listing 4. Adding logging

System.err.println("No command line args");
Logger logger = Logger.getLogger("net.zukowski.ibm");
StackTraceElement elements[] = e.getStackTrace();
for (int i=0, n=elements.length; i<n; i++) {
  logger.log(Level.WARNING, elements[i].getMethodName());
}

By default, logging messages are sent to the console. You can add logging to a file by attaching a Handler to the LogManager, as shown in Listing 5.


Listing 5. Attaching a Handler

try {
  Handler handler = new FileHandler("zuk.log");
  Logger.getLogger("").addHandler(handler);

  // log it
      
} catch (IOException logException) {
  System.err.println("Logging error");
}

While the console output is not in any easily parsable format, the file output is stored as an XML document. Listing 6 shows what that output might look like for this example.


Listing 6. File output

<?xml version="1.0" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2001-10-30T16:24:23</date>
  <millis>1004563463843</millis>
  <sequence>0</sequence>
  <logger>net.zukowski.ibm</logger>
  <level>WARNING</level>
  <class>Exceptions</class>
  <method>main</method>
  <thread>10</thread>
  <message>arrayAccess</message>
</record>
<record>
  <date>2001-10-30T16:24:24</date>
  <millis>1004563464015</millis>
  <sequence>1</sequence>
  <logger>net.zukowski.ibm</logger>
  <level>WARNING</level>
  <class>Exceptions</class>
  <method>main</method>
  <thread>10</thread>
  <message>main</message>
</record>
</log>


Complete example

Listing 7 provides a complete example for you to try out these new capabilities.


Listing 7. Complete example

import java.io.*;
import java.util.logging.*;

public class Exceptions {

  private static void fileAccess() throws IOException {
    // Fails because prefix is too short
    File f = File.createTempFile("x", "y");
  }

  private static void divZero() {
    System.out.println(1/0);
  }

  private static void arrayAccess(String array[]) {
    System.out.println("First: " + array[0]);
  }

  public static void main(String args[]) {
    try {
      try {
        fileAccess();
      } catch (Exception e) {
        class TheException extends Exception {
          public TheException() {
          }
          public TheException(String message) {
            super(message);
          }
          public TheException(Throwable throwable) {
            super(throwable);
          }
          public TheException(String message, Throwable throwable) {
            super(message, throwable);
          }
        }
        TheException theExc = new TheException("Prefix too short", e);
        throw theExc;
      }
    } catch (Exception cause) {
      System.err.println("Cause: " + cause);
      System.err.println("OriginalCause: " + cause.getCause());
    }
    try {
      divZero();
    } catch (Exception e) {
      System.err.println("Division by Zero");
      StackTraceElement elements[] = e.getStackTrace();
      for (int i=0, n=elements.length; i<n; i++) {
        System.err.println(elements[i].getFileName() + ":" + 
          elements[i].getLineNumber() + " ==> " +
          elements[i].getMethodName()+"()");
      }
    }
    try {
      arrayAccess(args);
    } catch (Exception e) {
      System.err.println("No command line args");

      try {
         Handler handler = new FileHandler("zuk.log");
         Logger.getLogger("").addHandler(handler);
    
        Logger logger = Logger.getLogger("net.zukowski.ibm");
        StackTraceElement elements[] = e.getStackTrace();
        for (int i=0, n=elements.length; i<n; i++) {
          logger.log(Level.WARNING, elements[i].getMethodName());
        }
      } catch (IOException logException) {
        System.err.println("Logging error");
      }
    }
  }
}


Resources

About the author

Author photo

John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and serves as the resident guru for a number of jGuru's community-driven Java FAQs. His latest books are Java Collections and Definitive Guide to Swing for Java 2 (2nd ed) from Apress. Reach him at jaz@zukowski.net.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10618
ArticleTitle=Magic with Merlin: Exceptions and logging
publish-date=12012001
author1-email=jaz@zukowski.net
author1-email-cc=jaloi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers