Many thanks for these explanations Jacob. Here are some comments. I
have included a modified version of Log4jCRS.

At 00:10 13.12.2002 -0600, Jacob Kjome wrote:

In fact, this version of Log4jCRS is *not* "part of the Container". It is driven by applications individually. Tomcat needs to know absolutely nothing about this class for multiple applications to utilize it.
Important point indeed.

Here is how it works...

First, Log4jCRS and log4j.jar need to be in the same classloader. This can be either WEB-INF/lib or a parent classloader like Tomcat's common/lib (note that classes in shared/lib count as being in the same classloader as common/lib). The case of using the WEB-INF/lib kind of defeats the purpose of using this at all since Log4j already has a unique logging environment to work within, but it works, nevertheless. What we'll assume for the following is that log4j.jar and Log4jCRS exist in common/lib (and/or shared/lib...although log4j.jar needs to be in common/lib or else Tomcat's commons-logging goes bonkers).
OK.

Each application, at startup, configures Log4j in the init() method of a servlet or in the contextInitialized() method of a servlet context listener (the latter being the best choice). Before any call to any configure() method, the following gets run...

Log4jCRS crs = new w();
crs.initLoggerRepository();
How about just calling Log4jCRS.doIdempotentInitialization() instead?
See also below.

Now call your favorite configure() method.

That's it! A new LoggerRepository has been created uniquely for your application keyed upon the WebappClassLoader of the current webapp. All other webapps do the same thing. Again, Tomcat is none-the-wiser and your code doesn't even know it happened. You set up your loggers and your configuration just like you always did. You need no special knowledge about Log4jCRS except just before running your app's configuration.
Yep.

Now you say, "but how do I clean up after logging"? "I have to shut down appenders when my app shuts down (and the VM/Tomcat continues to run) or my files will be locked if I am using, for instance, a FileAppender". Well, Log4jCRS comes to the rescue there as well....

Using the contextDestroyed() method of a servlet context listener, one can do the following....

ClassLoader cl = Thread.currentThread().getContextClassLoader();
Log4jCRS crs = Log4jCRS.getCRS(cl);
crs.w
crs.remove(cl);
Wouldn't the following accomplish the same?

LogManager.shutdown();
Log4jCRS crs = Log4jCRS.getSingleton();
crs.remove(cl);


That last line is less important since, with that classloader no longer in existence and the fact that it is stored in a WeakHashMap, it will clean itself up, eventually. However, since it is convenient, we just do it ourselves anyway.

To sum up...
We are able to create a unique LoggerRepository for any webapp that wants one even with log4j existing in the heterogeneous environment of the parent classloader where, normally, it would be used statically with the same LoggerRepository for all apps if not for Log4jCRS. And all this is done without Tomcat having a clue as to the fact that we are using a custom logger repository selector!

Caveat!!!!
The classes that load Log4jCRS *must* exist in the WebappClassLoader of *each* webapp. So, if you have a Log4jInit servlet or a servlet context listener that initializes Log4j, they must be loaded from WEB-INF/lib or WEB-INF/classes. This is because when Log4jCRS is loaded, it uses the classloader that loaded it as the key for the logger repositories.

So, Log4jCRS *must* exist in or beside log4j.jar *and* the classes that load Log4jCRS *must* exist in the WebappClassLoader.

If you can accept that, then I think you will enjoy using Log4jCRS.

There you go. Now you are informed.
Well put but I still don't understand why you need multiple CRS
instances especially since there can be one and only one active
RepositorySelector? Doesn't the Log4jCRS.getSingleton trick do it?

Jake
--
Ceki

/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software
 * License version 1.1, a copy of which has been included with this
 * distribution in the LICENSE.txt file.  */

package org.apache.log4j.selectors;

import org.apache.log4j.spi.RepositorySelector;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RootCategory;
import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * @author  Jacob Kjome 
 */
public class Log4jCRS implements RepositorySelector {
      
  // key: current thread's ContextClassLoader, 
  // value: Hierarchy instance
  final private static Map hierMap = Collections.synchronizedMap(new WeakHashMap());
  
  static  Log4jCRS singleton = new Log4jCRS();
  static  boolean initialized = false;
  
  private Log4jCRS() {}
  
  public static Hierarchy getHierarchy(ClassLoader cl) {
    return (Hierarchy) hierMap.get(cl);
  }
  
  public LoggerRepository getLoggerRepository() {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    Hierarchy hierarchy = (Hierarchy) hierMap.get(cl);
    
    if(hierarchy == null) {
      hierarchy = new Hierarchy(new RootCategory((Level) Level.DEBUG));
      hierMap.put(cl, hierarchy);
    } 
    return hierarchy;
  }
  
  /** 
   * The Container should initialize the logger repository for each
   * webapp upon startup or reload.  In this case, it is controllable
   * via each webapp.
   */
  public static void doIdempotentInitialization() {
    if(!initialized) {
      Object guard = new Object();
      LogManager.setRepositorySelector(singleton, guard);   
      initialized = true;
    }
  }

  public static Log4jCRS getSingleton() {
    return singleton;
  }
  
  public void remove(ClassLoader cl) {
    hierMap.remove(cl);
  }
}

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to