/*
* $Id: ApplicationAwareRepositorySelector.java 21563 2011-03-14 10:05:09Z dirk.olmes $
* --------------------------------------------------------------------------------------
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.module.launcher.log4j;
import org.mule.module.launcher.MuleApplicationClassLoader;
import org.mule.module.reboot.MuleContainerBootstrapUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RepositorySelector;
import org.apache.log4j.spi.RootLogger;
import org.apache.log4j.xml.DOMConfigurator;
public class ApplicationAwareRepositorySelector implements RepositorySelector
{
protected static final String PATTERN_LAYOUT = "%-5p %d [%t] %c: %m%n";
protected static final Integer NO_CCL_CLASSLOADER = 0;
protected ConcurrentMap<Integer, LoggerRepository> repository = new ConcurrentHashMap<Integer, LoggerRepository>();
// note that this is a direct log4j logger declaration, not a clogging one
protected Logger logger = Logger.getLogger(getClass());
public LoggerRepository getLoggerRepository()
{
final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
LoggerRepository repository = this.repository.get(ccl == null ? NO_CCL_CLASSLOADER : ccl.hashCode());
if (repository == null)
{
final RootLogger root = new RootLogger(Level.INFO);
repository = new Hierarchy(root);
try
{
ConfigWatchDog configWatchDog = null;
if (ccl instanceof MuleApplicationClassLoader)
{
MuleApplicationClassLoader muleCL = (MuleApplicationClassLoader) ccl;
// check if there's an app-specific logging configuration available,
// scope the lookup to this classloader only, as getResource() will delegate to parents
// locate xml config first, fallback to properties format if not found
URL appLogConfig = muleCL.findResource("log4j.xml");
if (appLogConfig == null)
{
appLogConfig = muleCL.findResource("log4j.properties");
}
final String appName = muleCL.getAppName();
if (appLogConfig == null)
{
// fallback to defaults
String logName = String.format("mule-app-%s.log", appName);
File logDir = new File(MuleContainerBootstrapUtils.getMuleHome(), "logs");
File logFile = new File(logDir, logName);
DailyRollingFileAppender fileAppender = new DailyRollingFileAppender(new PatternLayout(PATTERN_LAYOUT), logFile.getAbsolutePath(), "'.'yyyy-MM-dd");
fileAppender.setAppend(true);
fileAppender.activateOptions();
root.addAppender(fileAppender);
}
else
{
configureFrom(appLogConfig, repository);
if (appLogConfig.toExternalForm().startsWith("file:"))
{
// if it's not a file, no sense in monitoring it for changes
configWatchDog = new ConfigWatchDog(muleCL, appLogConfig.getFile(), repository);
configWatchDog.setName(String.format("[%s].log4j.config.monitor", appName));
}
else
{
if (logger.isInfoEnabled())
{
logger.info(String.format("Logging config %s is not an external file, will not be monitored for changes", appLogConfig));
}
}
}
}
else
{
// this is not an app init, but a Mule container, use the top-level defaults
File defaultSystemLog = new File(MuleContainerBootstrapUtils.getMuleHome(), "conf/log4j.xml");
if (!defaultSystemLog.exists() && !defaultSystemLog.canRead())
{
defaultSystemLog = new File(MuleContainerBootstrapUtils.getMuleHome(), "conf/log4j.properties");
}
configureFrom(defaultSystemLog.toURL(), repository);
configWatchDog = new ConfigWatchDog(ccl, defaultSystemLog.getAbsolutePath(), repository);
configWatchDog.setName("Mule.system.log4j.config.monitor");
}
final LoggerRepository previous = this.repository.putIfAbsent(ccl == null ? NO_CCL_CLASSLOADER : ccl.hashCode(), repository);
if (previous != null)
{
repository = previous;
}
if (configWatchDog != null)
{
configWatchDog.start();
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
return repository;
}
protected void configureFrom(URL url, LoggerRepository repository)
{
if (url.toExternalForm().endsWith(".xml"))
{
new DOMConfigurator().doConfigure(url, repository);
}
else
{
new PropertyConfigurator().doConfigure(url, repository);
}
}
// TODO rewrite using a single-threaded scheduled executor and terminate on undeploy/redeploy
// this is a modified and unified version from log4j to better fit Mule's app lifecycle
protected class ConfigWatchDog extends Thread
{
protected LoggerRepository repository;
protected File file;
protected long lastModif = 0;
protected boolean warnedAlready = false;
protected volatile boolean interrupted = false;
/**
* The default delay between every file modification check, set to 60
* seconds.
*/
static final public long DEFAULT_DELAY = 60000;
/**
* The name of the file to observe for changes.
*/
protected String filename;
/**
* The delay to observe between every check. By default set {@link
* #DEFAULT_DELAY}.
*/
protected long delay = DEFAULT_DELAY;
public ConfigWatchDog(final ClassLoader classLoader, String filename, LoggerRepository repository)
{
if (classLoader instanceof MuleApplicationClassLoader)
{
((MuleApplicationClassLoader) classLoader).addShutdownListener(new MuleApplicationClassLoader.ShutdownListener()
{
public void execute()
{
final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
ApplicationAwareRepositorySelector.this.repository.remove(ccl == null ? NO_CCL_CLASSLOADER : ccl.hashCode());
interrupted = true;
}
});
}
this.filename = filename;
this.file = new File(filename);
this.lastModif = file.lastModified();
setDaemon(true);
this.repository = repository;
this.delay = 10000; // 10 secs
}
public void doOnChange()
{
if (logger.isInfoEnabled())
{
logger.info("Reconfiguring logging from: " + filename);
}
if (filename.endsWith(".xml"))
{
new DOMConfigurator().doConfigure(filename, repository);
}
else
{
new PropertyConfigurator().doConfigure(filename, repository);
}
}
/**
* Set the delay to observe between each check of the file changes.
*/
public void setDelay(long delay)
{
this.delay = delay;
}
protected void checkAndConfigure()
{
boolean fileExists;
try
{
fileExists = file.exists();
}
catch (SecurityException e)
{
LogLog.warn("Was not allowed to read check file existence, file:[" + filename + "].");
interrupted = true; // there is no point in continuing
return;
}
if (fileExists)
{
long l = file.lastModified(); // this can also throw a SecurityException
if (l > lastModif)
{ // however, if we reached this point this
lastModif = l; // is very unlikely.
doOnChange();
warnedAlready = false;
}
}
else
{
if (!warnedAlready)
{
LogLog.debug("[" + filename + "] does not exist.");
warnedAlready = true;
}
}
}
public void run()
{
while (!interrupted)
{
try
{
Thread.sleep(delay);
}
catch (InterruptedException e)
{
interrupted = true;
Thread.currentThread().interrupt();
break;
}
checkAndConfigure();
}
if (logger.isDebugEnabled())
{
logger.debug(getName() + " terminated successfully");
}
}
}
}