| [ directory ] |
Logging is the act of keeping a record of important information in some serialized form. Some simple examples of logs include a text file with lines of error messages or information printed to System.err or System.out. Sometimes logged information is designed to be kept for long periods of time. Other times logged information is meant for short-term use to quickly debug a running application. How long to persist, where information is sent, and what information is sent are all important issues to logging. Planning ahead and utilizing a robust logging mechanism helps solve these issues and keeps a project, especially a Web Application, running smoothly.
The Servlet API by default provides a limited and ambiguous method of logging information. The ServletContext object provides two log() methods that log information in a method left implemented by individual container vendors. For application-wide logging, this device is inadequate. With the two log methods, either a String or String with a Throwable object's stack trace can be logged. No attempt is given at distinguishing between different types of information to log, and all logging relies on access to a ServletContext object.
For the purpose of building a practical Web Application, a full-blown logging API is introduced and demonstrated in this section. The logging API is not specifically defined by Servlets or JSP; however, it is a valuable tool for any project including a Web Application.
The far too common mindset is to build a Java application and get by with a rudimentary set of debugging calls. Most commonly, this set of calls consists of using the PrintStream objects System.out or System.err to send temporary information to a terminal. This method of application debugging is simple and works for small projects, but it quickly falls apart in most any real-world situation.
System.out.println() debugging, which is analogous to System.err.println() debugging, is instant gratification. If code does not work, most every Java developer knows inserting a few simple System.out.println() method calls makes something appear somewhere obvious. Given enough System.out.println() calls, it is easy to find a code flaw and get a program in working shape. For this purpose, System.out.println() works perfectly fine; however, a problem occurs when an application needs to constantly log information and will need to log information for the foreseeable future. This information might be trivial statements, such as "made it here", or important information such as runtime listings of system workloads. Far too many times, calls to System.out.println() are used in these cases when they really should not.
The issue to think about is, What will the debugging code do in the future? Inserting System.out.println() calls only works until the calls are no longer needed. Going back and commenting the calls out for performance is not terribly helpful, especially when a bug arises. What happens when more than one developer starts working on the project? If two different developers use completely arbitrary calls to System.out.println(), or equivalents, then each is likely to hinder the other. Without a common logging mechanism it is hard to collaborate. Another very important issue is, What happens when the logging information needs to be piped to a different location? Instead of to a terminal screen, what about a log file, email, or a common repository? Manually fixing countless System.out.println() calls is a complete waste of time, especially if the fix is going to need even more changes later on.
A good and commonly agreed-upon solution to saving debugging information is by means of a simple, yet robust logging API. A dedicated API can abstract the logging process, provide a common interface for multiple developers, enable and disable levels of logged information for performance, and allow easy changes to be made to the logging code in the future. When done properly, the whole logging package can also be as easily implemented same as a few simple System.out.println() statements.
The idea of logging and a dedicated logging API is not a new one. Robust methods of logging have been around for quite awhile and some mature API exist. The most notable of the logging API would have to be Log4j, the logging API for Java. Log4j is currently supported by the Apache community, http://jakarta.apache.org/log4j, and is at version 1.2. Log4j has some fantastic developers working behind it and is a true and tested logging API. In addition to Log4j, the standard Java development kit version 1.4 introduced a new logging API, the java.util.logging package. This J2SDK addition is positioned as the standard Java logging API and provides very similar functionality to Log4j.
In writing this book the choice had to be made between which of these logging API to use and push as a preferred choice. After some careful consideration and thorough use of both APIs the choice was made to use the java.util.logging package. The truth of the matter is that both packages do a more than sufficient job as a logging API. Log4j does have a slight advantage in terms of previous testing and widespread community use, but the java.util.logging package is already included in the default Java 1.4 download and will be around in future distributions.
The java.util.logging package is included with every Java SDK 1.4 distribution. No extra installation is required to use the API with this book's examples. If the code does not work, you probably did not follow the installation steps in Chapter 1. Make sure you are using the Java 2 Standard Development Kit 1.4 release or a later version.
The java.util.logging package can be generalized into two parts, Logger and Handler objects. A logger object is responsible for logging information to one or more Handler objects. Handler objects are responsible for customizing where and how information is logged. Using the java.util.logging package is as simple as creating a instance of a Logger object, registering one or more Handler objects, and logging information as needed.
For common logging, such as to terminals or files, the java.util.logging package includes everything that is needed to implement a simple logging system. In more complex cases the same functionality is used; however, custom subclasses of Logger and Handler objects might be required. Overall, the process of logging is always the same. As a quick introduction to the simplicity of the API, it is helpful to see a concrete code example. Listing 4-22 provides an example of implementing a Logger object that sends information to System.err.
<%@ page import="java.util.logging.*"%>
<%
Logger logger = Logger.getLogger("example");
logger.addHandler(new ConsoleHandler());
String info = request.getParameter("info");
if (info != null && !info.equals("")) {
logger.info(info);
}
%>
<html>
<head>
<title>A Simple Logger</title>
</head>
<body>
<form>
Information to log:<input name="info"><br>
<input type="submit">
</form>
</body>
</html>
Save Logger.jsp in the base directory of the jspbook Web Application and browse to http://127.0.0.1/jspbook/Logger.jsp. A small form is displayed with one input box. Anything typed in the box is logged upon submission of the form. Figure 4-11 shows a browser rendering of the HTML form.

