You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-cvs@jakarta.apache.org by ce...@apache.org on 2001/01/09 16:20:57 UTC

cvs commit: jakarta-log4j/doc deepExtension.html

ceki        01/01/09 07:20:55

  Added:       doc      deepExtension.html
  Log:
  Added Paul Glezen's "Adding Conversion Characters to PatternLayout" document to the std distrib.
  
  Submitted by: Paul Glezen
  
  Revision  Changes    Path
  1.1                  jakarta-log4j/doc/deepExtension.html
  
  Index: deepExtension.html
  ===================================================================
  <html>
  <head>
  <title>log4j extensions</title>
  </head>
  
  <body bgcolor="white">
  
  <center>
  <h1>Adding Conversion Characters to PatternLayout</h1>
  
    "Paul Glezen" 
  
    January 2000 <br><br>
  </center>
  
  <hr>
  <h2>Abstract</h2>
  
  <p>
  This article describes a systematic way to extend the
  <a href="http://log4j.org">log4j</a> API in order include
  additional attributes that can be formatted using the
  <code><a href="../javadoc/org/apache/log4j/PatternLayout.html">PatternLayout</a></code> 
  class.
  <p>
  <hr>
  <h2>Contents</h2>
  <ul>
  	<li><a href="#intro">Introduction</a>
  	<ul>
  		<li><a href=#case>The Case Study</a>
  		<li><a href=#peek>A Peek Under the Hood</a>
  	</ul>
  	<li><a href="#process">The Process</a>
  	<ul>
  		<li><a href="#LoggingEvent">Extending <code>LoggingEvent</code></a>
  		<li><a href="#PatternLayout">Extending <code>PatternLayout</code></a>
  		<li><a href="#PatternParser">Extending <code>PatternParser</code> and 
  			<code>PatternConverter</code></a>
  		<li><a href="#Category">Extending <code>Category</code></a>
  		<li><a href="#CategoryFactory">Extending <code>CategoryFactory</code></a>
  	</ul>
  	<li><a href="#usage">Usage</a>
  	<ul>
  		<li><a href="#configurators">Configurators</a>
  	</ul>
  	<li><a href="#enhancements">Further Enhancements</a>
  </ul>
  <p>
  <hr>
  <a name="intro"><h2>Introduction</h2></a>
  
  <p>
  This article assumes familiarity with the log4j
  <a href="manual.html">User Manual</a>.  It builds on fundamental
  classes described in both the User Manual and the
  <a href="index.html">Javadoc</a>.  To assist in illustrating the
  concepts, a simple case study will be developed along side the
  explanations.  The resulting classes may be used as a template
  for your own extensions.  Condenced (i.e. statements compressed,
  comments removed) snipets of the case study code
  are included in this document.
  
  <a name="case"><h3>The Case Study</h3></a>
  The case study was developed in a CORBA environment in which we desired
  the following information for each log entry. The letters in
  parenthesis represent the corresponding character to be used by the
  <code><a href="../javadoc/org/apache/log4j/PatternLayout.html">PatternLayout</a></code> 
  class for formatting.
  
  <p>
  <ul>
  <li><b>Host Name (h)</b> - the IP address or hostname of the physical machine
  	on which the Category was running.
  <li><b>Server Name (s)</b> - The name of the application server process.  In
  	this context, a <i>server</i> refers to a process that accepts requests
  	rather than referring to a machine.  The term <i>host</i> refers to a
  	physical machine.  Several servers may run on the same host.
  <li><b>Component Name (b)</b> - Rather than getting bogged down on what
  	constitutes a component, let's assume for the case study that a component
  	is a unit of software worth denoting in the logs.
  <li><b>Version (v)</b> - the version of the component from which the log entry
  	originated.
  </ul>
  <p>
  It seems odd to use "b" for the component name.  Presently
  <code><a href="../javadoc/org/apache/log4j/PatternLayout.html">PatternLayout</a></code> 
  already defines both "c" and "C" for class name and category name respectively.
  
  <h3><a name="peek">A Peek Under the Hood</a></h3>
  <p>
  In principle, if the steps described below are followed closely, there is 
  not a need to understand how the extended classes will be used by log4j.
  But sometimes software development can be entirely unprincipled.  You may
  wish to extend log4j in a different manner than describe here or you may
  make a mistake that requires knowledge of what is really going on.  (Heaven
  forbid there be a mistake in this document).  In any case, it doesn't hurt
  to get an idea of what's going on.
  <p>
  The following describes a "typical" logging scenario in the un-extended log4j
  case.
  <p>
  <ol>
  <li>Application code invokes a log request on a
  <code><a href="../javadoc/org/apache/log4j/Category.html">Category</a></code> 
  object.  Let's say the <code>info</code> method was invoked.
  <p>
  <li>The first thing <code>info</code> does is to check if logging has
  been turned off entirely for the info level.  If so, it returns
  immediately.  We'll assume for this scenario that logging has not
  been turned off for the info level.
  <p>
  <li>Next <code>info</code> compares the
  <code><a href="../javadoc/org/apache/log4j/Priority.html">Priority</a></code> 
  level for this category against <code>Priority.INFO</code>.  Assuming
  the priority warrants logging the message, the category instanciates a
  <code><a href="../javadoc/org/apache/log4j/spi/LoggingEvent.html">LoggingEvent</a></code> 
  object populated with information available for logging.
  <p>
  <li>The <code>Category</code> instance passes the <code>LoggingEvent</code>
  instance to all its 
  <code><a href="../javadoc/org/apache/log4j/Appender.html">Appender</a></code> 
  implementations.
  <p>
  <li>Most (but not all) <code>Appender</code> implementations should have 
  an associated subclass of 
  <code><a href="../javadoc/org/apache/log4j/Layout.html">Layout</a></code>.
  The <code>Layout</code> subclass is passed the <code>LoggingEvent</code> 
  instance and returns the event's information formatted in a 
  <code>String</code> according to the configuration of the <code>Layout</code>.
  <p>
  When the <code>Layout</code> subclass is
  <code><a href="../javadoc/org/apache/log4j/PatternLayout.html">PatternLayout</a></code>, 
  the format of the event's information is determined by a character sequence
  similar to the <b>C</b> language library's <code>printf</code> routine.
  <code>PatternLayout</code> delegates the parsing of this character sequence to a
  <code><a href="../javadoc/org/apache/log4j/helpers/PatternParser.html">PatternParser</a></code> 
  instance.
  <p>
  When the <code>PatternLayout</code> was constructed, it created a 
  <code>PatternParser</code> to tokenize the character sequence.  Upon 
  recognizing a token, the <code>PatternParser</code> constructs an appropriate
  <code><a href="../javadoc/org/apache/log4j/helpers/PatternConverter.html">PatternConverter</a></code> 
  subclass, passing it formatting information from the token.  Often the
  <code>PatternConverter</code> subclasses are implemented as static inner 
  classes of <code>PatternParser</code>.  The <code>parse</code> method of
  the <code>PatternParser</code> returns a linked list of these
  <code>PatternConverter</code> subclasses.
  <p>
  <li><code>PatternLayout.format()</code> passes the <code>LoggingEvent</code>
  to each <code>PatternConverter</code> subclass in the linked list.  Each link
  in the list selects a particular item from the <code>LoggingEvent</code>,
  converts this item to a <code>String</code> in the proper format and appends 
  it to a <code>StringBuffer</code>.
  <p>
  <li>The <code>format</code> method returns the resulting <code>String</code>
  to the <code>Appender</code> for output.
  </ol>
  <p>
  The above discussing involved most of the classes that we must extend or
  implement.
  <p>
  <ul>
  <li><code><a href="../javadoc/org/apache/log4j/PatternLayout.html">org.apache.log4j.PatternLayout</a></code> 
  <li><code><a href="../javadoc/org/apache/log4j/Category.html">org.apache.log4j.Category</a></code> 
  <li><code><a href="../javadoc/org/apache/log4j/spi/CategoryFactory.html">org.apache.log4j.spi.CategoryFactory</a></code> 
  <li><code><a href="../javadoc/org/apache/log4j/spi/LoggingEvent.html">org.apache.log4j.spi.LoggingEvent</a></code> 
  <li><code><a href="../javadoc/org/apache/log4j/helpers/PatternParser.html">org.apache.log4j.helpers.PatternParser</a></code> 
  <li><code><a href="../javadoc/org/apache/log4j/helpers/PatternConverter.html">org.apache.log4j.helpers.PatternConverter</a></code> 
  </ul>
  <p>
  <hr>
  <a name="process"><h2>The Process</h2></a>
  Below are steps required to add additional attributes available
  for logging by extending log4j.  This will allow you to specify
  their output formats in the same manner as those provided by the
  <code>PatternLayout</code> class.  The steps are numbered for
  reference only.  It makes no difference in which order they are
  followed.
  <p>
  It's helpful if you know the attributes you wish to add and a 
  <code>PatternLayout</code> symbol for each one before you begin.  Be
  sure to consult the <code>PatternLayout</code> documentation to ensure
  the symbols you select are not already in use.
  <p>
  Before we dig in, I should give the standard lecture on comments.
  If the log4j library were not well documented, it would be useless
  to everyone but the log4j creators; likewise with your extensions.
  Much like eating vegetables and saving the environment, we all agree
  commenting code properly should be done.  Yet it is often sacrificed 
  for more immediate pleasures.  We all write code faster without 
  comments; especially those pesky Javadoc comments.  But the reality
  is that the utility of undocumented code fades exponentially with time.
  <p>
  Since the log4j product comes with Javadoc comments together with
  the documentation it produces, it makes sense to include Javadoc
  comments in your extensions.  By their very nature, logging tools
  are strong candidates for re-use.  They can only be independently
  re-used if they are supported by strong documentation component.
  <p>
  This all having been said, I have elected to remove most comments from
  examples in the interest of space rather than including them to serve
  as a nagging reminder.  The reader is referred to the case study source 
  code files for a Javadoc version and a
  <a href="http://java.sun.com/javadoc">Javadoc website</a>
  for more information on Javadoc conventions.
  
  <a name="LoggingEvent"><h3>1. Extending <code>LoggingEvent</code></h3></a>
  
  Extending the <code>LoggingEvent</code> class should be one of the
  trivial steps.  All that is needed in the extension is the addition
  of public data members representing the new attributes and a new
  constructor to populate them.
  <p>
  <table border=1>
  <tr><td>
  <pre>
  import org.apache.log4j.Category;
  import org.apache.log4j.Priority;
  import org.apache.log4j.spi.LoggingEvent;
  
  public class AppServerLoggingEvent extends LoggingEvent
                                     implements java.io.Serializable 
  {
     public String hostname;
     public String component;
     public String server;
     public String version;
     
     public AppServerLoggingEvent( String    fqnOfCategoryClass, 
                                   AppServerCategory  category, 
                                   Priority  priority, 
                                   String    message, 
                                   Throwable throwable) 
     {
        super( fqnOfCategoryClass,
               category,
               priority,
               message,
               throwable );
  
        hostname  = category.getHostname();
        component = category.getComponent();
        server    = category.getServer();
        version   = category.getVersion();
     }  
  }
  </pre>
  </table>
  <p>
  The constructor demonstrates that in most cases, the <code>Category</code>
  subclass will contain most of the information necessary to populate
  the attributes of the <code>LoggingEvent</code> subclass.  Extensions
  to <code>LoggingEvent</code> seem no more than a collection of strings
  with a constructor.  Most of the work is done by the super class.
  
  <a name="PatternLayout"><h3>2. Extending <code>PatternLayout</code></h3></a>
  
  Extending the <code>PatternLayout</code> class should be another
  simple matter.  The extension to <code>PatternLayout</code> should 
  differ from its parent only in the creation of a
  <code>PatternParser</code> instance.  The extended
  <code>PatternLayout</code> should create an extended
  <code>PatternParser</code> class.  Fortunately, this task in
  <code>PatternLayout</code> is encapsulated within a single method.
  <p>
  <table border=1> <tr><td>
  <pre>
  import org.apache.log4j.PatternParser;
  import org.apache.log4j.PatternLayout;
  
  public class AppServerPatternLayout extends PatternLayout 
  {
     public AppServerPatternLayout() 
     {
        this(DEFAULT_CONVERSION_PATTERN);
     }
  
     public MyPatternLayout(String pattern) 
     {
        super(pattern);
     }
      
     public PatternParser createPatternParser(String pattern) 
     {
        PatternParser result;
        if ( pattern == null )
           result = new AppserverPatternParser( DEFAULT_CONVERSION_PATTERN );
        else
           result = new AppServerPatternParser ( pattern );
  
        return result;
    }
  }
  </pre>
  </table>
  
  <a name="PatternParser"><h3>3. Extend <code>PatternParser</code> and <code>PatternConverter</code></h3></a>
  
  Recall from our <a href="#peek">peek under the hood</a> that the
  <code>PatternParser</code> does much of its work in its
  <code>parse</code> method.  The <code>PatternLayout</code> object
  instanciates a <code>PatternParser</code> object by passing it
  the pattern string.  The PatternLayout then invokes the
  <code>parse</code> method of <code>PatternParser</code> to produce
  a linked list of <code>PatternConverter</code> subclass instances.
  It is this linked list of converters that is used to convert an
  event instance into a string used by appenders.
  <p>
  Our job will be to subclass <code>PatternParser</code> to properly
  interpret formatting characters we wish to add.  Fortunately,
  <code>PatternParser</code> has been designed so that only the one
  step in the parsing process differing for each formatting character
  has to be overridden.  The grunt work of parsing is still performed
  by the <code>PatternParser.parse()</code> method.  Only the
  <code>PatternParser.finalizeConverter</code> method has to be
  overridden.  This is the method that decides which
  <code>PatternConverter</code> to create based on a formatting
  character.
  <p>
  The extension to <code>PatternParser</code>, 
  <code>AppServerPatternParser</code>, is similar to its super class.
  It uses
  <ul>
  <li>constants to identify the various formats
  <li>a converter defined as private static inner-classes of
  	<code>AppServerPatternParser</code>.
  <li>a <code>finalizeConverter</code> method which instanciates
  	the appropriate converter for a given format character.
  </ul>
  <p>
  <code>AppServerPatternParser</code> differs principally by 
  dedicating a separate converter type for each logging
  attribute to be formatted.
  Rather than placing switch logic in the converter, like its
  parent class, each converter only converts one format character.
  This means the decision of which converter subclass
  to instanciate is made at layout instanciation time rather
  than in a switch statement at logging time.
  <p>
  It also differs in that the format constants are characters 
  rather than integers.
  <p>
  <table border=1>
  <tr><td>
  <pre>
  import org.apache.log4j.*;
  import org.apache.log4j.helpers.FormattingInfo;
  import org.apache.log4j.helpers.PatternConverter;
  import org.apache.log4j.helpers.PatternParser;
  import org.apache.log4j.spi.LoggingEvent;
  
  public class AppServerPatternParser extends PatternParser 
  {
     static final char HOSTNAME_CHAR  = 'h';
     static final char SERVER_CHAR    = 's';
     static final char COMPONENT_CHAR = 'b';
     static final char VERSION_CHAR   = 'v';
  
     public AppServerPatternParser(String pattern) 
     {
        super(pattern);
     }
      
     public void finalizeConverter(char formatChar) 
     {
        PatternConverter pc = null;
        switch( formatChar )
        {
           case HOSTNAME_CHAR:
              pc = new HostnamePatternConverter( formattingInfo );
              currentLiteral.setLength(0);
              addConverter( pc );
              break;
           case SERVER_CHAR:
              pc = new ServerPatternConverter( formattingInfo );
              currentLiteral.setLength(0);
              addConverter( pc );
              break;
           case COMPONENT_CHAR:
              pc = new ComponentPatternConverter( formattingInfo );
              currentLiteral.setLength(0);
              addConverter( pc );
              break;
           case VERSION_CHAR:
              pc = new VersionPatternConverter( formattingInfo );
              currentLiteral.setLength(0);
              addConverter( pc );
              break;
           default:
              super.finalizeConverter( formatChar );
        }
     }
    
     private static abstract class AppServerPatternConverter extends PatternConverter 
     {
        AppServerPatternConverter(FormattingInfo formattingInfo) 
        {
           super(formattingInfo);     
        }
  
        public String convert(LoggingEvent event) 
        {
           String result = null;
           AppServerLoggingEvent appEvent = null;
  
           if ( event instanceof AppServerLoggingEvent )
           {
              appEvent = (AppServerLoggingEvent) event;
              result = convert( appEvent );
           }
           return result;
        }
  
        public abstract String convert( AppServerLoggingEvent event );
     }
  
     private static class HostnamePatternConverter extends AppServerPatternConverter
     {
        HostnamePatternConverter( FormattingInfo formatInfo )
        {  super( formatInfo );  }
  
        public String convert( AppServerLoggingEvent event )
        {  return event.hostname;  }
     }
  
     private static class ServerPatternConverter extends AppServerPatternConverter
     {
        ServerPatternConverter( FormattingInfo formatInfo )
        {  super( formatInfo );  }
  
        public String convert( AppServerLoggingEvent event )
        {  return event.server;  }
     }
  
     private static class ComponentPatternConverter extends AppServerPatternConverter
     {
        ComponentPatternConverter( FormattingInfo formatInfo )
        {  super( formatInfo );  }
  
        public String convert( AppServerLoggingEvent event )
        {  return event.component;  }
     }
  
     private static class VersionPatternConverter extends AppServerPatternConverter
     {
        VersionPatternConverter( FormattingInfo formatInfo )
        {  super( formatInfo );  }
  
        public String convert( AppServerLoggingEvent event )
        {  return event.version;  }
     }
  }
  </pre>
  </table>
  <p>
  <a name="Category"><h3>4. Extending <code>Category</code></h3></a>
  Extending <code>Category</code> and its factory will be more straight 
  forward than extending <code>PatternParser</code> and the converters.
  The following tasks are involved in overridding 
  <code>Category</code> for our purposes.
  <p>
  <ul>
  <li>Add fields corresponding to the new attributes with their
  	corresponding getters and setters.
  <p>
  <li>Override the constructor to accept or acquire information 
  	about new attributes.
  <p>
  <li>Override the <code>forcedLog</code> method to ensure that a 
  	correctly populated instance of 
  	<code>AppServerLoggingEvent</code> is instanciated rather than
  	the default <code>LoggingEvent</code>.
  <p>
  <li>Override the <code>getInstance</code> method to use our
  	<code>CategoryFactory</code> (described in the next step).  This will
  	require that we hold a static reference to our factory and provide a
  	way to initialize it.
  </ul>
  <p>
  Most of the code below is standard getter/setter verbage which has been
  somewhat abbreviated.  The notable parts are in bold.  We add five more
  attributes to <code>Category</code>: the four new logging attributes
  plus a static <code>AppServerCategoryFactory</code> reference.  This is
  pre-initialized to an instance with attributes set to null as a
  precautionary measure.  Otherwise the <code>getInstance</code> method
  will result in a null pointer exception if invoked before the
  <code>setFactory</code> method.
  <p>
  The <code>getInstance</code> method simply invokes its parent class
  method that accepts a <code>CategoryFactory</code> reference in
  addition to the category name.
  <p>
  The <code>forcedLog</code> method follows closely the corresponding
  parent class method.  The most important difference is the instanciation
  of the <code>AppServerLoggingEvent</code>.  A minor yet necessary 
  difference is the use of the <code>getRendererMap()</code> method rather
  than accessing the data member directory as in <code>Category</code>.
  <code>Category</code> can do this because the rendererMap 
  is package level accessible.
  <p>
  The <code>setFactory</code> method is provided to allow application code 
  to set the factory used in the <code>getInstance</code> method.
  <p>
  <table border=1>
  <tr><td>
  <pre>
  import org.apache.log4j.Priority;
  import org.apache.log4j.Category;
  import org.apache.log4j.spi.CategoryFactory;
  import org.apache.log4j.spi.LoggingEvent;
  
  public class AppServerCategory extends Category
  {
     protected String <b>component</b>;
     protected String <b>hostname</b>;
     protected String <b>server</b>;
     protected String <b>version</b>;
     private static CategoryFactory <b>factory = 
                    new AppServerCategoryFactory(null, null, null)</b>;
  
     protected  AppServerCategory( String categoryName,
                                   String hostname,
                                   String server,
                                   String component,
                                   String version )
     {
        super( categoryName );
        
        this.hostname  = hostname;
        this.server    = server;
        this.component = component;
        this.version   = version;
     }
  
     public String getComponent()
     { return (component == null ) ? "" : result; }
  
     public String getHostname()
     {  return ( hostname == null ) ? "" : hostname; }
  
     public static Category <b>getInstance</b>(String name)
     {
        return Category.getInstance(name, factory);
     }
  
     public String getServer()
     {  return ( server == null ) ? "" : server; }
  
     public String getVersion()
     {  return ( version == null ) ? "" : version; }
  
     protected void <b>forcedLog</b>( String    fqn, 
                               Priority  priority, 
                               Object    message, 
                               Throwable t) 
     {
        String s;
        LoggingEvent event;
        if(message instanceof String) 
           s = (String) message;
        else
           s = myContext.<b>getRendererMap()</b>.findAndRender(message);
  
  <b>      event = new AppServerLoggingEvent(fqn, this, priority, s, t);</b>
        callAppenders( event );
     }
  
     public void setComponent(String componentName)
     { component = componentName; }
  
     public static void <b>setFactory</b>(CategoryFactory factory)
     { AppServerCategory.factory = factory; }
  
     public void setHostname(String hostname)
     { this.hostname = hostname; }
  
     public void setServer(String serverName)
     { server = serverName; }
  
     public void setVersion(String versionName)
     { version = versionName; }
  }
  </pre>
  </table>
  <p>
  
  <a name="CategoryFactory"><h3>5. Extending <code>CategoryFactory</code></h3></a>
  
  The last step is to provide an implementation of the 
  <code>CategoryFactory</code> interface that will correctly 
  instanciate our <code>AppServerCategory</code> objects.  It
  will obtain the hostname of the machine on which it runs using the
  <code>java.net</code> API.  Aside from providing getters and
  setters for the attributes introduced, the only method to
  be implemented is the <code>makeNewCategoryInstance</code>.
  <p>
  Below is a snipet from <code>AppServerCategoryFactory</code>
  with getters, setters and comments removed.
  <p>
  <table border=1>
  <tr><td>
  <pre>
  import org.apache.log4j.Category;
  import org.apache.log4j.spi.CategoryFactory;
  import java.net.InetAddress;
  import java.net.UnknownHostException;
  
  public class AppServerCategoryFactory implements CategoryFactory
  {
     protected String hostname;
     protected String server;
     protected String component;
     protected String version;
  
     protected  AppServerCategoryFactory( String serverName,
                                          String componentName,
                                          String versionName )
     {
        try
        {
           hostname = java.net.InetAddress.getLocalHost().getHostName();
        }
        catch ( java.net.UnknownHostException uhe )
        {
           System.err.println("Could not determine local hostname.");
        }
  
        server    = serverName;
        component = componentName;
        version   = versionName;
     }
  
     /**
      *  Create a new instance of <code>AppServerCategory</code>
      *  using the information contained in this instance.
      */
     public Category makeNewCategoryInstance(String name)
     {
         return new AppServerCategory(name, hostname, server, component, version);
     }
  }
  </pre>
  </table>
  <p>
  <hr>
  <a name="usage"><h2>Usage</h2></a>
  We now arrive at how to use what we have created.  We must remember to
  initialize log4j by creating an instance of 
  <code>AppServerCategoryFactory</code> and passing it to 
  <code>AppServerCategory</code>.  Once done, we can obtain a
  <code>AppServerCategoryInstance</code> anytime by using the static
  <code>getInstance</code> method of <code>AppServerCategory</code>.
  This will ensure that <code>AppServerLoggingEvent</code> instances
  are generated by the category logging methods.
  <p>
  <table border=1>
  <tr><td>
  <pre>
  import org.apache.log4j.*;
  import my.extensions.log4j.appserver.AppServerCategory;
  import my.extensions.log4j.appserver.AppServerCategoryFactory;
  import my.extensions.log4j.appserver.AppServerPatternLayout;
  
  public class test
  {
     private static String formatString = 
        "---------------------------------------------------%n" +
        "Time:      %d%n" +
        "Host:      %h%n" +
        "Server:    %s%n" +
        "Component: %b%n" +
        "Version:   %v%n" +
        "Priority:  %p%n" +
        "Thread Id: %t%n" +
        "Context:   %x%n" +
        "Message:   %m%n";
  
     public static void main(String[] args)
     {
        AppServerCategoryFactory factory;
        factory = new AppServerCategoryFactory("MyServer", "MyComponent", "1.0");
        AppServerCategory.setFactory( factory );
        Category cat = AppServerCategory.getInstance("some.cat");
  
        PatternLayout layout = new AppServerPatternLayout( formatString );
        cat.addAppender( new FileAppender( layout, System.out) );
  
        cat.debug("This is a debug statement.");
        cat.info("This is an info statement.");
        cat.warn("This is a warning statement.");
        cat.error("This is an error statement.");
        cat.fatal("This is a fatal statement.");
     }
  }
  </pre>
  </table>
  <a name="configurators"><h3>Configurators</h3></a>
  There is one a word of caution concerning the use of configurators that
  may create <code>Category</code> instances (such as
  <a href="../javadoc/org/apache/log4j/PropertyConfigurator.html">
  <code>PropertyConfigurator</code></a> 
  and 
  <a href="../javadoc/org/apache/log4j/xml/DOMConfigurator.html">
  <code>DOMConfigurator</code></a>).  Since these configurators do not
  know about our extensions, any <code>Category</code> instances they 
  create will not be <code>AppServerCategory</code> instances.  To
  prevent this problem, any <code>AppServerCategory</code> that one
  might want to be configured through a configurator should be
  instanciated before the configure method is invoked.  In this way,
  the configurator will configure the <code>AppServerCategory</code>
  that already exists rather than creating an instance of its super
  class in its place.
  <p>
  <hr>
  <a name="enhancements"><h2>Further Enhancements</h2></a>
  There are some direction in which this log4j extension may be enhanced.
  <p>
  <ol>
  <li>The hostname attribute could incorportate a formatting convention
  	similar to that of class and category names whereby only a certain
  	number of the more significant components are displayed.  But
  	whereas with class and category names, the most significant component
  	is on the right, with host names, it is on the right.
  <li>Specifying a version number could be dangerous since programmers
  	are apt to change versions of the code without changing the 
  	string constant in the code which specifies the version.  Some
  	source control programs provide for insertion of a version number
  	into source.  For those that don't, including the version number as
  	a constant is likely to lead to confusion later on.  It would be
  	nice to see this short-coming addressed.
  </body>
  </html>