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 Reads configuration options from an InputStream.
    421
    422 @since 1.2.17
    423 */
    424 public
    425 static
    426 void configure(InputStream inputStream) {
    427 new PropertyConfigurator().doConfigure(inputStream,
    428                   LogManager.getLoggerRepository());
    429 }
    430
    431
    432   /**
    433      Read configuration options from <code>properties</code>.
    434
    435      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
    436   */
    437   static
    438   public
    439   void configure(Properties properties) {
    440     new PropertyConfigurator().doConfigure(properties,
    441 					   LogManager.getLoggerRepository());
    442   }
    443
    444   /**
    445      Like {@link #configureAndWatch(String, long)} except that the
    446      default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
    447      used.
    448
    449      @param configFilename A file in key=value format.
    450
    451   */
    452   static
    453   public
    454   void configureAndWatch(String configFilename) {
    455     configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
    456   }
    457
    458
    459   /**
    460      Read the configuration file <code>configFilename</code> if it
    461      exists. Moreover, a thread will be created that will periodically
    462      check if <code>configFilename</code> has been created or
    463      modified. The period is determined by the <code>delay</code>
    464      argument. If a change or file creation is detected, then
    465      <code>configFilename</code> is read to configure log4j.
    466
    467       @param configFilename A file in key=value format.
    468       @param delay The delay in milliseconds to wait between each check.
    469   */
    470   static
    471   public
    472   void configureAndWatch(String configFilename, long delay) {
    473     PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
    474     pdog.setDelay(delay);
    475     pdog.start();
    476   }
    477
    478
    479   /**
    480      Read configuration options from <code>properties</code>.
    481
    482      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
    483   */
    484   public
    485   void doConfigure(Properties properties, LoggerRepository hierarchy) {
    486 	repository = hierarchy;
    487     String value = properties.getProperty(LogLog.DEBUG_KEY);
    488     if(value == null) {
    489       value = properties.getProperty("log4j.configDebug");
    490       if(value != null)
    491 	LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
    492     }
    493
    494     if(value != null) {
    495       LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
    496     }
    497
    498       //
    499       //   if log4j.reset=true then
    500       //        reset hierarchy
    501     String reset = properties.getProperty(RESET_KEY);
    502     if (reset != null && OptionConverter.toBoolean(reset, false)) {
    503           hierarchy.resetConfiguration();
    504     }
    505
    506     String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
    507 						       properties);
    508     if(thresholdStr != null) {
    509       hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
    510 						     (Level) Level.ALL));
    511       LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
    512     }
    513
    514     configureRootCategory(properties, hierarchy);
    515     configureLoggerFactory(properties);
    516     parseCatsAndRenderers(properties, hierarchy);
    517
    518     LogLog.debug("Finished configuring.");
    519     // We don't want to hold references to appenders preventing their
    520     // garbage collection.
    521     registry.clear();
    522   }
    523
    524     /**
    525      * Read configuration options from url <code>configURL</code>.
    526      *
    527      * @since 1.2.17
    528      */
    529     public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) {
    530         Properties props = new Properties();
    531         try {
    532             props.load(inputStream);
    533         } catch (IOException e) {
    534             if (e instanceof InterruptedIOException) {
    535                 Thread.currentThread().interrupt();
    536             }
    537             LogLog.error("Could not read configuration file from InputStream [" + inputStream
    538                  + "].", e);
    539             LogLog.error("Ignoring configuration InputStream [" + inputStream +"].");
    540             return;
    541           }
    542         this.doConfigure(props, hierarchy);
    543     }
    544
    545   /**
    546      Read configuration options from url <code>configURL</code>.
    547    */
    548   public
    549   void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
    550     Properties props = new Properties();
    551     LogLog.debug("Reading configuration from URL " + configURL);
    552     InputStream istream = null;
    553     URLConnection uConn = null;
    554     try {
    555       uConn = configURL.openConnection();
    556       uConn.setUseCaches(false);
    557       istream = uConn.getInputStream();
    558       props.load(istream);
    559     }
    560     catch (Exception e) {
    561       if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
    562           Thread.currentThread().interrupt();
    563       }
    564       LogLog.error("Could not read configuration file from URL [" + configURL
    565 		   + "].", e);
    566       LogLog.error("Ignoring configuration file [" + configURL +"].");
    567       return;
    568     }
    569     finally {
    570         if (istream != null) {
    571             try {
    572                 istream.close();
    573             } catch(InterruptedIOException ignore) {
    574                 Thread.currentThread().interrupt();
    575             } catch(IOException ignore) {
    576             } catch(RuntimeException ignore) {
    577             }
    578         }
    579     }
    580     doConfigure(props, hierarchy);
    581   }
    582
    583
    584   // --------------------------------------------------------------------------
    585   // Internal stuff
    586   // --------------------------------------------------------------------------
    587
    588   /**
    589      Check the provided <code>Properties</code> object for a
    590      {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
    591      entry specified by {@link #LOGGER_FACTORY_KEY}.  If such an entry
    592      exists, an attempt is made to create an instance using the default
    593      constructor.  This instance is used for subsequent Category creations
    594      within this configurator.
    595
    596      @see #parseCatsAndRenderers
    597    */
    598   protected void configureLoggerFactory(Properties props) {
    599     String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
    600 							   props);
    601     if(factoryClassName != null) {
    602       LogLog.debug("Setting category factory to ["+factoryClassName+"].");
    603       loggerFactory = (LoggerFactory)
    604 	          OptionConverter.instantiateByClassName(factoryClassName,
    605 							 LoggerFactory.class,
    606 							 loggerFactory);
    607       PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
    608     }
    609   }
    610
    611   /*
    612   void configureOptionHandler(OptionHandler oh, String prefix,
    613 			      Properties props) {
    614     String[] options = oh.getOptionStrings();
    615     if(options == null)
    616       return;
    617
    618     String value;
    619     for(int i = 0; i < options.length; i++) {
    620       value =  OptionConverter.findAndSubst(prefix + options[i], props);
    621       LogLog.debug(
    622          "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"].");
    623       // Some option handlers assume that null value are not passed to them.
    624       // So don't remove this check
    625       if(value != null) {
    626 	oh.setOption(options[i], value);
    627       }
    628     }
    629     oh.activateOptions();
    630   }
    631   */
    632
    633
    634   void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    635     String effectiveFrefix = ROOT_LOGGER_PREFIX;
    636     String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
    637
    638     if(value == null) {
    639       value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
    640       effectiveFrefix = ROOT_CATEGORY_PREFIX;
    641     }
    642
    643     if(value == null)
    644       LogLog.debug("Could not find root logger information. Is this OK?");
    645     else {
    646       Logger root = hierarchy.getRootLogger();
    647       synchronized(root) {
    648 	parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
    649       }
    650     }
    651   }
    652
    653
    654   /**
    655      Parse non-root elements, such non-root categories and renderers.
    656   */
    657   protected
    658   void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
    659     Enumeration enumeration = props.propertyNames();
    660     while(enumeration.hasMoreElements()) {
    661       String key = (String) enumeration.nextElement();
    662       if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
    663 	String loggerName = null;
    664 	if(key.startsWith(CATEGORY_PREFIX)) {
    665 	  loggerName = key.substring(CATEGORY_PREFIX.length());
    666 	} else if(key.startsWith(LOGGER_PREFIX)) {
    667 	  loggerName = key.substring(LOGGER_PREFIX.length());
    668 	}
    669 	String value =  OptionConverter.findAndSubst(key, props);
    670 	Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
    671 	synchronized(logger) {
    672 	  parseCategory(props, logger, key, loggerName, value);
    673 	  parseAdditivityForLogger(props, logger, loggerName);
    674 	}
    675       } else if(key.startsWith(RENDERER_PREFIX)) {
    676 	String renderedClass = key.substring(RENDERER_PREFIX.length());
    677 	String renderingClass = OptionConverter.findAndSubst(key, props);
    678 	if(hierarchy instanceof RendererSupport) {
    679 	  RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
    680 				  renderingClass);
    681 	}
    682       } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
    683           if (hierarchy instanceof ThrowableRendererSupport) {
    684             ThrowableRenderer tr = (ThrowableRenderer)
    685                   OptionConverter.instantiateByKey(props,
    686                           THROWABLE_RENDERER_PREFIX,
    687                           org.apache.log4j.spi.ThrowableRenderer.class,
    688                           null);
    689             if(tr == null) {
    690                 LogLog.error(
    691                     "Could not instantiate throwableRenderer.");
    692             } else {
    693                 PropertySetter setter = new PropertySetter(tr);
    694                 setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
    695                 ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
    696
    697             }
    698           }
    699       }
    700     }
    701   }
    702
    703   /**
    704      Parse the additivity option for a non-root category.
    705    */
    706   void parseAdditivityForLogger(Properties props, Logger cat,
    707 				  String loggerName) {
    708     String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
    709 					     props);
    710     LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
    711     // touch additivity only if necessary
    712     if((value != null) && (!value.equals(""))) {
    713       boolean additivity = OptionConverter.toBoolean(value, true);
    714       LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
    715 		   additivity);
    716       cat.setAdditivity(additivity);
    717     }
    718   }
    719
    720   /**
    721      This method must work for the root category as well.
    722    */
    723   void parseCategory(Properties props, Logger logger, String optionKey,
    724 		     String loggerName, String value) {
    725
    726     LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
    727     // We must skip over ',' but not white space
    728     StringTokenizer st = new StringTokenizer(value, ",");
    729
    730     // If value is not in the form ", appender.." or "", then we should set
    731     // the level of the loggeregory.
    732
    733     if(!(value.startsWith(",") || value.equals(""))) {
    734
    735       // just to be on the safe side...
    736       if(!st.hasMoreTokens())
    737 	return;
    738
    739       String levelStr = st.nextToken();
    740       LogLog.debug("Level token is [" + levelStr + "].");
    741
    742       // If the level value is inherited, set category level value to
    743       // null. We also check that the user has not specified inherited for the
    744       // root category.
    745       if(INHERITED.equalsIgnoreCase(levelStr) ||
    746  	                                  NULL.equalsIgnoreCase(levelStr)) {
    747 	if(loggerName.equals(INTERNAL_ROOT_NAME)) {
    748 	  LogLog.warn("The root logger cannot be set to null.");
    749 	} else {
    750 	  logger.setLevel(null);
    751 	}
    752       } else {
    753 	logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
    754       }
    755       LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
    756     }
    757
    758     // Begin by removing all existing appenders.
    759     logger.removeAllAppenders();
    760
    761     Appender appender;
    762     String appenderName;
    763     while(st.hasMoreTokens()) {
    764       appenderName = st.nextToken().trim();
    765       if(appenderName == null || appenderName.equals(","))
    766 	continue;
    767       LogLog.debug("Parsing appender named \"" + appenderName +"\".");
    768       appender = parseAppender(props, appenderName);
    769       if(appender != null) {
    770 	logger.addAppender(appender);
    771       }
    772     }
    773   }
    774
    775   Appender parseAppender(Properties props, String appenderName) {
    776     Appender appender = registryGet(appenderName);
    777     if((appender != null)) {
    778       LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
    779       return appender;
    780     }
    781     // Appender was not previously initialized.
    782     String prefix = APPENDER_PREFIX + appenderName;
    783     String layoutPrefix = prefix + ".layout";
    784
    785     appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
    786 					      org.apache.log4j.Appender.class,
    787 					      null);
    788     if(appender == null) {
    789       LogLog.error(
    790               "Could not instantiate appender named \"" + appenderName+"\".");
    791       return null;
    792     }
    793     appender.setName(appenderName);
    794
    795     if(appender instanceof OptionHandler) {
    796       if(appender.requiresLayout()) {
    797 	Layout layout = (Layout) OptionConverter.instantiateByKey(props,
    798 								  layoutPrefix,
    799 								  Layout.class,
    800 								  null);
    801 	if(layout != null) {
    802 	  appender.setLayout(layout);
    803 	  LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
    804 	  //configureOptionHandler(layout, layoutPrefix + ".", props);
    805           PropertySetter.setProperties(layout, props, layoutPrefix + ".");
    806 	  LogLog.debug("End of parsing for \"" + appenderName +"\".");
    807 	}
    808       }
    809       final String errorHandlerPrefix = prefix + ".errorhandler";
    810       String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
    811       if (errorHandlerClass != null) {
    812     		ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
    813 					  errorHandlerPrefix,
    814 					  ErrorHandler.class,
    815 					  null);
    816     		if (eh != null) {
    817     			  appender.setErrorHandler(eh);
    818     			  LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
    819     			  parseErrorHandler(eh, errorHandlerPrefix, props, repository);
    820     			  final Properties edited = new Properties();
    821     			  final String[] keys = new String[] {
    822     					  errorHandlerPrefix + "." + ROOT_REF,
    823     					  errorHandlerPrefix + "." + LOGGER_REF,
    824     					  errorHandlerPrefix + "." + APPENDER_REF_TAG
    825     			  };
    826     			  for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
    827     				  Map.Entry entry = (Map.Entry) iter.next();
    828     				  int i = 0;
    829     				  for(; i < keys.length; i++) {
    830     					  if(keys[i].equals(entry.getKey())) break;
    831     				  }
    832     				  if (i == keys.length) {
    833     					  edited.put(entry.getKey(), entry.getValue());
    834     				  }
    835     			  }
    836     		      PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
    837     			  LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
    838     		}
    839
    840       }
    841       //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
    842       PropertySetter.setProperties(appender, props, prefix + ".");
    843       LogLog.debug("Parsed \"" + appenderName +"\" options.");
    844     }
    845     parseAppenderFilters(props, appenderName, appender);
    846     registryPut(appender);
    847     return appender;
    848   }
    849
    850   private void parseErrorHandler(
    851 		  final ErrorHandler eh,
    852 		  final String errorHandlerPrefix,
    853 		  final Properties props,
    854 		  final LoggerRepository hierarchy) {
    855 		boolean rootRef = OptionConverter.toBoolean(
    856 					  OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
    857 		if (rootRef) {
    858 				  eh.setLogger(hierarchy.getRootLogger());
    859 	    }
    860 		String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props);
    861 		if (loggerName != null) {
    862 			Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
    863 			                : hierarchy.getLogger(loggerName, loggerFactory);
    864 			eh.setLogger(logger);
    865 		}
    866 		String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
    867 		if (appenderName != null) {
    868 			Appender backup = parseAppender(props, appenderName);
    869 			if (backup != null) {
    870 				eh.setBackupAppender(backup);
    871 			}
    872 		}
    873   }
    874
    875
    876   void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
    877     // extract filters and filter options from props into a hashtable mapping
    878     // the property name defining the filter class to a list of pre-parsed
    879     // name-value pairs associated to that filter
    880     final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
    881     int fIdx = filterPrefix.length();
    882     Hashtable filters = new Hashtable();
    883     Enumeration e = props.keys();
    884     String name = "";
    885     while (e.hasMoreElements()) {
    886       String key = (String) e.nextElement();
    887       if (key.startsWith(filterPrefix)) {
    888         int dotIdx = key.indexOf('.', fIdx);
    889         String filterKey = key;
    890         if (dotIdx != -1) {
    891           filterKey = key.substring(0, dotIdx);
    892           name = key.substring(dotIdx+1);
    893         }
    894         Vector filterOpts = (Vector) filters.get(filterKey);
    895         if (filterOpts == null) {
    896           filterOpts = new Vector();
    897           filters.put(filterKey, filterOpts);
    898         }
    899         if (dotIdx != -1) {
    900           String value = OptionConverter.findAndSubst(key, props);
    901           filterOpts.add(new NameValue(name, value));
    902         }
    903       }
    904     }
    905
    906     // sort filters by IDs, insantiate filters, set filter options,
    907     // add filters to the appender
    908     Enumeration g = new SortedKeyEnumeration(filters);
    909     while (g.hasMoreElements()) {
    910       String key = (String) g.nextElement();
    911       String clazz = props.getProperty(key);
    912       if (clazz != null) {
    913         LogLog.debug("Filter key: ["+key+"] class: ["+props.getProperty(key) +"] props: "+filters.get(key));
    914         Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
    915         if (filter != null) {
    916           PropertySetter propSetter = new PropertySetter(filter);
    917           Vector v = (Vector)filters.get(key);
    918           Enumeration filterProps = v.elements();
    919           while (filterProps.hasMoreElements()) {
    920             NameValue kv = (NameValue)filterProps.nextElement();
    921             propSetter.setProperty(kv.key, kv.value);
    922           }
    923           propSetter.activate();
    924           LogLog.debug("Adding filter of type ["+filter.getClass()
    925            +"] to appender named ["+appender.getName()+"].");
    926           appender.addFilter(filter);
    927         }
    928       } else {
    929         LogLog.warn("Missing class definition for filter: ["+key+"]");
    930       }
    931     }
    932   }
    933
    934
    935   void  registryPut(Appender appender) {
    936     registry.put(appender.getName(), appender);
    937   }
    938
    939   Appender registryGet(String name) {
    940     return (Appender) registry.get(name);
    941   }
    942 }
    943
    944 class PropertyWatchdog extends FileWatchdog {
    945
    946   PropertyWatchdog(String filename) {
    947     super(filename);
    948   }
    949
    950   /**
    951      Call {@link PropertyConfigurator#configure(String)} with the
    952      <code>filename</code> to reconfigure log4j. */
    953   public
    954   void doOnChange() {
    955     new PropertyConfigurator().doConfigure(filename,
    956 					   LogManager.getLoggerRepository());
    957   }
    958 }
    959
    960 class NameValue {
    961   String key, value;
    962   public NameValue(String key, String value) {
    963     this.key = key;
    964     this.value = value;
    965   }
    966   public String toString() {
    967     return key + "=" + value;
    968   }
    969 }
    970
    971 class SortedKeyEnumeration implements Enumeration {
    972
    973   private Enumeration e;
    974
    975   public SortedKeyEnumeration(Hashtable ht) {
    976     Enumeration f = ht.keys();
    977     Vector keys = new Vector(ht.size());
    978     for (int i, last = 0; f.hasMoreElements(); ++last) {
    979       String key = (String) f.nextElement();
    980       for (i = 0; i < last; ++i) {
    981         String s = (String) keys.get(i);
    982         if (key.compareTo(s) <= 0) break;
    983       }
    984       keys.add(i, key);
    985     }
    986     e = keys.elements();
    987   }
    988
    989   public boolean hasMoreElements() {
    990     return e.hasMoreElements();
    991   }
    992
    993   public Object nextElement() {
    994     return e.nextElement();
    995   }
    996 }