Information posted by the form is logged by the server to System.err. This can be verified by checking the location of System.err. By default, Tomcat saves all information sent to System.err in the catalina.out file located in the /logs directory of installation. Open up this file to see the logged information. For example, if "log test" was submitted via the HTML form, then the following line would be appended to catalina.out.
Mar 18, 2002 8:30:11 PM org.apache.jsp.Logger$jsp _jspService INFO: log test
For the time being, disregard the extra information included with the entry. The point of this example is to illustrate a quick and easy use of the java.util.logging package. Achieving a simple default-formatted log entry requires a few lines of code. Logger.jsp demonstrates this with the following two lines.
Logger logger = Logger.getLogger("example");
logger.addHandler(new ConsoleHandler());
After the Logger object was created and a Handler added, the system was ready to log information. There are many ways to log information, but in the example, the convenient info() method was used.
logger.info(info);
Subsequent calls to log information could also be included as desired; however, in this example, none were needed. The main purpose of the example was to introduce the general use of Logger and Handler objects. The java.util.logging package is easy to use and can be implemented with just a few lines of code. Discussion now expands to using the individual parts of the java.util.logging package for custom logging.
Handlers are responsible for handling information that needs to be logged. Should the information go to a terminal screen, flat file, or any other resource, it is the responsibility of a Handler to make sure it gets there. Information published to a Handler is represented by a java.util.logging.LogRecord object. A LogRecord object includes information to log, where the information came from, how important the information is, and a time-stamp. How to appropriately style and present this information is the responsibility of a java.util.logging.Formatter object. Each Handler object has a Formatter object associated with it to appropriately style published LogRecords.
A few Handler objects are included with the java.util.logging package:
StreamHandler: A StreamHandler object represents a Handler designed to export logged information to a java.io.OutputStream. The StreamHandler object includes a constructor that takes an OutputStream and Formatter object as parameters and uses them to format and stream logged information.
MemoryHandler: The MemoryHandler object provides a cheap way to keep a set of LogRecord objects in memory. After established amounts of LogRecords are buffered, they are all published to an Handler object for appropriate handling. The MemoryHandler object is best used when it is expensive to constantly publish individual records, perhaps if a connection needs to be opened and closed during the logging of each record. Consolidating a set of logs during one connection can be much more efficient.
The MemoryHandler object provides a constructor that takes as arguments a Handler, int value for a buffer size, and a push level. The Handler object is the Handler buffered LogRecord objects are published to, the int represents how many LogRecord objects to buffer, and the push level allows for important messages to cause the buffer to automatically flush.
SocketHandler: A SocketHandler is a convenient method for logging information using a network socket. By default the SocketHandler formats logged information in an XML-compatible format. The SocketHandler object provides a constructor that takes as an argument a String representing a host URL and an int representing the port to use. The Handler automatically opens a java.net.Socket to the given URL on the specified port for logging information.
FileHandler: A FileHandler object is a convenient Handler for logging information to a local file. The easiest use of the FileHandler object is to call the constructor providing a String that represents the file to use for logging information. More complex uses also exist for using the FileHandler object to automatically rotate logs between multiple files after a certain space limit has been reached.
The existing handlers in the J2SDK represent some of the most commonly used resources for saving logged information. Building an appropriate Handler object for most cases is nothing more than using one that already exists. Using all the aforementioned convenience Handler objects is not fully demonstrated by this book. The code is intuitive to use and is mentioned only to give an idea of what comes bundled with the java.util.logging package.
The style in which a Handler object exports information is completely configurable. Every Handler object relies on a Formatter object to convert a LogRecord into an appropriate String of information to log. The Formatter object being used to style a specific Handler instance can be obtained or set using the getFormatter() or setFormatter() methods.
To code a custom Formatter object, extend the Formatter class and override the relevant methods. There are four possible methods of interest:
format(LogRecord record): The format() method is invoked by a Logger class when publishing information to be logged. Passed as a parameter is the LogRecord object describing the information to log. The format() method returns a String representing the final format of the information to log.
formatMessage(LogRecord): The formatMessage() method is a convenience method that can be invoked by the format() method to localize a message using a resource bundle. Resource bundles are further explained in Chapter 12.
getHeader(Handler handler): The getHeader() method returns a header that should be used to surround a set of formatted log records. This method returns an empty String by default. In cases where a header must be included, such as the start of a parent XML element, this method can be overridden to produce the correct String.
getTail(Handler handler): The getTail() method works much like the getHeader() method but is used to return a tail to be placed at the end of a set of log records. This method returns an empty String by default. In cases where a tail must be included, this method can be overridden to produce the correct text.
There are two Formatting objects included with the java.util.logging package: SimpleFormatter and XMLFormatter. The SimpleFormatter object takes an instance of a LogRecord and converts it into a human-readable string. The string is usually one or two lines long and resembles the entries seen with the Logger.jsp example, Listing 4-22. The XMLFormatter object takes a LogRecord and formats it into an XML format.
For most purposes the SimpleFormatter object does an adequate job of logging information. Text returned by the SimpleFormatter object's format() method includes a time-stamp, the class sending the information to log, and the information to log. However, for the jspbook Web Application, a simple custom Formatter object will be created for both example purposes and use throughout the book. Save the code in Listing 4-23 as CustomLogger.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application.
package com.jspbook;
import java.io.*;
import java.util.*;
import java.util.logging.*;
public class CustomFormatter extends Formatter {
public String format(LogRecord log) {
Date date = new Date(log.getMillis());
String level = log.getLevel().getName();
String string = "[" + level + " " +date.toString() + "]\n";
string += log.getMessage() + "\n\n";
Throwable thrown = log.getThrown();
if (thrown != null) {
string += thrown.toString();
}
return string;
}
}
The preceding custom formatter is designed to work similar to the SimpleFormatter object but makes the styled result a little easier to read. The String returned by invoking the format() method includes a bracketed time-stamp followed by the logged message and an optional exception stack-trace. The general format is the following:
[time-stamp] message stack-trace
The brackets are used to easily distinguish separate log entries while the message and stack-trace provide information about what went wrong with the Web Application. Before actually using the CustomFormatter class, a custom Logger class needs to also be created for use with the jspbook Web Application.
A Logger object is used to log messages for a specific system of application components. Loggers are designed to be flexible and include features such as local-specific logging and logging information according to levels of importance. Logger objects are managed by a LogManager object that is responsible for keeping and configuring a collection of loggers.
Not all of the features of Logger and LogManager classes are covered in this book. The flexibility of these classes is extensive and not commonly needed. What is covered in this section is the basic use of the Logger class with JSP and Servlets. This includes customizing a logger for use in a Web Application and taking advantage of the various levels of logged information. International use of the Logger class for local-specific logs is covered in Chapter 12. Topics outside this scope are left for publications that focus more deeply on the java.util.logging package.
All information logged through an instance of a Logger object is associated with a specific level. Levels are used to efficiently manage different types of logged information. A level may be arbitrarily assigned but is intended to give some information about the nature of the information to log. Handler and Logger objects can selectively log only certain levels of information, making it practical to channel different types of information to desired locations.
The level scheme the java.util.logging package uses is defined by the java.util.logging.Level object. The levels are listed, in descending order of importance, as follows:
SEVERE: The SEVERE level is the highest level of importance. A SEVERE level represents a severe, or critical, message that is to be logged. Often the SEVERE Level is associated with a thrown exception.
WARNING: The WARNING level is the second highest level of importance. A WARNING level association represents a message to be logged that includes a warning. Warnings are important to signal a future possibility of a severe problem but are not severe problems themselves.
INFO: The INFO level represents an informative message to be logged. The message is less important than a warning or severe level message and is most helpful when debugging an application. The INFO level is commonly associated with the casual use of System.out.println() statements.
CONFIG: The CONFIG level represents configuration information being echoed back by an application. Messages at the CONFIG level are less important than informative messages and are meant for helping debug an application.
FINE: The FINE level represents a message that falls in none of the previous categories but is more important than a FINER or FINEST level message.
FINER: The FINER level represents a message less important than a FINE message but more important than a FINEST message.
FINEST: The FINEST level represents a message that is less important than all the other messages.
OFF: A level of OFF has the effect of turning off logging on either a Handler or Logger object. This level can be used when no logging is desired and absolute performance is the goal. A Logger or Handler object set to the OFF level immediately returns when information logging is attempted.
ALL: The ALL level setting logs all levels of messages.
Complementing these levels, the Logger object defines the following methods:
getLevel(): The getLevel() method returns a level object representing the current level the Logger is set to log messages. Messages of higher or equal priority to the level are logged.
setLevel(Level level): The setLevel() method sets the current level a Logger object should log messages at. Messages below the level are discarded. When the OFF level is specified, all messages are ignored.
log(Level level, String message): The log() method logs a given message at a given level. Should the message be below the current Logger object's level setting, it is discarded. For all of the allowed levels, self-named convenience methods also exist: severe(), warning(), info(), config(), fine(), finer(), and finest(). All of the convenience methods take as a parameter a String representing the message to log. Levels are implied.
log(Level level, String message, Throwable throwable): The log() method logs a given message at a given level. The LogRecord object published by this method also includes the Throwable object specified as the throwable parameter.
Using the preceding methods a Logger object can effectively log information based on arbitrary levels. The common use of these levels is to safely log critical information at all times while keeping warning and debugging information around only when developers need it. The performance difference between logging all information versus only the important information can be noticeable and it often makes sense to distinguish between the two.
Fine, Finer, Finest, and AllOne of the most criticized features of the java.util.logging package is the inclusion of the fine, finer, finest and all levels. These levels are commonly regarded as redundant versions of the info level. If a situation requires use of fine, finer, and finest logging, there is nothing wrong with these methods, but do not feel obliged to use them. |
A nice feature of the java.util.logging package is that it can be used to log information by any class. Compared to the ServletContext log() methods, this functionality is quite nice, but it does not mean it is beneficial to completely abstract logging away from Servlets and JSP. A logging mechanism designed to work with a Web Application should take full advantage of that Web Application. Remember the reasons leading to the introduction of the java.util.logging package. Normal logging schemes are usually inadequate; using System.out.println() method calls becomes inefficient as a project grows and is not easily maintained in the future. Using ServletContext object's log() methods is very limiting and ambiguous. A robust logging API such as the java.util.logging package solves these problems. However, the java.util.logging package is not designed to be the final solution to an application's logging needs. The framework is extensible and much can be gained from using the package to most effectively suit individual needs.
In the case of a Web Application the java.util.logging package can be combined with the functionality of JSP and Servlets. From this combination a site can have a robust logging mechanism that easily ports and is configurable by the same methods as the rest of the Web Application. A great use of combining these two APIs is a custom logging ServletContextListener that can initialize with the Web Application and a JSP that can configure the logging mechanism as well as provide a Web interface for administrative use. Listing 4-24 is the ServletContextListener class.
package com.jspbook;
import java.io.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SiteLogger implements ServletContextListener {
// Site Logger
private static Logger logger;
public static Logger getLogger() throws IOException {
return logger;
}
public void contextInitialized(ServletContextEvent e) {
ServletContext sc = e.getServletContext();
// Get an instance of a Logger
logger = Logger.getLogger("global");
logger.setLevel(Level.INFO);
try {
FileHandler fh = null;
String root = sc.getRealPath("/");
fh = new FileHandler(root+"WEB-INF/log.txt");
fh.setFormatter(new CustomFormatter());
logger.addHandler(fh);
} catch (IOException ee) {
System.err.println("Can't load logger: " +ee.getMessage());
}
sc.setAttribute("com.jspbook.SiteLogger", logger);
}
public void contextDestroyed(ServletContextEvent e) {
ServletContext sc = e.getServletContext();
sc.removeAttribute("com.jspbook.SiteLogger");
logger = null;
}
}
Save SiteLogger.java in the /WEB-INF/classes/com/jspbook directory of the jspbook Web Application. Deploy the listener by adding an entry in web.xml, as shown in Listing 4-25.
...
<listener>
<listener-class>com.jspbook.SiteLogger</listener-class>
</listener>
...
Compile SiteLogger.java and reload the Web Application. The SiteLogger class now functions as a general-purpose logging class. Any other class in the Web Application may invoke the static getLogger() method to obtain an instance of the Web Application's new logging mechanism.
For the listener to be of much help it must be used by other code in the Web application. First, let us create an administrative JSP for modifying the logger during runtime, as shown in Listing 4-26. The page will provide a method of setting the logger's current level and viewing the current log.
<%@ page import="java.util.logging.*, java.io.*"%>
<%
// get logger
Logger logger = com.jspbook.SiteLogger.getLogger();
// get request parameters
String level = request.getParameter("level");
if (level != null && !level.equals("")) {
logger.setLevel(Level.parse(level));
}
// set current level
request.setAttribute("l", logger.getLevel());
// set current log
StringWriter sw = new StringWriter();
request.setAttribute("log", sw);
// parse in current log
InputStream is =
application.getResourceAsStream("/WEB-INF/log.txt");
if (is != null) {
for (int i = is.read();i!=-1;i=is.read()) {
sw.write((char)i);
}
} else {
sw.write("Can't load log file!");
}
%>
<html>
<head>
<title>Site Logging Configuration</title>
</head>
<body>
<h3>Set the site's logging level:</h3>
<b>Current level:</b> ${l}<br><br>
<form>
<select name="level">
<option value="SEVERE">Severe</option>
<option value="WARNING">Warning</option>
<option value="INFO">Info</option>
<option value="CONFIG">Config</option>
</select><br>
<input type="submit" value="Update Level"><br>
</form>
<h3>Current Log:</h3>
<pre>${log}</pre>
</body>
</html>
Save the preceding code as SiteLoggerAdmin.jsp in the root directory of the jspbook Web Application. The code provides an HTML form for changing the current level of the logger (only between severe, warning, info, and config), and the page displays the current log. You can test the page by browsing to http://127.0.0.1/jspbook/SiteLoggerAdmin.jsp; however, there is currently little to see. Nothing currently logs information via the SiteLogger class.
Save the following JSP as SiteLogger.jsp in the root directory of the jspbook Web Application. The code provides example code for how the SiteLogger class can be used to log information. An HTML form passes information to be logged and the level of the information. The JSP then takes this information, obtains a reference to the Logger class, and logs the information using the appropriate level.
<%@ page import="java.util.logging.*"%>
<%
// get logger
Logger logger = com.jspbook.SiteLogger.getLogger();
// get required request parameters
String info = request.getParameter("info");
String level = request.getParameter("level");
// log information appropriately
if (info != null && !info.equals("") &&
level != null && !level.equals("")) {
logger.log(Level.parse(level), info);
}
%>
<html>
<head>
<title>A Simple Logger</title>
</head>
<body>
<form>
<table>
<tr>
<td>Level:</td>
<td>
<select name="level">
<option value="SEVERE">Severe</option>
<option value="WARNING">Warning</option>
<option value="INFO">Information</option>
<option value="CONFIG">Configuration</option>
</select>
</td>
</tr>
<tr>
<td>Information to log:</td>
<td><input name="info"><br></td>
</tr>
</table>
<input type="submit">
</form>
<a href="SiteLogger">View/Configure Log</a>
</body>
</html>
The HTML form is of little interest, it simply queries a string to log and a level. Of interest is the general code for using the SiteLogger class's getLogger() method and logging information.
<% // get logger Logger logger = com.jspbook.SiteLogger.getLogger(); // get required request parameters String info = request.getParameter("info"); String level = request.getParameter("level"); // log information appropriately if (info != null && !info.equals("") && level != null && !level.equals("")) { logger.log(Level.parse(level), info); } %>
Highlighted are the relevant lines, and the code is unsurprisingly straightforward. First, the getLogger() method is invoked to obtain an instance of the previously initialized Logger class. Next the Logger class is used same as any other java.util.logging.Logger class.
Test out the new functionality by browsing to http://127.0.0.1/jspbook/SiteLogger.jsp. An HTML form appears, allowing you to log information at arbitrary levels. Figure 4-12 provides a browser rendering of the form.

Try submitting several different messages using various levels. Next, view the information by browsing to http://127.0.0.1/jspbook/SiteLoggerAdmin.jsp. If you like, use SiteLoggerAdmin.jsp to set the level of the application-wide Logger class and notice how messages logged below this level are ignored. By default the SiteLogger class is set at info level.
All-around, the use of the SiteLogger class should be intuitiveeven trivialand that is exactly the point. Instead of relying on the default Servlet logging mechanism, a more robust solution can easily be implemented using a simple ServletContextListener class. At Web Application initialization, your favorite logging API, in this case java.util.logging, can be initialized for use. During runtime, a static method provides easy access to the logging functionality for all of your other classes. Using a true logging API to handle Web Application logging is not difficult, and it is something you should use in most every Web Application.
| [ directory ] |