Chapter 18

Exception Handling

In this chapter:

1.      You will learn about basics of Exception Handling 

2.      You will understand the exception handling from servlet specification perspective

3.      You will understand exception handling facilities in Struts1.1

4.      We will develop a simple yet robust utility to log exceptions

5.      We will cover strategies to centralize logging in production environments

 

Exception handling is very crucial part often overlooked in web application development that has ramifications far beyond deployment. You know how to handle exceptions using the built-in Java construct to catch one and handle it appropriately. But what is appropriate? The basic rationale behind exception handling is to catch errors and report them. What is the level of detail needed in reporting the exception? How should the user be notified of the exception? How should customer support handle problem reports and track and trace the exception from the logs? As a developer where do you handle the exceptions? These are some of the major questions we will answer in this chapter first from a generic way and then as applicable to Struts applications.

Under normal circumstances when you catch the exception in a method, you print the stack trace using the printStacktrace() method or declare the method to throw the exception. In a production system, when an exception is thrown it's likely that the system is unable to process end user’s request. When such an exception occurs, the end user normally expects the following:

§  A message indicating that an error has occurred

§  A unique error identifier that he can use while reporting it to customer support.

§  Quick resolution of the problem.

The customer support should have access to back-end mechanisms to resolve the problem. The customer service team should, for example, receive immediate error notification, so that the service representative is aware of the problem before the customer calls for resolution. Furthermore, the service representative should be able to use the unique error identifier (reported by the user) to lookup the production log files for quick identification of the problem – preferably up to the exact line number (or at least the exact method). In order to provide both the end user and the support team with the tools and services they need, you as a developer must have a clear picture, as you are building a system, of everything that can go wrong with it once it is deployed.

18.1 Exception Handling Basics

It is common usage by the developers to put System.out.println() to track the exception and flow through the code. While they come in handy, they have to be avoided due to the following reasons:

 

1.      System.out.println is expensive. These calls are synchronized for the duration of disk I/O, which significantly slows throughput.

2.      By default, stack traces are logged to the console. But browsing the console for an exception trace isn't feasible in a production system.

3.      In addition, they aren't guaranteed to show up in the production system, because system administrators can map System.out and System.errs to ' ' [>nul] on NT and dev/nul on UNIX. Moreover, if you're running the J2EE app server as an NT service, you won't even have a console.

4.      Even if you redirect the console log to an output file, chances are that the file will be overwritten when the production J2EE app servers are restarted.

5.      Using System.out.println during testing and then removing them before production isn't an elegant solution either, because doing so means your production code will not function the same as your test code.

 

What you need is a mechanism to declaratively control logging so that your test code and your production code are the same, and performance overhead incurred in production is minimal when logging is declaratively turned off. The obvious solution here is to use a logging utility. It is pretty customary these days to use a utility like Log4J (http://jakarta.apache.org/log4j) for logging. With the right coding conventions in place, a logging utility will pretty much take care of recording any type of messages, whether a system error or some warning. However it is up to you as a developer to make the best use of the utilities. It requires a lot of forethought to handle exceptions effectively. In this chapter we will use Log4J to log exceptions effectively. Hence we will review Log4J before proceeding to look at some commonly accepted principles of Exception handling in Java.

18.2 Log4J crash course

Log4J is the logging implementation available from Apache’s Jakarta project and has been around long before JDK Logging appeared and quite naturally has a larger developer base. Lot of material is freely available online if you want to dig deeper into Log4J and we have held back from such a detailed treatment here. As with any Logging mechanisms, this library provides powerful capabilities to declaratively control logging and the level of logging.

In Log4J, all the logging occurs through the Logger class in org.apache.log4j package. The Logger class supports five levels for logging. They are FATAL, ERROR, WARNING, INFO, DEBUG. Without Log4J, you would perhaps use a Boolean flag to control the logging. With such a boolean flag, there are only two states – logging or no logging. In Log4J the levels are defined to fine tune the amount of logging. Here is how you would user the Log4J.

 

           Logger logger = Logger.getLogger (“foo.bar”);

           logger.debug (“This is a debug message”);

 

The code above first obtains the Logger instance named foo.bar and logs a message at DEBUG level. You can declaratively turn off the logging for messages at lower level than WARNING. This means the messages logged at INFO and DEBUG level will not be logged.

Logged messages always end up in a destination like file, database table etc. The destination of the log message is specified using the Appender. The Appender can represent a file, console, email address or as exotic as a JMS channel. If you need a destination that is not supported by the classes out of the box you can write a new class that implements the Appender interface. Appenders can be configured at startup in a variety of ways. One way to configure them is through an XML file. A XML file is shown below.

 

<appender name="Mybank-Warn"

         class="org.apache.log4j.FileAppender">

  <param name="Threshold" value="WARN" />

  <param name="File"   value="./logs/mybank-warnings.log" />

  <param name="Append" value="false" />

  <layout class="org.apache.log4j.PatternLayout">

     <param name="ConversionPattern"

            value="%d [%x][%t] %-5p %c{2} - %m%n"/>

  </layout>

</appender>

 

<category name="foo.bar" additivity="false">

  <appender-ref ref="Mybank-Warn" />

  <appender-ref ref="Developer-Console" />

</category>

 

The above XML when translated to simple English reads as follows: The Appender named Mybank-Warn logs the messages to a file mybank-warnings.log. Only messages with a threshold of WARN or higher are logged. The format of the message is as specified by the PatternLayout.

The format of the output message is specified using Layout. Standard classes for specifying the layout like PatternLayout are used most of the times and the format is declaratively specified using symbols like %d which instructs Log4J to include date time in the log and %m – the actual message itself and so on. 

As you saw earlier, the logging is performed through a named Logger instance. If you are wondering how the Logger would know which Appender to log to, it is the <category> element in the above XML that provides the link between the two. The Logger uses the <category> setting in the XML to get this information. The <category> in the above XML is called foo.bar. Recall that we tried to log using a Logger named foo.bar. The foo.bar Logger gets the FileAppender Mybank-Warn  appender through the foo.bar category setting in the XML. And then the messages end up in the file mybank-warnings.log.

There can be more than one appenders associated with a category. This implies that the messages logged with a Logger can potentially end up in multiple locations if needed.

18.3 Principles of Exception Handling

The following are some of the generally accepted principles of exception handling:

6.      If you can't handle an exception, don't catch it.

7.      Catch an exception as close as possible to its source.

8.      If you catch an exception, don't swallow it.

9.      Log an exception where you catch it, unless you plan to re-throw it.

10.   Preserve the stack trace when you re-throw the exception by wrapping the original exception in the new one.

11.   Use as many typed exceptions as you need, particularly for application exceptions. Do not just use java.lang.Exception every time you need to declare a throws clause. By fine graining the throws clause, it is self-documenting and becomes evident to the caller that different exceptions have to be handled.

12.   If you programming application logic, use unchecked exceptions to indicate an error from which the user cannot recover. If you are creating third party libraries to be used by other developers, use checked exceptions for unrecoverable errors too.

13.   Never throw unchecked exceptions in your methods just because it clutters the method signature. There are some scenarios where this is good (For e.g. EJB Interface/Implementations, where unchecked exceptions alter the bean behavior in terms of transac