package er.extensions.logging;
import java.util.Properties;
import org.apache.log4j.Appender;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import com.webobjects.foundation.NSLog;
import com.webobjects.foundation.NSNotificationCenter;
import er.extensions.foundation.ERXConfigurationManager;
import er.extensions.foundation.ERXSystem;
/**
* Custom subclass of Logger. The main reason for this class is to isolate the
* log4j dependency to only this class. This gives us the freedom in the future
* to switch logging systems and this should be the only effected class .. in
* theory.
*/
public class ERXLogger extends org.apache.log4j.Logger {
public static final String CONFIGURE_LOGGING_WITH_SYSTEM_PROPERTIES = "configureLoggingWithSystemProperties";
/** logging supprt */
public static Logger log;
public static Factory factory = null;
static {
String factoryClassName = System.getProperty("log4j.loggerFactory");
if (factoryClassName == null) {
factoryClassName = ERXLogger.Factory.class.getName();
}
if (factoryClassName.indexOf("ERXLogger$Factory") >= 0) {
System.getProperties().remove("log4j.loggerFactory");
factoryClassName = null;
}
if (factoryClassName != null) {
try {
ERXLogger.factory = (Factory) Class.forName(factoryClassName).newInstance();
}
catch (Exception ex) {
System.err.println("Exception while creating logger factory of class " + factoryClassName + ": " + ex);
}
} else {
ERXLogger.factory = new Factory();
}
}
/**
* LoggerFactory subclass that creates ERXLogger objects instead of the
* default Logger classes.
*/
public static class Factory implements org.apache.log4j.spi.LoggerFactory {
/**
* Overriden method used to create new Logger classes.
*
* @param name
* to create the new Logger instance for
* @return new Logger object for the given name
*/
public Logger makeNewLoggerInstance(String name) {
if (ERXLogger.log != null && ERXLogger.log.isDebugEnabled()) {
ERXLogger.log.debug("makeNewLoggerInstance: " + name);
}
return new ERXLogger(name);
}
/**
* Override this in your own subclass to do somthing after the logging
* config did change.
*
*/
public void loggingConfigurationDidChange() {
// default is to do nothing
}
}
/**
* Main entry point for getting an Logger for a given name. Calls getLogger
* to return the instance of Logger from our custom Factory.
*
* Note that if the log4j system has not been setup correctly, meaning the
* LoggerFactory subclass has not been correctly put in place, then
* RuntimeException will be thrown.
*
* @param name
* to create the logger for
* @return Logger for the given name.
*/
public static ERXLogger getERXLogger(String name) {
Logger logger = ERXLogger.getLogger(name);
if (logger != null && !(logger instanceof ERXLogger)) {
ERXLogger.configureLoggingWithSystemProperties();
logger = ERXLogger.getLogger(name);
}
if (logger != null && !(logger instanceof ERXLogger)) {
throw new RuntimeException("Can't load Logger for \"" + name + "\" because it is not of class ERXLogger but \"" + logger.getClass().getName() + "\". Let your Application class inherit from ERXApplication or call ERXLog4j.configureLogging() statically the first thing in your app. \nAlso check if there is a \"log4j.loggerFactory=er.extensions.Logger$Factory\" line in your properties.");
}
return (ERXLogger) logger;
}
/**
* Overrides method of superclass to return a logger using our custom
* Logger$Factory class. This works identical to
* {@link org.apache.log4j.Logger#getLogger log4.Logger.getLogger}
*
* @param name
* to create the logger for
* @return Logger for the given name.
*/
public static Logger getLogger(String name) {
return Logger.getLogger(name, ERXLogger.factory);
}
/**
* Creates a logger for a given class object. Gets a logger for the fully
* qualified class name of the given class.
*
* @param clazz
* Class object to create the logger for
* @return logger for the given class name
*/
public static ERXLogger getERXLogger(Class clazz) {
return ERXLogger.getERXLogger(clazz.getName());
}
public static Logger getLogger(Class clazz) {
return ERXLogger.getERXLogger(clazz);
}
/**
* Creates a logger for the given class object plus a restricting subtopic.
* For instance if you had the class <code>a.b.Foo</code> and you wanted to
* create a logger for the subtopic 'utilities' for the class Foo then the
* created logging logger would have the path:
* <code>a.b.Foo.utilities</code>.
*
* @param clazz
* Class object to create the logger for
* @param subTopic
* to restrict the current logger to
* @return logger for the given class and subtopic
*/
// ENHANCEME: We could do something more useful here...
public static ERXLogger getERXLogger(Class clazz, String subTopic) {
return ERXLogger.getERXLogger(clazz.getName() + (subTopic != null && subTopic.length() > 0 ? "." + subTopic : null));
}
/**
* Default constructor. Constructs a logger for the given name.
*
* @param name
* of the logging logger
*/
public ERXLogger(String name) {
super(name);
}
public static synchronized void configureLoggingWithSystemProperties() {
ERXLogger.configureLogging(ERXSystem.getProperties());
}
/**
* Sets up the logging system with the given configuration in
* {@link java.util.Properties} format.
*
* @param properties
* with the logging configuration
*/
public static synchronized void configureLogging(Properties properties) {
LogManager.resetConfiguration();
BasicConfigurator.configure();
// AK: we re-configure the logging a few lines later from the
// properties, but in case
// no config is set, we set the root level to info, install the brigde
// which sets it's own logging level to DEBUG
// and the output should be pretty much the same as with plain WO
Logger.getRootLogger().setLevel(Level.INFO);
int allowedLevel = NSLog.debug.allowedDebugLevel();
if (!(NSLog.debug instanceof ERXNSLogLog4jBridge)) {
NSLog.setOut(new ERXNSLogLog4jBridge(ERXNSLogLog4jBridge.OUT));
NSLog.setErr(new ERXNSLogLog4jBridge(ERXNSLogLog4jBridge.ERR));
NSLog.setDebug(new ERXNSLogLog4jBridge(ERXNSLogLog4jBridge.DEBUG));
}
NSLog.debug.setAllowedDebugLevel(allowedLevel);
PropertyConfigurator.configure(properties);
// AK: if the root logger has no appenders, something is really broken
// most likely the properties didn't read correctly.
if (!Logger.getRootLogger().getAllAppenders().hasMoreElements()) {
Appender appender = new ConsoleAppender(new ERXPatternLayout("%-5p %d{HH:mm:ss} (%-20c:%L): %m%n"), "System.out");
Logger.getRootLogger().addAppender(appender);
Logger.getRootLogger().setLevel(Level.DEBUG);
Logger.getRootLogger().error("Logging prefs couldn't get read from properties, using defaults");
}
if (ERXLogger.log == null) {
ERXLogger.log = Logger.getLogger(Logger.class);
}
ERXLogger.log.info("Updated the logging configuration with the current system properties.");
if (ERXLogger.log.isDebugEnabled()) {
ERXLogger.log.debug("log4j.loggerFactory: " + System.getProperty("log4j.loggerFactory"));
ERXLogger.log.debug("Factory: " + ERXLogger.factory);
// MS: This just trips everyone up, and it really seems to only be
// used by PW developers, so I say we just turn it on when we need it.
// log.debug("", new RuntimeException(
// "This is not a real exception. It is just to show you where logging was initialized."
// ));
}
// PropertyPrinter printer = new PropertyPrinter(new
// PrintWriter(System.out));
// printer.print(new PrintWriter(System.out));
if (ERXLogger.factory != null) {
ERXLogger.factory.loggingConfigurationDidChange();
}
NSNotificationCenter.defaultCenter().postNotification(ERXConfigurationManager.ConfigurationDidChangeNotification, null);
}
/**
* Dumps an Throwable's Stack trace on the appender if debugging is enabled.
*
* @param throwable
* throwable to dump
*/
public void debugStackTrace(Throwable throwable) {
if (isDebugEnabled()) {
throwable.printStackTrace();
}
}
}