 | Level: Introductory John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.
01 Dec 2001 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. 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.
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:
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.
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.
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.
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.
<?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.
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
Rate this page
|  |