LogbackServletContextListener.java

package com.ziesemer.logging;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.BasicConfigurator;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;

/**
 * @author Mark A. Ziesemer
 * 	<a href="http://www.ziesemer.com">&lt;www.ziesemer.com&gt;</a>
 */
public class LogbackServletContextListener implements ServletContextListener{
	
	protected static final Logger LOGGER = LoggerFactory.getLogger(LogbackServletContextListener.class);
	
	@Override
	public void contextInitialized(final ServletContextEvent sce){
		final ServletContext sc = sce.getServletContext();
		final String appName = sc.getContextPath();
		
		// http://jira.qos.ch/browse/LOGBACK-764
		
		final LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory();
		
		context.reset();
		if(!appName.isEmpty()){
			context.setName(appName);
		}
		
		boolean configured = false;
		final ContextInitializer ci = new ContextInitializer(context);
		
		try{
			final JoranConfigurator configurator = new JoranConfigurator();
			configurator.setContext(context);
			
			// These first entries effectively override the design of the "logback.xml" vs. "logback-test.xml" bit -
			//   but the same goal can be accomplished by including the same path on both a "main" and a "test" classpath,
			//   ensuring both are on the path of the same classloader (level), but with the "test" path coming before "main".
			// This is the default in Maven builds, for example.
			
			InputStream is = getClass().getResourceAsStream("/com/ziesemer/logging/contexts/" + appName + "/logback.xml");
			if(is == null){
				is = getClass().getResourceAsStream("/com/ziesemer/logging/logback.xml");
				if(is == null){
					final URL url = ci.findURLOfDefaultConfigurationFile(true);
					if(url != null){
						ci.configureByResource(url);
						configured = true;
					}else{
						// Using standard parent-first classloading, this can be placed within the container to override,
						//   or fall-back to the copy embedded within this logging library.
						is = getClass().getResourceAsStream("/com/ziesemer/logging/logback-default.xml");
					}
				}
			}
			if(is != null){
				try{
					configurator.doConfigure(is);
					configured = true;
				}finally{
					is.close();
				}
			}
			
			// This should never happen, as this logging library itself should be packaged with the above "logback.xml".
			if(!configured){
				new BasicConfigurator().configure(context);
			}
			
			// Allow the application to append additional configurations from within the package
			//   (likely, instead of using the default "logback.xml"):
			is = getClass().getResourceAsStream("/com/ziesemer/logging/logback-append.xml");
			if(is != null){
				try{
					configurator.doConfigure(is);
				}finally{
					is.close();
				}
			}
			is = getClass().getResourceAsStream("/com/ziesemer/logging/contexts/" + appName + "/logback-append.xml");
			if(is != null){
				try{
					configurator.doConfigure(is);
				}finally{
					is.close();
				}
			}
			// Allow the container to append append additional configurations (used globally):
			is = getClass().getResourceAsStream("/com/ziesemer/logging/logback-container-append.xml");
			if(is != null){
				try{
					configurator.doConfigure(is);
				}finally{
					is.close();
				}
			}
		}catch(final JoranException je){
			// StatusPrinter will handle this
		}catch(final IOException ioe){
			LOGGER.error(ioe.toString(), ioe);
		}
		
		StatusPrinter.printInCaseOfErrorsOrWarnings(context);
		
		LOGGER.info("Context initialized.");
	}
	
	@Override
	public void contextDestroyed(final ServletContextEvent sce){
		LOGGER.info("Destroying context...");
		
		final LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory();
		context.stop();
	}
	
}