[ Team LiB ] Previous Section Next Section

Recipe 14.6 Using log4j in a JSP

Problem

You want to include logging statements in a JSP.

Solution

Design a custom tag that uses log4j to initiate logging messages.

Discussion

A custom tag is an XML element that you invent or design for use in a JSP. In other words, the JSP container does not provide the custom actions; the web developer himself designs the Java classes that provide the tag functionality. A custom tag or action can be used to implement log4j logging functionality in JSPs.

In this recipe, I show:

  • A Java class that provides the tag handler for a custom tag named cbck:log.

  • A Tag Library Descriptor (TLD) that provides the web application with information about the tag.

  • A JSP page that uses the cbck:log tag.

Example 14-9 shows the Java class LoggerTag on which the cbck:log tag is based. Each custom action is actually driven behind the scenes by one or more Java classes. In this case, LoggerTag is like a JavaBean that wraps the log4j classes, which we import at the top of the tag class.

Custom JSP actions are a complex topic, so I explain this tag by focusing mainly on its log4j features. See Chapter 22 to help fill in the missing spaces in your own knowledge about custom tag development.

Example 14-9. A custom tag that uses log4j
package com.jspservletcookbook;           

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import java.lang.reflect.Method;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

public class LoggerTag extends BodyTagSupport {

    private Logger log = null;
    private String configFile = null;
    private String level = null;
    private static final String[] LEVELS =  
       { "debug","info","warn","error","fatal"};

  public void setConfigFile(String fileName){

      this.configFile = fileName;

  }

  public void setLevel(String level){

      this.level = level;

  }

  public int doEndTag( ) throws JspException {

      String realPath = pageContext.getServletContext( ).getRealPath("/");
      String fileSep = System.getProperty("file.separator");

      if (realPath != null && (!realPath.endsWith(fileSep))){
          realPath = realPath + fileSep;}

      //configure the logger if the user provides this optional attribute
      if (configFile != null)
          PropertyConfigurator.configure(realPath +
              "WEB-INF/classes/" + configFile);

      //throw an exception if the tag user provides an invalid level,
      //something other than DEBUG, INFO, WARN, ERROR, or FATAL

      level = level.toLowerCase( );

      if (! contains(level))
        throw new JspException(
          "The value given for the level attribute is invalid.");
  
      //The logger has the same name as the class:
      //com.jspservletcookbook.LoggerTag. Therefore, it inherits its 
      //appenders from a logger defined in the config file: 
      //com.jspservletcookbook
      log = Logger.getLogger(LoggerTag.class);

      String message = getBodyContent( ).getString( ).trim( );
      Method method = null;

      try{
            
          method = log.getClass( ).
            getMethod(level,new Class[]{ Object.class });

          method.invoke(log,new String[]{message});

    } catch (Exception e){}
    
    return EVAL_PAGE;
  } // doEndTag
     
  public void release( ){ 
     
      //release resources used by instance variables
      log = null;
      configFile = null;
      level = null;

  }// release
     
  private boolean contains(String str){
     
      for (int i = 0; i < LEVELS.length; i++){
         
          if(LEVELS[i].equals(str))
              return true;
      }
      return false;
  }// contains
}

The LoggerTag extends the javax.servlet.jsp.tagext.BodyTagSupport class, which is designed for custom actions that process body content, or the text that may appear between opening and closing tags.

The tag attributes, a required attribute named level and the configFile optional attribute, are handled like JavaBean properties: with "setter" methods (e.g., public void setLevel(String level)). The doEndTag( ) method does most of the important work for the tag:

  1. It attempts to configure the logger if the user has provided a configuration filename in the configFile attribute.

  2. It checks if the level is valid (one of DEBUG, INFO, WARN, ERROR, or FATAL).

  3. It logs the message.

Example 14-10 shows the TLD, which conveys tag specifics to the JSP container, such as whether an attribute is required or optional. The tag library associated with this TLD describes only the cbck:log tag. The TLD files must be located in WEB-INF or a subdirectory thereof, or inside of the META-INF directory of a JAR that is placed in WEB-INF/lib.

