Log4jRepositorySelector

Using your own log4j.xml file (with WAR files) - Log4j RepositorySelector

If you need each application context to have its own log4j configuration then you should write a custom RepositorySelector that changes how the LogManager gets a logger.  Using this technique, Logger.getLogger() will return a different logger based on the context class loader.  Each context class loader has its own configuration set up with its own log4j.xml file.

 

The following example shows a RepositorySelector that looks for a log4j.xml file in the WEB-INF direcotry. Right below this class is an example using an application ContextListener which will initialize your log4j.xml file calling this RepositorySelector. See the attachment, repositoryselector.zip, for a full example showing two web applications with different logging configurations based on different log4j.xml files.

/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/
package org.jboss.repositoryselectorexample;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RepositorySelector;
import org.apache.log4j.spi.RootLogger;
import org.apache.log4j.xml.DOMConfigurator;
import org.w3c.dom.Document;

/**
 * This RepositorySelector is for use with web applications. It assumes that
 * your log4j.xml file is in the WEB-INF directory.
 * 
 * @author Stan Silvert
 */
public class AppRepositorySelector implements RepositorySelector {
    private static boolean initialized = false;
    private static Object guard = LogManager.getRootLogger();
    private static Map repositories = new HashMap();
    private static LoggerRepository defaultRepository;

    public static synchronized void init(ServletConfig servletConfig)
            throws ServletException {
        init(servletConfig.getServletContext());
    }

    public static synchronized void init(ServletContext servletContext)
            throws ServletException {
        if (!initialized) // set the global RepositorySelector
        {
            defaultRepository = LogManager.getLoggerRepository();
            RepositorySelector theSelector = new AppRepositorySelector();
            LogManager.setRepositorySelector(theSelector, guard);
            initialized = true;
        }

        Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
        loadLog4JConfig(servletContext, hierarchy);
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        repositories.put(loader, hierarchy);
    }

    public static synchronized void removeFromRepository() {
        repositories.remove(Thread.currentThread().getContextClassLoader());
    }

    // load log4j.xml from WEB-INF
    private static void loadLog4JConfig(ServletContext servletContext,
            Hierarchy hierarchy) throws ServletException {
        try {
            String log4jFile = "/WEB-INF/log4j.xml";
            InputStream log4JConfig = servletContext.getResourceAsStream(log4jFile);
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();            builder.setEntityResolver(new Log4jEntityResolver());
            Document doc = builder.parse(log4JConfig);
            DOMConfigurator conf = new DOMConfigurator();
            conf.doConfigure(doc.getDocumentElement(), hierarchy);
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    private AppRepositorySelector() {
    }

    public LoggerRepository getLoggerRepository() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        LoggerRepository repository = (LoggerRepository) repositories.get(loader);
        if (repository == null) {
            return defaultRepository;
        } else {
            return repository;
        }
    }
}

 

ServletContextListener which calls our RepositorySelector

package org.jboss.webapp.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import package org.jboss.repositoryselectorexample.AppRepositorySelector;

public class AppContextListener implements ServletContextListener {
     
    public void contextDestroyed(ServletContextEvent contextEvent) { }
 
    public void contextInitialized(ServletContextEvent contextEvent) {
        try {
             AppRepositorySelector.init(contextEvent.getServletContext());
        } catch (Exception ex) {
             System.err.println(ex);
        }
    }
}

 

Don't forgot to declare your ContextListener in your web.xml

(Add this section just above your serlvet definitions)

<listener>
   <listener-class>org.jboss.webapp.listener.AppContextListener</listener-class>
</listener>

 

If you prefer to use a Servlet to make the initialization call:

you can initialize the Repository from an init method -

public void init(ServletConfig config) throws ServletException {
    super.init(config);
    AppRepositorySelector.init(config);
}

 

If using the Servlet approach don't forget to declare your Servlet in your web.xml and set it load on startup: <load-on-startup>1</load-on-startup>

 

 

Using your own log4j.xml file (with EJB JAR files ) -

 

Ive modified the above code to work with EJB. I have tested this code on JBBoss 4.0, using Session beans.

 

The problem as described above, is that you don't want to use JBoss's conf/log4j.xml file - you want to use your own. This means you don't have to configure files on your container to get your beans to log - you can simple hot deploy your bean and it will start logging with no extra config on the container.

 

I have not tested this code with EAR files, only with individual EJB Jar's.

 

What you need to do is place you custom log4j.xml in your EJB project's META-INF directory, next to your jboss.xml and ejb-jar.xml.

 

Create a class file with the source code below. You can change the package declaration to suite, and add it to your project. If you are going to be deploying many EJBs or rather, many ejb jars - then i suggest you put this class in its own jar, and place it with the rest of JBoss's jars (eg: C:\jboss-4.0.4.GA\server\default\lib).

 

Now in you EJB, in your constructor, do this:

 

public TestingLoggingBean() throws Exception
{
ContextRepositorySelector.init(this);
trcLog = Logger.getLogger("MyLogger");
}

 

This will make sure your logger is initialized with your own log4j.xml.

 

package org.jboss.repositoryselectorexample;

/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/


import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.ejb.SessionBean;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RepositorySelector;
import org.apache.log4j.spi.RootCategory;
import org.apache.log4j.xml.DOMConfigurator;
import org.w3c.dom.Document;

/**
 * This RepositorySelector is for use with web applications.  It assumes that
 * your log4j.xml file is in the META-INF directory.
 *
 * @author  Stan Silvert, modified for use with EJB by Dean Fredericks
 */


public class ContextRepositorySelector implements RepositorySelector
{
   private static boolean initialized = false;

   // This object is used for the guard because it doesn't get
   // recycled when the application is redeployed.
   private static Object guard = LogManager.getRootLogger();
   
   private static Map repositories = new HashMap();
   private static LoggerRepository defaultRepository;

   /**
    * Register your web-app with this repository selector.
    */
   public static synchronized void init(SessionBean sessionbean) throws Exception
   {
      if( !initialized ) // set the global RepositorySelector
      {
         defaultRepository = LogManager.getLoggerRepository();
         RepositorySelector theSelector = new ContextRepositorySelector();
         LogManager.setRepositorySelector(theSelector, guard);
         initialized = true;
      }
      
      Hierarchy hierarchy = new Hierarchy(new RootCategory(Level.DEBUG));
      loadLog4JConfig(hierarchy, sessionbean);
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      repositories.put(loader, hierarchy);
   }

   // load log4j.xml from META-INF
   private static void loadLog4JConfig(Hierarchy hierarchy,SessionBean sessionbean) throws Exception 
   {
        String log4jFile = "/META-INF/log4j.xml";
       InputStream log4JConfig = sessionbean.getClass().getResourceAsStream(log4jFile);
       Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(log4JConfig);
       DOMConfigurator conf = new DOMConfigurator();
       conf.doConfigure(doc.getDocumentElement(), hierarchy);
    }

   private ContextRepositorySelector() 
   {
        // constructor private - no access allowed
   }

   public LoggerRepository getLoggerRepository() 
   {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      LoggerRepository repository = (LoggerRepository)repositories.get(loader);
      
      if (repository == null) 
      {
          return defaultRepository;
      } 
      else 
      {
          return repository;
      }
   }
}

 

Referenced by: