View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18
    19  // Contibutors: "Luke Blanshard" <Luke@quiq.com>
    20  //              "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>
    21  //               Anders Kristensen <akristensen@dynamicsoft.com>
    22
    23  package org.apache.log4j;
    24
    25  import java.io.FileInputStream;
    26  import java.io.IOException;
    27  import java.io.InputStream;
    28  import java.io.InterruptedIOException;
    29  import java.net.URLConnection;
    30  import java.util.Enumeration;
    31  import java.util.Hashtable;
    32  import java.util.Properties;
    33  import java.util.StringTokenizer;
    34  import java.util.Vector;
    35  import java.util.Iterator;
    36  import java.util.Map;
    37
    38  import org.apache.log4j.config.PropertySetter;
    39  import org.apache.log4j.helpers.FileWatchdog;
    40  import org.apache.log4j.helpers.LogLog;
    41  import org.apache.log4j.helpers.OptionConverter;
    42  import org.apache.log4j.or.RendererMap;
    43  import org.apache.log4j.spi.Configurator;
    44  import org.apache.log4j.spi.Filter;
    45  import org.apache.log4j.spi.LoggerFactory;
    46  import org.apache.log4j.spi.LoggerRepository;
    47  import org.apache.log4j.spi.OptionHandler;
    48  import org.apache.log4j.spi.RendererSupport;
    49  import org.apache.log4j.spi.ThrowableRenderer;
    50  import org.apache.log4j.spi.ThrowableRendererSupport;
    51  import org.apache.log4j.spi.ErrorHandler;
    52
    53  /**
    54     Allows the configuration of log4j from an external file.  See
    55     <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
    56     expected format.
    57
    58     <p>It is sometimes useful to see how log4j is reading configuration
    59     files. You can enable log4j internal logging by defining the
    60     <b>log4j.debug</b> variable.
    61
    62     <P>As of log4j version 0.8.5, at class initialization time class,
    63     the file <b>log4j.properties</b> will be searched from the search
    64     path used to load classes. If the file can be found, then it will
    65     be fed to the {@link PropertyConfigurator#configure(java.net.URL)}
    66     method.
    67
    68     <p>The <code>PropertyConfigurator</code> does not handle the
    69     advanced configuration features supported by the {@link
    70     org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as
    71     support custom {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers},
    72     nested appenders such as the {@link org.apache.log4j.AsyncAppender
    73     AsyncAppender}, etc.
    74
    75     <p>All option <em>values</em> admit variable substitution. The
    76     syntax of variable substitution is similar to that of Unix
    77     shells. The string between an opening <b>&quot;${&quot;</b> and
    78     closing <b>&quot;}&quot;</b> is interpreted as a key. The value of
    79     the substituted variable can be defined as a system property or in
    80     the configuration file itself. The value of the key is first
    81     searched in the system properties, and if not found there, it is
    82     then searched in the configuration file being parsed.  The
    83     corresponding value replaces the ${variableName} sequence. For
    84     example, if <code>java.home</code> system property is set to
    85     <code>/home/xyz</code>, then every occurrence of the sequence
    86     <code>${java.home}</code> will be interpreted as
    87     <code>/home/xyz</code>.
    88
    89
    90     @author Ceki G&uuml;lc&uuml;
    91     @author Anders Kristensen
    92     @since 0.8.1 */
    93  public class PropertyConfigurator implements Configurator {
    94
    95    /**
    96       Used internally to keep track of configured appenders.
    97     */
    98    protected Hashtable registry = new Hashtable(11);
    99    private LoggerRepository repository;
    100   protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
    101
    102   static final String      CATEGORY_PREFIX = "log4j.category.";
    103   static final String      LOGGER_PREFIX   = "log4j.logger.";
    104   static final String       FACTORY_PREFIX = "log4j.factory";
    105   static final String    ADDITIVITY_PREFIX = "log4j.additivity.";
    106   static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
    107   static final String ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
    108   static final String      APPENDER_PREFIX = "log4j.appender.";
    109   static final String      RENDERER_PREFIX = "log4j.renderer.";
    110   static final String      THRESHOLD_PREFIX = "log4j.threshold";
    111   private static final String      THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
    112   private static final String LOGGER_REF	= "logger-ref";
    113   private static final String ROOT_REF		= "root-ref";
    114   private static final String APPENDER_REF_TAG 	= "appender-ref";
    115
    116
    117   /** Key for specifying the {@link org.apache.log4j.spi.LoggerFactory
    118       LoggerFactory}.  Currently set to "<code>log4j.loggerFactory</code>".  */
    119   public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
    120
    121     /**
    122      * If property set to true, then hierarchy will be reset before configuration.
    123      */
    124   private static final String RESET_KEY = "log4j.reset";
    125
    126   static final private String INTERNAL_ROOT_NAME = "root";
    127
    128   /**
    129     Read configuration from a file. <b>The existing configuration is
    130     not cleared nor reset.</b> If you require a different behavior,
    131     then call {@link  LogManager#resetConfiguration
    132     resetConfiguration} method before calling
    133     <code>doConfigure</code>.
    134
    135     <p>The configuration file consists of statements in the format
    136     <code>key=value</code>. The syntax of different configuration
    137     elements are discussed below.
    138
    139     <h3>Repository-wide threshold</h3>
    140
    141     <p>The repository-wide threshold filters logging requests by level
    142     regardless of logger. The syntax is:
    143
    144     <pre>
    145     log4j.threshold=[level]
    146     </pre>
    147
    148     <p>The level value can consist of the string values OFF, FATAL,
    149     ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
    150     custom level value can be specified in the form
    151     level#classname. By default the repository-wide threshold is set
    152     to the lowest possible value, namely the level <code>ALL</code>.
    153     </p>
    154
    155
    156     <h3>Appender configuration</h3>
    157
    158     <p>Appender configuration syntax is:
    159     <pre>
    160     # For appender named <i>appenderName</i>, set its class.
    161     # Note: The appender name can contain dots.
    162     log4j.appender.appenderName=fully.qualified.name.of.appender.class
    163
    164     # Set appender specific options.
    165     log4j.appender.appenderName.option1=value1
    166     ...
    167     log4j.appender.appenderName.optionN=valueN
    168     </pre>
    169
    170     For each named appender you can configure its {@link Layout}. The
    171     syntax for configuring an appender's layout is:
    172     <pre>
    173     log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
    174     log4j.appender.appenderName.layout.option1=value1
    175     ....
    176     log4j.appender.appenderName.layout.optionN=valueN
    177     </pre>
    178
    179     The syntax for adding {@link Filter}s to an appender is:
    180     <pre>
    181     log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
    182     log4j.appender.appenderName.filter.ID.option1=value1
    183     ...
    184     log4j.appender.appenderName.filter.ID.optionN=valueN
    185     </pre>
    186     The first line defines the class name of the filter identified by ID;
    187     subsequent lines with the same ID specify filter option - value
    188     paris. Multiple filters are added to the appender in the lexicographic
    189     order of IDs.
    190
    191     The syntax for adding an {@link ErrorHandler} to an appender is:
    192     <pre>
    193     log4j.appender.appenderName.errorhandler=fully.qualified.name.of.filter.class
    194     log4j.appender.appenderName.errorhandler.root-ref={true|false}
    195     log4j.appender.appenderName.errorhandler.logger-ref=loggerName
    196     log4j.appender.appenderName.errorhandler.appender-ref=appenderName
    197     log4j.appender.appenderName.errorhandler.option1=value1
    198     ...
    199     log4j.appender.appenderName.errorhandler.optionN=valueN
    200     </pre>
    201
    202     <h3>Configuring loggers</h3>
    203
    204     <p>The syntax for configuring the root logger is:
    205     <pre>
    206       log4j.rootLogger=[level], appenderName, appenderName, ...
    207     </pre>
    208
    209     <p>This syntax means that an optional <em>level</em> can be
    210     supplied followed by appender names separated by commas.
    211
    212     <p>The level value can consist of the string values OFF, FATAL,
    213     ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
    214     custom level value can be specified in the form
    215     <code>level#classname</code>.
    216
    217     <p>If a level value is specified, then the root level is set
    218     to the corresponding level.  If no level value is specified,
    219     then the root level remains untouched.
    220
    221     <p>The root logger can be assigned multiple appenders.
    222
    223     <p>Each <i>appenderName</i> (separated by commas) will be added to
    224     the root logger. The named appender is defined using the
    225     appender syntax defined above.
    226
    227     <p>For non-root categories the syntax is almost the same:
    228     <pre>
    229     log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
    230     </pre>
    231
    232     <p>The meaning of the optional level value is discussed above
    233     in relation to the root logger. In addition however, the value
    234     INHERITED can be specified meaning that the named logger should
    235     inherit its level from the logger hierarchy.
    236
    237     <p>If no level value is supplied, then the level of the
    238     named logger remains untouched.
    239
    240     <p>By default categories inherit their level from the
    241     hierarchy. However, if you set the level of a logger and later
    242     decide that that logger should inherit its level, then you should
    243     specify INHERITED as the value for the level value. NULL is a
    244     synonym for INHERITED.
    245
    246     <p>Similar to the root logger syntax, each <i>appenderName</i>
    247     (separated by commas) will be attached to the named logger.
    248
    249     <p>See the <a href="../../../../manual.html#additivity">appender
    250     additivity rule</a> in the user manual for the meaning of the
    251     <code>additivity</code> flag.
    252
    253     <h3>ObjectRenderers</h3>
    254
    255     You can customize the way message objects of a given type are
    256     converted to String before being logged. This is done by
    257     specifying an {@link org.apache.log4j.or.ObjectRenderer ObjectRenderer}
    258     for the object type would like to customize.
    259
    260     <p>The syntax is:
    261
    262     <pre>
    263     log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
    264     </pre>
    265
    266     As in,
    267     <pre>
    268     log4j.renderer.my.Fruit=my.FruitRenderer
    269     </pre>
    270
    271    <h3>ThrowableRenderer</h3>
    272
    273    You can customize the way an instance of Throwable is
    274    converted to String before being logged. This is done by
    275    specifying an {@link org.apache.log4j.spi.ThrowableRenderer ThrowableRenderer}.
    276
    277    <p>The syntax is:
    278
    279    <pre>
    280    log4j.throwableRenderer=fully.qualified.name.of.rendering.class
    281    log4j.throwableRenderer.paramName=paramValue
    282    </pre>
    283
    284    As in,
    285    <pre>
    286    log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer
    287    </pre>
    288
    289     <h3>Logger Factories</h3>
    290
    291     The usage of custom logger factories is discouraged and no longer
    292     documented.
    293
    294     <h3>Resetting Hierarchy</h3>
    295
    296     The hierarchy will be reset before configuration when
    297     log4j.reset=true is present in the properties file.
    298
    299     <h3>Example</h3>
    300
    301     <p>An example configuration is given below. Other configuration
    302     file examples are given in the <code>examples</code> folder.
    303
    304     <pre>
    305
    306     # Set options for appender named "A1".
    307     # Appender "A1" will be a SyslogAppender
    308     log4j.appender.A1=org.apache.log4j.net.SyslogAppender
    309
    310     # The syslog daemon resides on www.abc.net
    311     log4j.appender.A1.SyslogHost=www.abc.net
    312
    313     # A1's layout is a PatternLayout, using the conversion pattern
    314     # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
    315     # include # the relative time since the start of the application in
    316     # milliseconds, followed by the level of the log request,
    317     # followed by the two rightmost components of the logger name,
    318     # followed by the callers method name, followed by the line number,
    319     # the nested disgnostic context and finally the message itself.
    320     # Refer to the documentation of {@link PatternLayout} for further information
    321     # on the syntax of the ConversionPattern key.
    322     log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    323     log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
    324
    325     # Set options for appender named "A2"
    326     # A2 should be a RollingFileAppender, with maximum file size of 10 MB
    327     # using at most one backup file. A2's layout is TTCC, using the
    328     # ISO8061 date format with context printing enabled.
    329     log4j.appender.A2=org.apache.log4j.RollingFileAppender
    330     log4j.appender.A2.MaxFileSize=10MB
    331     log4j.appender.A2.MaxBackupIndex=1
    332     log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
    333     log4j.appender.A2.layout.ContextPrinting=enabled
    334     log4j.appender.A2.layout.DateFormat=ISO8601
    335
    336     # Root logger set to DEBUG using the A2 appender defined above.
    337     log4j.rootLogger=DEBUG, A2
    338
    339     # Logger definitions:
    340     # The SECURITY logger inherits is level from root. However, it's output
    341     # will go to A1 appender defined above. It's additivity is non-cumulative.
    342     log4j.logger.SECURITY=INHERIT, A1
    343     log4j.additivity.SECURITY=false
    344
    345     # Only warnings or above will be logged for the logger "SECURITY.access".
    346     # Output will go to A1.
    347     log4j.logger.SECURITY.access=WARN
    348
    349
    350     # The logger "class.of.the.day" inherits its level from the
    351     # logger hierarchy.  Output will go to the appender's of the root
    352     # logger, A2 in this case.
    353     log4j.logger.class.of.the.day=INHERIT
    354     </pre>
    355
    356     <p>Refer to the <b>setOption</b> method in each Appender and
    357     Layout for class specific options.
    358
    359     <p>Use the <code>#</code> or <code>!</code> characters at the
    360     beginning of a line for comments.
    361
    362    <p>
    363    @param configFileName The name of the configuration file where the
    364    configuration information is stored.
    365
    366   */
    367   public
    368   void doConfigure(String configFileName, LoggerRepository hierarchy) {
    369     Properties props = new Properties();
    370     FileInputStream istream = null;
    371     try {
    372       istream = new FileInputStream(configFileName);
    373       props.load(istream);
    374       istream.close();
    375     }
    376     catch (Exception e) {
    377       if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
    378           Thread.currentThread().interrupt();
    379       }
    380       LogLog.error("Could not read configuration file ["+configFileName+"].", e);
    381       LogLog.error("Ignoring configuration file [" + configFileName+"].");
    382       return;
    383     } finally {
    384         if(istream != null) {
    385             try {
    386                 istream.close();
    387             } catch(InterruptedIOException ignore) {
    388                 Thread.currentThread().interrupt();
    389             } catch(Throwable ignore) {
    390             }
    391
    392         }
    393     }
    394     // If we reach here, then the config file is alright.
    395     doConfigure(props, hierarchy);
    396   }
    397
    398   /**
    399    */
    400   static
    401   public
    402   void configure(String configFilename) {
    403     new PropertyConfigurator().doConfigure(configFilename,
    404 					   LogManager.getLoggerRepository());
    405   }
    406
    407   /**
    408      Read configuration options from url <code>configURL</code>.
    409
    410      @since 0.8.2
    411    */
    412   public
    413   static
    414   void configure(java.net.URL configURL) {
    415     new PropertyConfigurator().doConfigure(configURL,
    416 					   LogManager.getLoggerRepository());
    417   }
    418
    419
    420   /**
    421      Read configuration options from <code>properties</code>.
    422
    423      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
    424   */
    425   static
    426   public
    427   void configure(Properties properties) {
    428     new PropertyConfigurator().doConfigure(properties,
    429 					   LogManager.getLoggerRepository());
    430   }
    431
    432   /**
    433      Like {@link #configureAndWatch(String, long)} except that the
    434      default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
    435      used.
    436
    437      @param configFilename A file in key=value format.
    438
    439   */
    440   static
    441   public
    442   void configureAndWatch(String configFilename) {
    443     configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
    444   }
    445
    446
    447   /**
    448      Read the configuration file <code>configFilename</code> if it
    449      exists. Moreover, a thread will be created that will periodically
    450      check if <code>configFilename</code> has been created or
    451      modified. The period is determined by the <code>delay</code>
    452      argument. If a change or file creation is detected, then
    453      <code>configFilename</code> is read to configure log4j.
    454
    455       @param configFilename A file in key=value format.
    456       @param delay The delay in milliseconds to wait between each check.
    457   */
    458   static
    459   public
    460   void configureAndWatch(String configFilename, long delay) {
    461     PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
    462     pdog.setDelay(delay);
    463     pdog.start();
    464   }
    465
    466
    467   /**
    468      Read configuration options from <code>properties</code>.
    469
    470      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
    471   */
    472   public
    473   void doConfigure(Properties properties, LoggerRepository hierarchy) {
    474 	repository = hierarchy;
    475     String value = properties.getProperty(LogLog.DEBUG_KEY);
    476     if(value == null) {
    477       value = properties.getProperty("log4j.configDebug");
    478       if(value != null)
    479 	LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
    480     }
    481
    482     if(value != null) {
    483       LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
    484     }
    485
    486       //
    487       //   if log4j.reset=true then
    488       //        reset hierarchy
    489     String reset = properties.getProperty(RESET_KEY);
    490     if (reset != null && OptionConverter.toBoolean(reset, false)) {
    491           hierarchy.resetConfiguration();
    492     }
    493
    494     String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
    495 						       properties);
    496     if(thresholdStr != null) {
    497       hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
    498 						     (Level) Level.ALL));
    499       LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
    500     }
    501
    502     configureRootCategory(properties, hierarchy);
    503     configureLoggerFactory(properties);
    504     parseCatsAndRenderers(properties, hierarchy);
    505
    506     LogLog.debug("Finished configuring.");
    507     // We don't want to hold references to appenders preventing their
    508     // garbage collection.
    509     registry.clear();
    510   }
    511
    512   /**
    513      Read configuration options from url <code>configURL</code>.
    514    */
    515   public
    516   void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
    517     Properties props = new Properties();
    518     LogLog.debug("Reading configuration from URL " + configURL);
    519     InputStream istream = null;
    520     URLConnection uConn = null;
    521     try {
    522       uConn = configURL.openConnection();
    523       uConn.setUseCaches(false);
    524       istream = uConn.getInputStream();
    525       props.load(istream);
    526     }
    527     catch (Exception e) {
    528       if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
    529           Thread.currentThread().interrupt();
    530       }
    531       LogLog.error("Could not read configuration file from URL [" + configURL
    532 		   + "].", e);
    533       LogLog.error("Ignoring configuration file [" + configURL +"].");
    534       return;
    535     }
    536     finally {
    537         if (istream != null) {
    538             try {
    539                 istream.close();
    540             } catch(InterruptedIOException ignore) {
    541                 Thread.currentThread().interrupt();
    542             } catch(IOException ignore) {
    543             } catch(RuntimeException ignore) {
    544             }
    545         }
    546     }
    547     doConfigure(props, hierarchy);
    548   }
    549
    550
    551   // --------------------------------------------------------------------------
    552   // Internal stuff
    553   // --------------------------------------------------------------------------
    554
    555   /**
    556      Check the provided <code>Properties</code> object for a
    557      {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
    558      entry specified by {@link #LOGGER_FACTORY_KEY}.  If such an entry
    559      exists, an attempt is made to create an instance using the default
    560      constructor.  This instance is used for subsequent Category creations
    561      within this configurator.
    562
    563      @see #parseCatsAndRenderers
    564    */
    565   protected void configureLoggerFactory(Properties props) {
    566     String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
    567 							   props);
    568     if(factoryClassName != null) {
    569       LogLog.debug("Setting category factory to ["+factoryClassName+"].");
    570       loggerFactory = (LoggerFactory)
    571 	          OptionConverter.instantiateByClassName(factoryClassName,
    572 							 LoggerFactory.class,
    573 							 loggerFactory);
    574       PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
    575     }
    576   }
    577
    578   /*
    579   void configureOptionHandler(OptionHandler oh, String prefix,
    580 			      Properties props) {
    581     String[] options = oh.getOptionStrings();
    582     if(options == null)
    583       return;
    584
    585     String value;
    586     for(int i = 0; i < options.length; i++) {
    587       value =  OptionConverter.findAndSubst(prefix + options[i], props);
    588       LogLog.debug(
    589          "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"].");
    590       // Some option handlers assume that null value are not passed to them.
    591       // So don't remove this check
    592       if(value != null) {
    593 	oh.setOption(options[i], value);
    594       }
    595     }
    596     oh.activateOptions();
    597   }
    598   */
    599
    600
    601   void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    602     String effectiveFrefix = ROOT_LOGGER_PREFIX;
    603     String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
    604
    605     if(value == null) {
    606       value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
    607       effectiveFrefix = ROOT_CATEGORY_PREFIX;
    608     }
    609
    610     if(value == null)
    611       LogLog.debug("Could not find root logger information. Is this OK?");
    612     else {
    613       Logger root = hierarchy.getRootLogger();
    614       synchronized(root) {
    615 	parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
    616       }
    617     }
    618   }
    619
    620
    621   /**
    622      Parse non-root elements, such non-root categories and renderers.
    623   */
    624   protected
    625   void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
    626     Enumeration enumeration = props.propertyNames();
    627     while(enumeration.hasMoreElements()) {
    628       String key = (String) enumeration.nextElement();
    629       if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
    630 	String loggerName = null;
    631 	if(key.startsWith(CATEGORY_PREFIX)) {
    632 	  loggerName = key.substring(CATEGORY_PREFIX.length());
    633 	} else if(key.startsWith(LOGGER_PREFIX)) {
    634 	  loggerName = key.substring(LOGGER_PREFIX.length());
    635 	}
    636 	String value =  OptionConverter.findAndSubst(key, props);
    637 	Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
    638 	synchronized(logger) {
    639 	  parseCategory(props, logger, key, loggerName, value);
    640 	  parseAdditivityForLogger(props, logger, loggerName);
    641 	}
    642       } else if(key.startsWith(RENDERER_PREFIX)) {
    643 	String renderedClass = key.substring(RENDERER_PREFIX.length());
    644 	String renderingClass = OptionConverter.findAndSubst(key, props);
    645 	if(hierarchy instanceof RendererSupport) {
    646 	  RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
    647 				  renderingClass);
    648 	}
    649       } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
    650           if (hierarchy instanceof ThrowableRendererSupport) {
    651             ThrowableRenderer tr = (ThrowableRenderer)
    652                   OptionConverter.instantiateByKey(props,
    653                           THROWABLE_RENDERER_PREFIX,
    654                           org.apache.log4j.spi.ThrowableRenderer.class,
    655                           null);
    656             if(tr == null) {
    657                 LogLog.error(
    658                     "Could not instantiate throwableRenderer.");
    659             } else {
    660                 PropertySetter setter = new PropertySetter(tr);
    661                 setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
    662                 ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
    663
    664             }
    665           }
    666       }
    667     }
    668   }
    669
    670   /**
    671      Parse the additivity option for a non-root category.
    672    */
    673   void parseAdditivityForLogger(Properties props, Logger cat,
    674 				  String loggerName) {
    675     String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
    676 					     props);
    677     LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
    678     // touch additivity only if necessary
    679     if((value != null) && (!value.equals(""))) {
    680       boolean additivity = OptionConverter.toBoolean(value, true);
    681       LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
    682 		   additivity);
    683       cat.setAdditivity(additivity);
    684     }
    685   }
    686
    687   /**
    688      This method must work for the root category as well.
    689    */
    690   void parseCategory(Properties props, Logger logger, String optionKey,
    691 		     String loggerName, String value) {
    692
    693     LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
    694     // We must skip over ',' but not white space
    695     StringTokenizer st = new StringTokenizer(value, ",");
    696
    697     // If value is not in the form ", appender.." or "", then we should set
    698     // the level of the loggeregory.
    699
    700     if(!(value.startsWith(",") || value.equals(""))) {
    701
    702       // just to be on the safe side...
    703       if(!st.hasMoreTokens())
    704 	return;
    705
    706       String levelStr = st.nextToken();
    707       LogLog.debug("Level token is [" + levelStr + "].");
    708
    709       // If the level value is inherited, set category level value to
    710       // null. We also check that the user has not specified inherited for the
    711       // root category.
    712       if(INHERITED.equalsIgnoreCase(levelStr) ||
    713  	                                  NULL.equalsIgnoreCase(levelStr)) {
    714 	if(loggerName.equals(INTERNAL_ROOT_NAME)) {
    715 	  LogLog.warn("The root logger cannot be set to null.");
    716 	} else {
    717 	  logger.setLevel(null);
    718 	}
    719       } else {
    720 	logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
    721       }
    722       LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
    723     }
    724
    725     // Begin by removing all existing appenders.
    726     logger.removeAllAppenders();
    727
    728     Appender appender;
    729     String appenderName;
    730     while(st.hasMoreTokens()) {
    731       appenderName = st.nextToken().trim();
    732       if(appenderName == null || appenderName.equals(","))
    733 	continue;
    734       LogLog.debug("Parsing appender named \"" + appenderName +"\".");
    735       appender = parseAppender(props, appenderName);
    736       if(appender != null) {
    737 	logger.addAppender(appender);
    738       }
    739     }
    740   }
    741
    742   Appender parseAppender(Properties props, String appenderName) {
    743     Appender appender = registryGet(appenderName);
    744     if((appender != null)) {
    745       LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
    746       return appender;
    747     }
    748     // Appender was not previously initialized.
    749     String prefix = APPENDER_PREFIX + appenderName;
    750     String layoutPrefix = prefix + ".layout";
    751
    752     appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
    753 					      org.apache.log4j.Appender.class,
    754 					      null);
    755     if(appender == null) {
    756       LogLog.error(
    757               "Could not instantiate appender named \"" + appenderName+"\".");
    758       return null;
    759     }
    760     appender.setName(appenderName);
    761
    762     if(appender instanceof OptionHandler) {
    763       if(appender.requiresLayout()) {
    764 	Layout layout = (Layout) OptionConverter.instantiateByKey(props,
    765 								  layoutPrefix,
    766 								  Layout.class,
    767 								  null);
    768 	if(layout != null) {
    769 	  appender.setLayout(layout);
    770 	  LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
    771 	  //configureOptionHandler(layout, layoutPrefix + ".", props);
    772           PropertySetter.setProperties(layout, props, layoutPrefix + ".");
    773 	  LogLog.debug("End of parsing for \"" + appenderName +"\".");
    774 	}
    775       }
    776       final String errorHandlerPrefix = prefix + ".errorhandler";
    777       String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
    778       if (errorHandlerClass != null) {
    779     		ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
    780 					  errorHandlerPrefix,
    781 					  ErrorHandler.class,
    782 					  null);
    783     		if (eh != null) {
    784     			  appender.setErrorHandler(eh);
    785     			  LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
    786     			  parseErrorHandler(eh, errorHandlerPrefix, props, repository);
    787     			  final Properties edited = new Properties();
    788     			  final String[] keys = new String[] {
    789     					  errorHandlerPrefix + "." + ROOT_REF,
    790     					  errorHandlerPrefix + "." + LOGGER_REF,
    791     					  errorHandlerPrefix + "." + APPENDER_REF_TAG
    792     			  };
    793     			  for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
    794     				  Map.Entry entry = (Map.Entry) iter.next();
    795     				  int i = 0;
    796     				  for(; i < keys.length; i++) {
    797     					  if(keys[i].equals(entry.getKey())) break;
    798     				  }
    799     				  if (i == keys.length) {
    800     					  edited.put(entry.getKey(), entry.getValue());
    801     				  }
    802     			  }
    803     		      PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
    804     			  LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
    805     		}
    806
    807       }
    808       //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
    809       PropertySetter.setProperties(appender, props, prefix + ".");
    810       LogLog.debug("Parsed \"" + appenderName +"\" options.");
    811     }
    812     parseAppenderFilters(props, appenderName, appender);
    813     registryPut(appender);
    814     return appender;
    815   }
    816
    817   private void parseErrorHandler(
    818 		  final ErrorHandler eh,
    819 		  final String errorHandlerPrefix,
    820 		  final Properties props,
    821 		  final LoggerRepository hierarchy) {
    822 		boolean rootRef = OptionConverter.toBoolean(
    823 					  OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
    824 		if (rootRef) {
    825 				  eh.setLogger(hierarchy.getRootLogger());
    826 	    }
    827 		String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props);
    828 		if (loggerName != null) {
    829 			Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
    830 			                : hierarchy.getLogger(loggerName, loggerFactory);
    831 			eh.setLogger(logger);
    832 		}
    833 		String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
    834 		if (appenderName != null) {
    835 			Appender backup = parseAppender(props, appenderName);
    836 			if (backup != null) {
    837 				eh.setBackupAppender(backup);
    838 			}
    839 		}
    840   }
    841
    842
    843   void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
    844     // extract filters and filter options from props into a hashtable mapping
    845     // the property name defining the filter class to a list of pre-parsed
    846     // name-value pairs associated to that filter
    847     final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
    848     int fIdx = filterPrefix.length();
    849     Hashtable filters = new Hashtable();
    850     Enumeration e = props.keys();
    851     String name = "";
    852     while (e.hasMoreElements()) {
    853       String key = (String) e.nextElement();
    854       if (key.startsWith(filterPrefix)) {
    855         int dotIdx = key.indexOf('.', fIdx);
    856         String filterKey = key;
    857         if (dotIdx != -1) {
    858           filterKey = key.substring(0, dotIdx);
    859           name = key.substring(dotIdx+1);
    860         }
    861         Vector filterOpts = (Vector) filters.get(filterKey);
    862         if (filterOpts == null) {
    863           filterOpts = new Vector();
    864           filters.put(filterKey, filterOpts);
    865         }
    866         if (dotIdx != -1) {
    867           String value = OptionConverter.findAndSubst(key, props);
    868           filterOpts.add(new NameValue(name, value));
    869         }
    870       }
    871     }
    872
    873     // sort filters by IDs, insantiate filters, set filter options,
    874     // add filters to the appender
    875     Enumeration g = new SortedKeyEnumeration(filters);
    876     while (g.hasMoreElements()) {
    877       String key = (String) g.nextElement();
    878       String clazz = props.getProperty(key);
    879       if (clazz != null) {
    880         LogLog.debug("Filter key: ["+key+"] class: ["+props.getProperty(key) +"] props: "+filters.get(key));
    881         Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
    882         if (filter != null) {
    883           PropertySetter propSetter = new PropertySetter(filter);
    884           Vector v = (Vector)filters.get(key);
    885           Enumeration filterProps = v.elements();
    886           while (filterProps.hasMoreElements()) {
    887             NameValue kv = (NameValue)filterProps.nextElement();
    888             propSetter.setProperty(kv.key, kv.value);
    889           }
    890           propSetter.activate();
    891           LogLog.debug("Adding filter of type ["+filter.getClass()
    892            +"] to appender named ["+appender.getName()+"].");
    893           appender.addFilter(filter);
    894         }
    895       } else {
    896         LogLog.warn("Missing class definition for filter: ["+key+"]");
    897       }
    898     }
    899   }
    900
    901
    902   void  registryPut(Appender appender) {
    903     registry.put(appender.getName(), appender);
    904   }
    905
    906   Appender registryGet(String name) {
    907     return (Appender) registry.get(name);
    908   }
    909 }
    910
    911 class PropertyWatchdog extends FileWatchdog {
    912
    913   PropertyWatchdog(String filename) {
    914     super(filename);
    915   }
    916
    917   /**
    918      Call {@link PropertyConfigurator#configure(String)} with the
    919      <code>filename</code> to reconfigure log4j. */
    920   public
    921   void doOnChange() {
    922     new PropertyConfigurator().doConfigure(filename,
    923 					   LogManager.getLoggerRepository());
    924   }
    925 }
    926
    927 class NameValue {
    928   String key, value;
    929   public NameValue(String key, String value) {
    930     this.key = key;
    931     this.value = value;
    932   }
    933   public String toString() {
    934     return key + "=" + value;
    935   }
    936 }
    937
    938 class SortedKeyEnumeration implements Enumeration {
    939
    940   private Enumeration e;
    941
    942   public SortedKeyEnumeration(Hashtable ht) {
    943     Enumeration f = ht.keys();
    944     Vector keys = new Vector(ht.size());
    945     for (int i, last = 0; f.hasMoreElements(); ++last) {
    946       String key = (String) f.nextElement();
    947       for (i = 0; i < last; ++i) {
    948         String s = (String) keys.get(i);
    949         if (key.compareTo(s) <= 0) break;
    950       }
    951       keys.add(i, key);
    952     }
    953     e = keys.elements();
    954   }
    955
    956   public boolean hasMoreElements() {
    957     return e.hasMoreElements();
    958   }
    959
    960   public Object nextElement() {
    961     return e.nextElement();
    962   }
    963 }