Example 14-10. The TLD for the custom logger tag
<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE taglib
        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
    "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
    
    <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>cbck</short-name>
    <uri>jspservletcookbook.com.tags</uri>
    <description>Cookbook custom tags</description>
    
    <tag>
        <name>log</name>
        <tag-class>com.jspservletcookbook.LoggerTag</tag-class>
        <body-content>JSP</body-content>
        <description>This tag uses log4j to log a message.</description>

        <attribute>
            <name>configFile</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <description>
             This attribute provides any configuration filename for the
             logger. The file must be located in 
             WEB-INF/classes.
            </description>
        </attribute>
        
        <attribute>
            <name>level</name>
                    <required>true</required>
                    <rtexprvalue>false</rtexprvalue>
                    <description>This attribute provides the level for the log request.
            </description>
        </attribute>

    </tag>
</taglib>

Example 14-11 shows the logger.jsp page and how the custom action can be used.

Example 14-11. A JSP uses a log custom action to access log4j
<%@page contentType="text/html"%>
<%@ taglib uri="jspservletcookbook.com.tags" prefix="cbck" %>
<html>
<head><title>A logging JSP</title></head>
<body>
<h2>Here is the logging statement</h2>

<cbck:log level="debug">
Debug message from logger.jsp
</cbck:log>

Debug message from logger.jsp
</body>
</html>

First, the page uses the taglib directive to declare the tag library that contains our custom action. Example 14-10 shows the TLD file, an XML file that describes the properties of a tag library's various tags. Chapter 22 describes TLDs in more detail.

The cbck:log custom action allows a developer to log a message from the JSP by nesting the message text within the cbck:log tag (i.e., the body content of the tag is the log message). The cbck part of the tag is the prefix that the taglib directive declared. The log part is the name of the tag. The tag allows the developer to declare the logging level with the custom action's level attribute.

Typically, a component such as an initialization servlet initializes the log4j logging system when the web application starts up. The custom action described here does not have to initialize log4j itself. However, I've included an optional configFile attribute that permits me to specify the name of a log4j configuration file, which will configure the logger's level, appender(s), and layout.


For this tag, assume that you want to decide which logging level to use, and thus pass in a value for the level attribute. The tag class does not know whether the message will request a logging level of DEBUG, INFO, WARN, ERROR, or FATAL. Since the logger's methods in log4j use the same name as the levels, we can dynamically call the proper method based on the value of the level attribute. This is the purpose of the code:

method = log.getClass( ).
   getMethod(level,new Class[]{ Object.class });

method.invoke(log,new String[]{message});

We get a java.lang.reflect.Method object that is named either DEBUG, INFO, WARN, ERROR, or FATAL, and then invoke that method calling method.invoke, passing in the log message from the JSP page.

A configuration filename is not required for this tag, so how does log4j know how and where to log the message? This tag assumes that a servlet has already initialized the log4j system for the web application, which is typical for the use of log4j in a web environment. The configuration file is the one described by Recipe 14.4 and shown in Example 14-5.

You can also use a servlet as an log4j-initialization servlet, similar to Example 14-6.


That configuration file created a logging mechanism that sends messages to the console and a file, so that is where the custom tag's messages go. For example, running logger.jsp displays a message on the console:

DEBUG - Debug message from logger.jsp

The tag's logger writes the following message in the file example.log:

DEBUG Logger:LoggerTag Date: 2003-05-12 12:53:13,750 - Debug message from logger.jsp

If you want to include your own configuration file, you can include a configFile attribute when using the custom tag. The tag will configure the logger using that file instead of any previously initialized one:

if (configFile != null)
    PropertyConfigurator.configure(
      pageContext.getServletContext( ).getRealPath("/") +
        "WEB-INF/classes/" + configFile);

The PropertyConfigurator.configure( ) method allows you to specify the name of a log4j properties file when you initialize the logging system, if the filename is different than log4j.properties. The PropertyConfigurator.configure( ) method (in log4j Version 1.2.8) does not throw an exception that can be caught in the tag class. You could check for the existence of the configFile value (representing the path to a file in the web application) explicitly in the code using the java.io API, and then throw an exception if the configFile attribute declares an invalid filename.


See Also

Recipe 14.2 on downloading and setting up log4j; Recipe 14.3 on using a log4j logger without a properties file; Recipe 14.4 on adding an appender to the root logger; Recipe 14.5 on using a pattern layout with a logger's appender; Recipe 14.7 and Recipe 14.8 on using log4j with application event listeners; the log4j download site: http://jakarta.apache.org/log4j/docs/download.html; the log4j Javadoc page: http://jakarta.apache.org/log4j/docs/api/index.html; the log4j project documentation page: http://jakarta.apache.org/log4j/docs/documentation.html.

    [ Team LiB ] Previous Section Next Section