Revised log4j interfaces

Revised log4j interfaces

Ceki Gülcü

In the current log4j code, the Category class is polluted with static methods such as getInstance(), getRoot(), exists() etc. These methods essentially wrap method invocations on the default hierarchy. Although a single hierarchy may be suitable in stand alone applications, it is inadequate when used within Servlet containers or Application Servers. Moreover, a number of users have asked for the simplication of the Category class.

At this juncture it also seems beneficial to adopt some of the component names as proposed in the upcoming JSR47 API, namely, Level instead of Priority and Logger instead of Category.

Levels, Loggers and the LogManager

It is proposed to rename the Priority class to Level. To preserve backward compatibility a Priority class will be included which will extend Level without changing or adding any functionality. Classes that contain methods involving priorities, say setPriority, will contain a method called setLevel and also a method called setPriority (to preserve backward compatibility).

The user will only deal with a minimal Logger class offering few essential methods. More concretely:

public abstract class Logger {

  protected final String  name;  
  
  protected Logger(String name) {
    this.name = name;
  }

  public final String getName() {
    return name;
  }

  public abstract boolean isDebugEnabled(); 
  public abstract void debug(Object message);  
  public abstract void debug(Object message, Throwable t);  

  public abstract boolean isInfoEnabled(); 
  public abstract void info(Object message);  
  public abstract void info(Object message, Throwable t);

  public abstract boolean isWarnEnabled(); 
  public abstract void warn(Object message);  
  public abstract void warn(Object message, Throwable t);

  public abstract boolean isErrorEnabled(); 
  public abstract void error(Object message);  
  public abstract void error(Object message, Throwable t);

  public abstract boolean isFatalEnabled(); 
  public abstract void fatal(Object message);  
  public abstract void fatal(Object message, Throwable t);

  public abstract boolean isEnabledFor(Level level);  
  public abstract void log(Level level, Object message, Throwable t);  
  public abstract void log(Level level, Object message);

}

Here are a few remarks on the Logger class.

LogManager

Intent

The LogManager provides a flexible method for retrieving Logger instances of varying types held in context-dependent repositories.

Motivation

Log4j is a a low level API used in a variety of projects. Consequently, it is hard to make a priori assumptions about the environment where log4j will run. The problem is particularly acute in embedded components (e.g. libraries) which rely on log4j for their logging. The author of embedded component can rarely afford to make restrictive assumptions about the surrounding environment, a fortiori assumtions about logging. It might be the case that the end user is not interested in logging at all. It would be a total waste to generate logging output for a user who will never look at them. Under such circumstances, the embedded component will want to use a NullLogger which would not genereate any log output at all.

Logging in Application Servers and Servlet Containers also create unique problems. It is often desirable to separate logging output originating from different applications (or web-application in the case of Servlet Containers). In the current version of log4j it is possible to have different applications live in their own parallel universe by using a different hierarchy for each application. For more details, refer to my multiple hierarchy tutorial for Servlet Containers.

Using multiple hierarchies works well with code that is designed to use them. However, it does not entend to a library which uses log4j but is unaware of multiple hierarchies.

LogManager should allow us to vary Logger implementation depending on the circumstances. Moreover, we would like to be able to control the logging repository (or hierarchy) where loggers are held depending on the application context.

Related Patterns

LogManager is related to the AbstractFactory design pattern. It is largely based on the PluggableFactory pattern. Refer to John Vlissides' two articles Pluggable Factory, Part I and Pluggable Factory, Part II to gain more insight on the problem.

Public interface

The public interface of the LogManager class consists of a few static methods.

public class LogManager {

  public static Logger getRootLogger() {
     // return the appropriate root Logger 
  }

  public static Logger getLogger(String name) {
     // return the appropriate Logger instance
  }

  // The actual task of manufacturing loggers is delegated to a LoggerRepository
  public static void setLoggerRepository(LoggerRepository repository) {
    
  }  
}

Note that altough the methods are static there is a lot of flexibility in the underlying implementation. See the implementation section below.

