站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Servlets and JavaServer Pages: The J2EE Technology Web Tier

Servlets and JavaServer Pages: The J2EE Technology Web Tier

[ directory ]Web Application Exception Handling Logging and Performance

Logging

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 Problem with System.out.println()

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.

JDK 1.4 Logging Versus Log4j

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.

Using the java.util.logging Package

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.

Listing 4-22. Logger.jsp
<%@ 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.

Figure 4-11. Browser Rendering of Logger.jsp

graphics/04fig11.gif


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

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:

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.

Formatting

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:

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.

Listing 4-23. CustomFormatter.java
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.

Loggers

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.

Levels of Logged Information

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:

Complementing these levels, the Logger object defines the following methods:

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 All

One 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.


Custom Web Application Logging

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.

Listing 4-24. SiteLogger.java
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.

Listing 4-25. SiteLogger web.xml Listener Deployment
...
  <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.

Listing 4-26. SiteLoggerAdmin.jsp
<%@ 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.

Listing 4-27. SiteLogger.jsp
<%@ 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.

Figure 4-12. Browser Rendering of SiteLogger.jsp

graphics/04fig12.gif


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 ]Web Application Exception Handling Logging and Performance