WebSphere Application Server 6.o uses commons-logging and is loaded by the application server's class loader. Normally logging would configured on the server level through the administration console. Go to Troubleshooting, click Logging and Tracing > server name > Change Log Detail Levels and configure what needs to be logged there. However, there are times when the WebSphere logging is not enough and applications need something more powerful such as Log4j to do the logging.
It is recommended that the class loaders are left to the default policies rather than switching to PARENT_LAST or using an APPLICATION rather than MODULE policy. This will ensure you are coding to spec and make it easier for you to switch application servers if needed (e.g. Geronimo on the development workstations and WebSphere Application Server for the production servers). It also prevents leaks when restarting applications as the class loader and all its data are unloaded cleanly.
To configure Log4j to work on an application deployed in WebSphere 6.0, the application module must have the commons-logging.jar and log4j.jar file within its classpath. It is then a matter of invoking the LogFactory.setAttribute() method on initialization.
For web modules, create an initialization servlet as follows:
public class InitServlet extends GenericServlet implements Servlet {
public void init() {
LogFactory.getFactory().setAttribute(
LogFactoryImpl.LOG_PROPERTY,
Log4JLogger.class.getName());
DOMConfigurator.configure(
getClass().getClassLoader().getResource("log4jconfig.xml"));
}
public void service(ServletRequest request, ServletResponse response) {
}
public void destroy() {
LogFactory.releaseAll();
}
}Some notes:
- The DOMConfigurator line specifies where to find the Log4J configuration to use.
- The destroy() method is used to release all the Log objects that were created by the LogFactory within the current class loader in order to prevent leaks when restarting.
- A log statement may be put into the service() method, to indicate successful configuration of the LogFactory.
For EJB modules, there is no such thing as an initialization EJB
within the spec so it has to be done in the ejbCreate() and ejbRemove() methods. Since this can be repetitive, it is best that the EJBs extend a base class that sets this up. An example of an abstract EJB base class is as follows:
public abstract class BaseSessionBean implements SessionBean {
public void ejbCreate() throws CreateException {
LogFactory.getFactory().setAttribute(
LogFactoryImpl.LOG_PROPERTY,
Log4JLogger.class.getName());
DOMConfigurator.configure(
getClass().getClassLoader().getResource("log4jconfig.xml"));
}
public void ejbRemove() {
LogFactory.releaseAll();
}
}This is similar to the InitServlet except that it uses the EJB constructs rather than that of Servlets. This base class has to be extended by all EJBs in the project to ensure that the LogFactory gets configured correctly. However, this is very intrusive to the application since it introduces the Fragile Base Class antipattern.
To solve the antipattern, two things need to be done: create a custom LogFactory and let the module know to use the custom LogFactory. The custom log factory looks like this:
public class CustomLogFactory extends LogFactoryImpl {
private static final String LOGGER = Log4JLogger.class.getName();
protected String getLogClassName() {
setAttribute(LOG_PROPERTY, LOGGER);
return super.getLogClassName();
}
protected Constructor getLogConstructor() throws LogConfigurationException {
setAttribute(LOG_PROPERTY, LOGGER);
return super.getLogConstructor();
}
}Some notes:
- The getLogClassName() and getLogConstructor() methods are called by LogFactory in order to create the actual log.
- The setAttribute() method for the two methods in order to ensure that the Log wanted is the Log4J logger. Putting the setAttribute() in the constructor does not work.
The other thing to do is register the new LogFactory. To do this, create a file in the META-INF/services directory called org.apache.commons.logging.LogFactory and put in the fully qualified name of the custom LogFactory. This has to be done for every module, but at least it is away from the application class hierarchy and can even be kept in a separate jar.
However, this will still leak since the releaseAll() method is not called and there is no way of implementing this without putting it as part of the class heirarchy.