Typical usage:


import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

public class Foo {

 final static Logger logger = LogManager.getLogger(Foo.class.getName());

 void bar() {
   logger.debug("hello world.");
 }
}

One of the advantages of the LogManager approach is that the getInstance method can return totally different Logger implementations. For example, under JDK 1.2 we can return a Logger implementation that is aware of the Java2 security model whereas under JDK 1.1 the returned logger implementation may be oblivious to Java2 security checks. In shipped code, LogManager may be configured to return a NullLogger which could implement the Logger (abstract) class with empty method bodies.

Impelementation

The behavior of LogManager is mostly determined by the LoggerRepository it uses. However, it may also be influenced by system properties or by parameters passed as a result of LogManager method invocations.

Here is a tentative implementation.

package org.apache.log4j;

/**
  Use the LogManager to retreive instances of {@link Logger}.
*/
public final class LogManager {

  static private Object guard = null;
  static private LoggerRepository loggerRepository = null;

  // By default the logger repository is set to a platform dependent
  // default logger repository.
  static {
    if(java14) {
      loggerRepository = new DefaultLoggerRepository14();
    } else if (java2) {
      loggerRepository = new DefaultLoggerRepository2();
    } else {
      loggerRepository = new DefaultLoggerRepository11();
    }
  }


  /**
     Sets LoggerRepository but only if the correct
     guard is passed as parameter.
     
     Initally the guard is null.  If the guard is
     null, then invoking this method sets the logger
     repository and the guard. Following invocations will throw a {@link
     IllegalArgumentException}, unless the previously set
     guard is passed as the second parameter.

     This allows a high-level component to set the logger repository to
     be used, thus, fixing the log4j environment.
     
     For example, when tomcat starts it will be able to install its
     own logger repository. However, if and when Tomcat is embedded
     within JBoss, then JBoss will install its loggger repository and
     Tomcat will use the repository set by its container, JBoss.
  */
  public
  static
  void  setLoggerRepository(LoggerRepository repository, Object guard) 
                                              throws IllegalArgumentException {
    if((LogManager.guard != null) && (LogManager.guard != guard)) {
      throw new IllegalArgumentException(
           "Attempted to reset the LoggerRepository without possessing the guard.");
    }

    if(repository == null) {
      throw new IllegalArgumentException("LoggerRepository must be non-null.");
    }

    LogManager.guard = guard;
    LogManager.loggerRepository = repository;

  }

  /**
     Retrieve the appropriate root logger.
   */
  public
  static
  Logger getRootLogger() {
     // Delegate the choice of the root logger to the logger repository
    return loggerRepository.getRootLogger();
  }

  /**
     Retrieve the appropriate {@link Logger} instance.  
  */
  public
  static
  Logger getLogger(String name) {
     // Delegate the actual manufacturing of the logger to the logger repository.
    return loggerRepository.getLogger(name);
  }
}

LoggerRepository

Intent Serve as a repository of logger objects offering primitives for retreiving individual loggers or acting on collections of loggers. In special cases a LoggerRepository can be itself composed of LoggerRepositories.

Motivation One of the distinctive feature of log4j is its ability to arrange loggers (categories) in a hierarchy. See the Hierarchy class for further detail. A hierarchy is merely a repository of categories which happens to arrange them in a tree like structure. The log4j hiearchy as its stands today (August 2001) can only deal with Category objects which are heavier than loggers as defined previously.

public interface LoggerRepository {

  // return the appropriate root Logger
  public Logger getRootLogger();

  // return an appropriate Logger instance
  public Logger getLogger(String name);

  // 
  public void enable(Level level);
  
  
}

Backward compatibility

Existing users of log4j need not worry about the compatibility of their code with future versions of log4j. We will make sure that client code runs seamlessly with log4j versions based on the revised interfaces proposed in this document.

Contributors

The present propposal is based on discussions that were held on the jakarta-commons mailing list. The contributors listed below are not known to endorse this proposal in any way.