--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+// Contributors: "Luke Blanshard" <Luke@quiq.com>
+// "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>
+// Anders Kristensen <akristensen@dynamicsoft.com>
+
+package org.apache.log4j;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.log4j.config.PropertySetter;
+import org.apache.log4j.helpers.FileWatchdog;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.or.RendererMap;
+import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.spi.ThrowableRenderer;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.apache.log4j.spi.ErrorHandler;
+
+/**
+ Allows the configuration of log4j from an external file. See
+ <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
+ expected format.
+
+ <p>It is sometimes useful to see how log4j is reading configuration
+ files. You can enable log4j internal logging by defining the
+ <b>log4j.debug</b> variable.
+
+ <P>As of log4j version 0.8.5, at class initialization time class,
+ the file <b>log4j.properties</b> will be searched from the search
+ path used to load classes. If the file can be found, then it will
+ be fed to the {@link PropertyConfigurator#configure(java.net.URL)}
+ method.
+
+ <p>The <code>PropertyConfigurator</code> does not handle the
+ advanced configuration features supported by the {@link
+ org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as
+ support custom {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers},
+ nested appenders such as the {@link org.apache.log4j.AsyncAppender
+ AsyncAppender}, etc.
+
+ <p>All option <em>values</em> admit variable substitution. The
+ syntax of variable substitution is similar to that of Unix
+ shells. The string between an opening <b>"${"</b> and
+ closing <b>"}"</b> is interpreted as a key. The value of
+ the substituted variable can be defined as a system property or in
+ the configuration file itself. The value of the key is first
+ searched in the system properties, and if not found there, it is
+ then searched in the configuration file being parsed. The
+ corresponding value replaces the ${variableName} sequence. For
+ example, if <code>java.home</code> system property is set to
+ <code>/home/xyz</code>, then every occurrence of the sequence
+ <code>${java.home}</code> will be interpreted as
+ <code>/home/xyz</code>.
+
+
+ @author Ceki Gülcü
+ @author Anders Kristensen
+ @since 0.8.1 */
+public class PropertyConfigurator implements Configurator {
+
+ /**
+ Used internally to keep track of configured appenders.
+ */
+ protected Hashtable registry = new Hashtable(11);
+ private LoggerRepository repository;
+ protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
+
+ static final String CATEGORY_PREFIX = "log4j.category.";
+ static final String LOGGER_PREFIX = "log4j.logger.";
+ static final String FACTORY_PREFIX = "log4j.factory";
+ static final String ADDITIVITY_PREFIX = "log4j.additivity.";
+ static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
+ static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
+ static final String APPENDER_PREFIX = "log4j.appender.";
+ static final String RENDERER_PREFIX = "log4j.renderer.";
+ static final String THRESHOLD_PREFIX = "log4j.threshold";
+ private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
+ private static final String LOGGER_REF = "logger-ref";
+ private static final String ROOT_REF = "root-ref";
+ private static final String APPENDER_REF_TAG = "appender-ref";
+
+
+ /** Key for specifying the {@link org.apache.log4j.spi.LoggerFactory
+ LoggerFactory}. Currently set to "<code>log4j.loggerFactory</code>". */
+ public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
+
+ /**
+ * If property set to true, then hierarchy will be reset before configuration.
+ */
+ private static final String RESET_KEY = "log4j.reset";
+
+ static final private String INTERNAL_ROOT_NAME = "root";
+
+ /**
+ Read configuration from a file. <b>The existing configuration is
+ not cleared nor reset.</b> If you require a different behavior,
+ then call {@link LogManager#resetConfiguration
+ resetConfiguration} method before calling
+ <code>doConfigure</code>.
+
+ <p>The configuration file consists of statements in the format
+ <code>key=value</code>. The syntax of different configuration
+ elements are discussed below.
+
+ <h3>Repository-wide threshold</h3>
+
+ <p>The repository-wide threshold filters logging requests by level
+ regardless of logger. The syntax is:
+
+ <pre>
+ log4j.threshold=[level]
+ </pre>
+
+ <p>The level value can consist of the string values OFF, FATAL,
+ ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
+ custom level value can be specified in the form
+ level#classname. By default the repository-wide threshold is set
+ to the lowest possible value, namely the level <code>ALL</code>.
+ </p>
+
+
+ <h3>Appender configuration</h3>
+
+ <p>Appender configuration syntax is:
+ <pre>
+ # For appender named <i>appenderName</i>, set its class.
+ # Note: The appender name can contain dots.
+ log4j.appender.appenderName=fully.qualified.name.of.appender.class
+
+ # Set appender specific options.
+ log4j.appender.appenderName.option1=value1
+ ...
+ log4j.appender.appenderName.optionN=valueN
+ </pre>
+
+ For each named appender you can configure its {@link Layout}. The
+ syntax for configuring an appender's layout is:
+ <pre>
+ log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
+ log4j.appender.appenderName.layout.option1=value1
+ ....
+ log4j.appender.appenderName.layout.optionN=valueN
+ </pre>
+
+ The syntax for adding {@link Filter}s to an appender is:
+ <pre>
+ log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
+ log4j.appender.appenderName.filter.ID.option1=value1
+ ...
+ log4j.appender.appenderName.filter.ID.optionN=valueN
+ </pre>
+ The first line defines the class name of the filter identified by ID;
+ subsequent lines with the same ID specify filter option - value
+ pairs. Multiple filters are added to the appender in the lexicographic
+ order of IDs.
+
+ The syntax for adding an {@link ErrorHandler} to an appender is:
+ <pre>
+ log4j.appender.appenderName.errorhandler=fully.qualified.name.of.filter.class
+ log4j.appender.appenderName.errorhandler.root-ref={true|false}
+ log4j.appender.appenderName.errorhandler.logger-ref=loggerName
+ log4j.appender.appenderName.errorhandler.appender-ref=appenderName
+ log4j.appender.appenderName.errorhandler.option1=value1
+ ...
+ log4j.appender.appenderName.errorhandler.optionN=valueN
+ </pre>
+
+ <h3>Configuring loggers</h3>
+
+ <p>The syntax for configuring the root logger is:
+ <pre>
+ log4j.rootLogger=[level], appenderName, appenderName, ...
+ </pre>
+
+ <p>This syntax means that an optional <em>level</em> can be
+ supplied followed by appender names separated by commas.
+
+ <p>The level value can consist of the string values OFF, FATAL,
+ ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
+ custom level value can be specified in the form
+ <code>level#classname</code>.
+
+ <p>If a level value is specified, then the root level is set
+ to the corresponding level. If no level value is specified,
+ then the root level remains untouched.
+
+ <p>The root logger can be assigned multiple appenders.
+
+ <p>Each <i>appenderName</i> (separated by commas) will be added to
+ the root logger. The named appender is defined using the
+ appender syntax defined above.
+
+ <p>For non-root categories the syntax is almost the same:
+ <pre>
+ log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
+ </pre>
+
+ <p>The meaning of the optional level value is discussed above
+ in relation to the root logger. In addition however, the value
+ INHERITED can be specified meaning that the named logger should
+ inherit its level from the logger hierarchy.
+
+ <p>If no level value is supplied, then the level of the
+ named logger remains untouched.
+
+ <p>By default categories inherit their level from the
+ hierarchy. However, if you set the level of a logger and later
+ decide that that logger should inherit its level, then you should
+ specify INHERITED as the value for the level value. NULL is a
+ synonym for INHERITED.
+
+ <p>Similar to the root logger syntax, each <i>appenderName</i>
+ (separated by commas) will be attached to the named logger.
+
+ <p>See the <a href="../../../../manual.html#additivity">appender
+ additivity rule</a> in the user manual for the meaning of the
+ <code>additivity</code> flag.
+
+ <h3>ObjectRenderers</h3>
+
+ You can customize the way message objects of a given type are
+ converted to String before being logged. This is done by
+ specifying an {@link org.apache.log4j.or.ObjectRenderer ObjectRenderer}
+ for the object type would like to customize.
+
+ <p>The syntax is:
+
+ <pre>
+ log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
+ </pre>
+
+ As in,
+ <pre>
+ log4j.renderer.my.Fruit=my.FruitRenderer
+ </pre>
+
+ <h3>ThrowableRenderer</h3>
+
+ You can customize the way an instance of Throwable is
+ converted to String before being logged. This is done by
+ specifying an {@link org.apache.log4j.spi.ThrowableRenderer ThrowableRenderer}.
+
+ <p>The syntax is:
+
+ <pre>
+ log4j.throwableRenderer=fully.qualified.name.of.rendering.class
+ log4j.throwableRenderer.paramName=paramValue
+ </pre>
+
+ As in,
+ <pre>
+ log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer
+ </pre>
+
+ <h3>Logger Factories</h3>
+
+ The usage of custom logger factories is discouraged and no longer
+ documented.
+
+ <h3>Resetting Hierarchy</h3>
+
+ The hierarchy will be reset before configuration when
+ log4j.reset=true is present in the properties file.
+
+ <h3>Example</h3>
+
+ <p>An example configuration is given below. Other configuration
+ file examples are given in the <code>examples</code> folder.
+
+ <pre>
+
+ # Set options for appender named "A1".
+ # Appender "A1" will be a SyslogAppender
+ log4j.appender.A1=org.apache.log4j.net.SyslogAppender
+
+ # The syslog daemon resides on www.abc.net
+ log4j.appender.A1.SyslogHost=www.abc.net
+
+ # A1's layout is a PatternLayout, using the conversion pattern
+ # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
+ # include # the relative time since the start of the application in
+ # milliseconds, followed by the level of the log request,
+ # followed by the two rightmost components of the logger name,
+ # followed by the callers method name, followed by the line number,
+ # the nested diagnostic context and finally the message itself.
+ # Refer to the documentation of {@link PatternLayout} for further information
+ # on the syntax of the ConversionPattern key.
+ log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+ log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
+
+ # Set options for appender named "A2"
+ # A2 should be a RollingFileAppender, with maximum file size of 10 MB
+ # using at most one backup file. A2's layout is TTCC, using the
+ # ISO8061 date format with context printing enabled.
+ log4j.appender.A2=org.apache.log4j.RollingFileAppender
+ log4j.appender.A2.MaxFileSize=10MB
+ log4j.appender.A2.MaxBackupIndex=1
+ log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
+ log4j.appender.A2.layout.ContextPrinting=enabled
+ log4j.appender.A2.layout.DateFormat=ISO8601
+
+ # Root logger set to DEBUG using the A2 appender defined above.
+ log4j.rootLogger=DEBUG, A2
+
+ # Logger definitions:
+ # The SECURITY logger inherits is level from root. However, it's output
+ # will go to A1 appender defined above. It's additivity is non-cumulative.
+ log4j.logger.SECURITY=INHERIT, A1
+ log4j.additivity.SECURITY=false
+
+ # Only warnings or above will be logged for the logger "SECURITY.access".
+ # Output will go to A1.
+ log4j.logger.SECURITY.access=WARN
+
+
+ # The logger "class.of.the.day" inherits its level from the
+ # logger hierarchy. Output will go to the appender's of the root
+ # logger, A2 in this case.
+ log4j.logger.class.of.the.day=INHERIT
+ </pre>
+
+ <p>Refer to the <b>setOption</b> method in each Appender and
+ Layout for class specific options.
+
+ <p>Use the <code>#</code> or <code>!</code> characters at the
+ beginning of a line for comments.
+
+ <p>
+ @param configFileName The name of the configuration file where the
+ configuration information is stored.
+
+ */
+ public
+ void doConfigure(String configFileName, LoggerRepository hierarchy) {
+ Properties props = new Properties();
+ FileInputStream istream = null;
+ try {
+ istream = new FileInputStream(configFileName);
+ props.load(istream);
+ istream.close();
+ }
+ catch (Exception e) {
+ if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ LogLog.error("Could not read configuration file ["+configFileName+"].", e);
+ LogLog.error("Ignoring configuration file [" + configFileName+"].");
+ return;
+ } finally {
+ if(istream != null) {
+ try {
+ istream.close();
+ } catch(InterruptedIOException ignore) {
+ Thread.currentThread().interrupt();
+ } catch(Throwable ignore) {
+ }
+
+ }
+ }
+ // If we reach here, then the config file is alright.
+ doConfigure(props, hierarchy);
+ }
+
+ /**
+ */
+ static
+ public
+ void configure(String configFilename) {
+ new PropertyConfigurator().doConfigure(configFilename,
+ LogManager.getLoggerRepository());
+ }
+
+ /**
+ Read configuration options from url <code>configURL</code>.
+
+ @since 0.8.2
+*/
+public
+static
+void configure(java.net.URL configURL) {
+ new PropertyConfigurator().doConfigure(configURL,
+ LogManager.getLoggerRepository());
+}
+
+/**
+Reads configuration options from an InputStream.
+
+@since 1.2.17
+*/
+public
+static
+void configure(InputStream inputStream) {
+new PropertyConfigurator().doConfigure(inputStream,
+ LogManager.getLoggerRepository());
+}
+
+
+ /**
+ Read configuration options from <code>properties</code>.
+
+ See {@link #doConfigure(String, LoggerRepository)} for the expected format.
+ */
+ static
+ public
+ void configure(Properties properties) {
+ new PropertyConfigurator().doConfigure(properties,
+ LogManager.getLoggerRepository());
+ }
+
+ /**
+ Like {@link #configureAndWatch(String, long)} except that the
+ default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
+ used.
+
+ @param configFilename A file in key=value format.
+
+ */
+ static
+ public
+ void configureAndWatch(String configFilename) {
+ configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
+ }
+
+
+ /**
+ Read the configuration file <code>configFilename</code> if it
+ exists. Moreover, a thread will be created that will periodically
+ check if <code>configFilename</code> has been created or
+ modified. The period is determined by the <code>delay</code>
+ argument. If a change or file creation is detected, then
+ <code>configFilename</code> is read to configure log4j.
+
+ @param configFilename A file in key=value format.
+ @param delay The delay in milliseconds to wait between each check.
+ */
+ static
+ public
+ void configureAndWatch(String configFilename, long delay) {
+ PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
+ pdog.setDelay(delay);
+ pdog.start();
+ }
+
+
+ /**
+ Read configuration options from <code>properties</code>.
+
+ See {@link #doConfigure(String, LoggerRepository)} for the expected format.
+ */
+ public
+ void doConfigure(Properties properties, LoggerRepository hierarchy) {
+ repository = hierarchy;
+ String value = properties.getProperty(LogLog.DEBUG_KEY);
+ if(value == null) {
+ value = properties.getProperty("log4j.configDebug");
+ if(value != null) {
+ LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
+ }
+ }
+
+ if(value != null) {
+ LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
+ }
+
+ //
+ // if log4j.reset=true then
+ // reset hierarchy
+ String reset = properties.getProperty(RESET_KEY);
+ if (reset != null && OptionConverter.toBoolean(reset, false)) {
+ hierarchy.resetConfiguration();
+ }
+
+ String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
+ properties);
+ if(thresholdStr != null) {
+ hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
+ Level.ALL));
+ LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
+ }
+
+ configureRootCategory(properties, hierarchy);
+ configureLoggerFactory(properties);
+ parseCatsAndRenderers(properties, hierarchy);
+
+ LogLog.debug("Finished configuring.");
+ // We don't want to hold references to appenders preventing their
+ // garbage collection.
+ registry.clear();
+ }
+
+ /**
+ * Read configuration options from an InputStream.
+ *
+ * @since 1.2.17
+ */
+ public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) {
+ Properties props = new Properties();
+ try {
+ props.load(inputStream);
+ } catch (IOException e) {
+ if (e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LogLog.error("Could not read configuration file from InputStream [" + inputStream
+ + "].", e);
+ LogLog.error("Ignoring configuration InputStream [" + inputStream +"].");
+ return;
+ }
+ this.doConfigure(props, hierarchy);
+ }
+
+ /**
+ Read configuration options from url <code>configURL</code>.
+ */
+ public
+ void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
+ Properties props = new Properties();
+ LogLog.debug("Reading configuration from URL " + configURL);
+ InputStream istream = null;
+ URLConnection uConn = null;
+ try {
+ uConn = configURL.openConnection();
+ uConn.setUseCaches(false);
+ istream = uConn.getInputStream();
+ props.load(istream);
+ }
+ catch (Exception e) {
+ if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ LogLog.error("Could not read configuration file from URL [" + configURL
+ + "].", e);
+ LogLog.error("Ignoring configuration file [" + configURL +"].");
+ return;
+ }
+ finally {
+ if (istream != null) {
+ try {
+ istream.close();
+ } catch(InterruptedIOException ignore) {
+ Thread.currentThread().interrupt();
+ } catch(IOException ignore) {
+ } catch(RuntimeException ignore) {
+ }
+ }
+ }
+ doConfigure(props, hierarchy);
+ }
+
+
+ // --------------------------------------------------------------------------
+ // Internal stuff
+ // --------------------------------------------------------------------------
+
+ /**
+ Check the provided <code>Properties</code> object for a
+ {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
+ entry specified by {@link #LOGGER_FACTORY_KEY}. If such an entry
+ exists, an attempt is made to create an instance using the default
+ constructor. This instance is used for subsequent Category creations
+ within this configurator.
+
+ @see #parseCatsAndRenderers
+ */
+ protected void configureLoggerFactory(Properties props) {
+ String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
+ props);
+ if(factoryClassName != null) {
+ LogLog.debug("Setting category factory to ["+factoryClassName+"].");
+ loggerFactory = (LoggerFactory)
+ OptionConverter.instantiateByClassName(factoryClassName,
+ LoggerFactory.class,
+ loggerFactory);
+ PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
+ }
+ }
+
+ /*
+ void configureOptionHandler(OptionHandler oh, String prefix,
+ Properties props) {
+ String[] options = oh.getOptionStrings();
+ if(options == null)
+ return;
+
+ String value;
+ for(int i = 0; i < options.length; i++) {
+ value = OptionConverter.findAndSubst(prefix + options[i], props);
+ LogLog.debug(
+ "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"].");
+ // Some option handlers assume that null value are not passed to them.
+ // So don't remove this check
+ if(value != null) {
+ oh.setOption(options[i], value);
+ }
+ }
+ oh.activateOptions();
+ }
+ */
+
+
+ void configureRootCategory(Properties props, LoggerRepository hierarchy) {
+ String effectiveFrefix = ROOT_LOGGER_PREFIX;
+ String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
+
+ if(value == null) {
+ value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
+ effectiveFrefix = ROOT_CATEGORY_PREFIX;
+ }
+
+ if(value == null) {
+ LogLog.debug("Could not find root logger information. Is this OK?");
+ } else {
+ Logger root = hierarchy.getRootLogger();
+ synchronized(root) {
+ parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
+ }
+ }
+ }
+
+
+ /**
+ Parse non-root elements, such non-root categories and renderers.
+ */
+ protected
+ void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
+ Enumeration enumeration = props.propertyNames();
+ while(enumeration.hasMoreElements()) {
+ String key = (String) enumeration.nextElement();
+ if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
+ String loggerName = null;
+ if(key.startsWith(CATEGORY_PREFIX)) {
+ loggerName = key.substring(CATEGORY_PREFIX.length());
+ } else if(key.startsWith(LOGGER_PREFIX)) {
+ loggerName = key.substring(LOGGER_PREFIX.length());
+ }
+ String value = OptionConverter.findAndSubst(key, props);
+ Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
+ synchronized(logger) {
+ parseCategory(props, logger, key, loggerName, value);
+ parseAdditivityForLogger(props, logger, loggerName);
+ }
+ } else if(key.startsWith(RENDERER_PREFIX)) {
+ String renderedClass = key.substring(RENDERER_PREFIX.length());
+ String renderingClass = OptionConverter.findAndSubst(key, props);
+ if(hierarchy instanceof RendererSupport) {
+ RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
+ renderingClass);
+ }
+ } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
+ if (hierarchy instanceof ThrowableRendererSupport) {
+ ThrowableRenderer tr = (ThrowableRenderer)
+ OptionConverter.instantiateByKey(props,
+ THROWABLE_RENDERER_PREFIX,
+ org.apache.log4j.spi.ThrowableRenderer.class,
+ null);
+ if(tr == null) {
+ LogLog.error(
+ "Could not instantiate throwableRenderer.");
+ } else {
+ PropertySetter setter = new PropertySetter(tr);
+ setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
+ ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
+
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ Parse the additivity option for a non-root category.
+ */
+ void parseAdditivityForLogger(Properties props, Logger cat,
+ String loggerName) {
+ String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
+ props);
+ LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
+ // touch additivity only if necessary
+ if((value != null) && (!value.equals(""))) {
+ boolean additivity = OptionConverter.toBoolean(value, true);
+ LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
+ additivity);
+ cat.setAdditivity(additivity);
+ }
+ }
+
+ /**
+ This method must work for the root category as well.
+ */
+ void parseCategory(Properties props, Logger logger, String optionKey,
+ String loggerName, String value) {
+
+ LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
+ // We must skip over ',' but not white space
+ StringTokenizer st = new StringTokenizer(value, ",");
+
+ // If value is not in the form ", appender.." or "", then we should set
+ // the level of the loggeregory.
+
+ if(!(value.startsWith(",") || value.equals(""))) {
+
+ // just to be on the safe side...
+ if(!st.hasMoreTokens()) {
+ return;
+ }
+
+ String levelStr = st.nextToken();
+ LogLog.debug("Level token is [" + levelStr + "].");
+
+ // If the level value is inherited, set category level value to
+ // null. We also check that the user has not specified inherited for the
+ // root category.
+ if(INHERITED.equalsIgnoreCase(levelStr) ||
+ NULL.equalsIgnoreCase(levelStr)) {
+ if(loggerName.equals(INTERNAL_ROOT_NAME)) {
+ LogLog.warn("The root logger cannot be set to null.");
+ } else {
+ logger.setLevel(null);
+ }
+ } else {
+ logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG));
+ }
+ LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
+ }
+
+ // Begin by removing all existing appenders.
+ logger.removeAllAppenders();
+
+ Appender appender;
+ String appenderName;
+ while(st.hasMoreTokens()) {
+ appenderName = st.nextToken().trim();
+ if(appenderName == null || appenderName.equals(",")) {
+ continue;
+ }
+ LogLog.debug("Parsing appender named \"" + appenderName +"\".");
+ appender = parseAppender(props, appenderName);
+ if(appender != null) {
+ logger.addAppender(appender);
+ }
+ }
+ }
+
+ Appender parseAppender(Properties props, String appenderName) {
+ Appender appender = registryGet(appenderName);
+ if((appender != null)) {
+ LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
+ return appender;
+ }
+ // Appender was not previously initialized.
+ String prefix = APPENDER_PREFIX + appenderName;
+ String layoutPrefix = prefix + ".layout";
+
+ appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
+ org.apache.log4j.Appender.class,
+ null);
+ if(appender == null) {
+ LogLog.error(
+ "Could not instantiate appender named \"" + appenderName+"\".");
+ return null;
+ }
+ appender.setName(appenderName);
+
+ if(appender instanceof OptionHandler) {
+ if(appender.requiresLayout()) {
+ Layout layout = (Layout) OptionConverter.instantiateByKey(props,
+ layoutPrefix,
+ Layout.class,
+ null);
+ if(layout != null) {
+ appender.setLayout(layout);
+ LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
+ //configureOptionHandler(layout, layoutPrefix + ".", props);
+ PropertySetter.setProperties(layout, props, layoutPrefix + ".");
+ LogLog.debug("End of parsing for \"" + appenderName +"\".");
+ }
+ }
+ final String errorHandlerPrefix = prefix + ".errorhandler";
+ String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
+ if (errorHandlerClass != null) {
+ ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
+ errorHandlerPrefix,
+ ErrorHandler.class,
+ null);
+ if (eh != null) {
+ appender.setErrorHandler(eh);
+ LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
+ parseErrorHandler(eh, errorHandlerPrefix, props, repository);
+ final Properties edited = new Properties();
+ final String[] keys = new String[] {
+ errorHandlerPrefix + "." + ROOT_REF,
+ errorHandlerPrefix + "." + LOGGER_REF,
+ errorHandlerPrefix + "." + APPENDER_REF_TAG
+ };
+ for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ int i = 0;
+ for(; i < keys.length; i++) {
+ if(keys[i].equals(entry.getKey())) {
+ break;
+ }
+ }
+ if (i == keys.length) {
+ edited.put(entry.getKey(), entry.getValue());
+ }
+ }
+ PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
+ LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
+ }
+
+ }
+ //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
+ PropertySetter.setProperties(appender, props, prefix + ".");
+ LogLog.debug("Parsed \"" + appenderName +"\" options.");
+ }
+ parseAppenderFilters(props, appenderName, appender);
+ registryPut(appender);
+ return appender;
+ }
+
+ private void parseErrorHandler(
+ final ErrorHandler eh,
+ final String errorHandlerPrefix,
+ final Properties props,
+ final LoggerRepository hierarchy) {
+ boolean rootRef = OptionConverter.toBoolean(
+ OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
+ if (rootRef) {
+ eh.setLogger(hierarchy.getRootLogger());
+ }
+ String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props);
+ if (loggerName != null) {
+ Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
+ : hierarchy.getLogger(loggerName, loggerFactory);
+ eh.setLogger(logger);
+ }
+ String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
+ if (appenderName != null) {
+ Appender backup = parseAppender(props, appenderName);
+ if (backup != null) {
+ eh.setBackupAppender(backup);
+ }
+ }
+ }
+
+
+ void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
+ // extract filters and filter options from props into a hashtable mapping
+ // the property name defining the filter class to a list of pre-parsed
+ // name-value pairs associated to that filter
+ final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
+ int fIdx = filterPrefix.length();
+ Hashtable filters = new Hashtable();
+ Enumeration e = props.keys();
+ String name = "";
+ while (e.hasMoreElements()) {
+ String key = (String) e.nextElement();
+ if (key.startsWith(filterPrefix)) {
+ int dotIdx = key.indexOf('.', fIdx);
+ String filterKey = key;
+ if (dotIdx != -1) {
+ filterKey = key.substring(0, dotIdx);
+ name = key.substring(dotIdx+1);
+ }
+ Vector filterOpts = (Vector) filters.get(filterKey);
+ if (filterOpts == null) {
+ filterOpts = new Vector();
+ filters.put(filterKey, filterOpts);
+ }
+ if (dotIdx != -1) {
+ String value = OptionConverter.findAndSubst(key, props);
+ filterOpts.add(new NameValue(name, value));
+ }
+ }
+ }
+
+ // sort filters by IDs, insantiate filters, set filter options,
+ // add filters to the appender
+ Enumeration g = new SortedKeyEnumeration(filters);
+ while (g.hasMoreElements()) {
+ String key = (String) g.nextElement();
+ String clazz = props.getProperty(key);
+ if (clazz != null) {
+ LogLog.debug("Filter key: ["+key+"] class: ["+props.getProperty(key) +"] props: "+filters.get(key));
+ Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
+ if (filter != null) {
+ PropertySetter propSetter = new PropertySetter(filter);
+ Vector v = (Vector)filters.get(key);
+ Enumeration filterProps = v.elements();
+ while (filterProps.hasMoreElements()) {
+ NameValue kv = (NameValue)filterProps.nextElement();
+ propSetter.setProperty(kv.key, kv.value);
+ }
+ propSetter.activate();
+ LogLog.debug("Adding filter of type ["+filter.getClass()
+ +"] to appender named ["+appender.getName()+"].");
+ appender.addFilter(filter);
+ }
+ } else {
+ LogLog.warn("Missing class definition for filter: ["+key+"]");
+ }
+ }
+ }
+
+
+ void registryPut(Appender appender) {
+ registry.put(appender.getName(), appender);
+ }
+
+ Appender registryGet(String name) {
+ return (Appender) registry.get(name);
+ }
+}
+
+class PropertyWatchdog extends FileWatchdog {
+
+ PropertyWatchdog(String filename) {
+ super(filename);
+ }
+
+ /**
+ Call {@link PropertyConfigurator#configure(String)} with the
+ <code>filename</code> to reconfigure log4j. */
+ public
+ void doOnChange() {
+ new PropertyConfigurator().doConfigure(filename,
+ LogManager.getLoggerRepository());
+ }
+}
+
+class NameValue {
+ String key, value;
+ public NameValue(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+ public String toString() {
+ return key + "=" + value;
+ }
+}
+
+class SortedKeyEnumeration implements Enumeration {
+
+ private Enumeration e;
+
+ public SortedKeyEnumeration(Hashtable ht) {
+ Enumeration f = ht.keys();
+ Vector keys = new Vector(ht.size());
+ for (int i, last = 0; f.hasMoreElements(); ++last) {
+ String key = (String) f.nextElement();
+ for (i = 0; i < last; ++i) {
+ String s = (String) keys.get(i);
+ if (key.compareTo(s) <= 0) {
+ break;
+ }
+ }
+ keys.add(i, key);
+ }
+ e = keys.elements();
+ }
+
+ public boolean hasMoreElements() {
+ return e.hasMoreElements();
+ }
+
+ public Object nextElement() {
+ return e.nextElement();
+ }
+}