Tuesday, March 9, 2010

Shutting down log4j

When I integrated Spring Security into CollectionSpace, the dynamic deployment of cspace-services.war stopped working. We use Cargo's JBoss plugin for maven to dynamically deploy war on a running server instance. The redeploy (undeploy + deploy) would fail with an error "Shutting down log4j"! Only restart of the server will allow to deploy the war. We use slf4 with log4j binding.

While the process of coming up to the following list of steps was long, the list itself is short.

1. In our environment, JBoss uses log4j, cspace-services.war uses log4j and Spring used it as well. However, Spring was shutting down log4j logger when our war was undeploying due to the following listener. Remove Log4jConfigListener from web.xml.

1:  <listener>  
2:   <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
3:  </listener>  

2. Do not package log4j jar with your web app. JBoss ships with log4j.jar that is available in the system classpath.

3. Have your own log RepositorySelector to separate your log from other applications (here JBoss). This also helps in stopping JBoss's RepositorySelector from jamming application's log entry onto its own. Here is how repository selector looked.

1:  package org.collectionspace.services.common.log;  
2:  import java.util.Hashtable;  
3:  import javax.naming.Context;  
4:  import javax.naming.InitialContext;  
5:  import javax.naming.NamingException;  
6:  import org.apache.log4j.Hierarchy;  
7:  import org.apache.log4j.Level;  
8:  import org.apache.log4j.spi.LoggerRepository;  
9:  import org.apache.log4j.spi.RepositorySelector;  
10:  import org.apache.log4j.spi.RootLogger;  
11:  /**  
12:   * CollectionSpaceLog4jRepositorySelector is a CollectionSpace  
13:   * specific log4j repository selector. See Ceki's solution  
14:   * for more details  
15:   * Courtsey Ceki Gulcu http://articles.qos.ch/sc.html  
16:   */  
17:  /** JNDI based Repository selector */  
18:  public class CollectionSpaceLog4jRepositorySelector implements RepositorySelector {  
19:    // key: name of logging context,  
20:    // value: Hierarchy instance  
21:    private Hashtable ht;  
22:    private Hierarchy defaultHierarchy;  
23:    public CollectionSpaceLog4jRepositorySelector() {  
24:      ht = new Hashtable();  
25:      defaultHierarchy = new Hierarchy(new RootLogger(Level.DEBUG));  
26:    }  
27:    // the returned value is guaranteed to be non-null  
28:    public LoggerRepository getLoggerRepository() {  
29:      String loggingContextName = null;  
30:      try {  
31:        Context ctx = new InitialContext();  
32:        loggingContextName = (String) ctx.lookup("java:comp/env/cspace-logging-context");  
33:      } catch (NamingException ne) {  
34:        // we can't log here  
35:      }  
36:      if (loggingContextName == null) {  
37:        return defaultHierarchy;  
38:      } else {  
39:        Hierarchy h = (Hierarchy) ht.get(loggingContextName);  
40:        if (h == null) {  
41:          h = new Hierarchy(new RootLogger(Level.DEBUG));  
42:          ht.put(loggingContextName, h);  
43:        }  
44:        return h;  
45:      }  
46:    }  
47:    /**  
48:     * The Container should remove the entry when the web-application  
49:     * is removed or restarted.  
50:     * */  
51:    public void remove(ClassLoader cl) {  
52:      ht.remove(cl);  
53:    }  
54:  }  

The JNDI environment entry was registered as follows in web.xml:

1:    <env-entry>  
2:      <description>Sets the logging context for the Tiger web-app</description>  
3:      <env-entry-name>cspace-logging-context</env-entry-name>  
4:      <env-entry-value>CSpaceLoggingContext</env-entry-value>  
5:      <env-entry-type>java.lang.String</env-entry-type>  
6:    </env-entry>  

Here is how we initialized it in our ServletContextLIstener

1:  public class CollectionSpaceServiceContextListener implements ServletContextListener {  
2:    @Override  
3:    public void contextInitialized(ServletContextEvent event) {  
4:  ...  
5:        LogManager.setRepositorySelector(new CollectionSpaceLog4jRepositorySelector(),  
6:            null);  
7:  ...  
8:    }  



4. Remove log4j.properties or log4j.xml from your war. We could still share log4j configuration with JBoss by adding categories into the jboss-log4j.xml. This is nice because you could dynamically change the log level using JMX mbeans provided by JBoss. If you package your own configuration in the war, you will need to redeploy it if you want to change log level or will have to build your own JMX Mbeans to change the level dynamically.

As I said before, the debugging took longer than writing this post. Hopefully, it is useful...

No comments:

Post a Comment