001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018
019// Contibutors: "Luke Blanshard" <Luke@quiq.com>
020//              "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>
021//               Anders Kristensen <akristensen@dynamicsoft.com>
022
023package org.apache.log4j;
024
025import java.io.FileInputStream;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.InterruptedIOException;
029import java.net.URLConnection;
030import java.util.Enumeration;
031import java.util.Hashtable;
032import java.util.Properties;
033import java.util.StringTokenizer;
034import java.util.Vector;
035import java.util.Iterator;
036import java.util.Map;
037
038import org.apache.log4j.config.PropertySetter;
039import org.apache.log4j.helpers.FileWatchdog;
040import org.apache.log4j.helpers.LogLog;
041import org.apache.log4j.helpers.OptionConverter;
042import org.apache.log4j.or.RendererMap;
043import org.apache.log4j.spi.Configurator;
044import org.apache.log4j.spi.Filter;
045import org.apache.log4j.spi.LoggerFactory;
046import org.apache.log4j.spi.LoggerRepository;
047import org.apache.log4j.spi.OptionHandler;
048import org.apache.log4j.spi.RendererSupport;
049import org.apache.log4j.spi.ThrowableRenderer;
050import org.apache.log4j.spi.ThrowableRendererSupport;
051import org.apache.log4j.spi.ErrorHandler;
052
053/**
054   Allows the configuration of log4j from an external file.  See
055   <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
056   expected format.
057
058   <p>It is sometimes useful to see how log4j is reading configuration
059   files. You can enable log4j internal logging by defining the
060   <b>log4j.debug</b> variable.
061
062   <P>As of log4j version 0.8.5, at class initialization time class,
063   the file <b>log4j.properties</b> will be searched from the search
064   path used to load classes. If the file can be found, then it will
065   be fed to the {@link PropertyConfigurator#configure(java.net.URL)}
066   method.
067
068   <p>The <code>PropertyConfigurator</code> does not handle the
069   advanced configuration features supported by the {@link
070   org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as
071   support custom {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers},
072   nested appenders such as the {@link org.apache.log4j.AsyncAppender
073   AsyncAppender}, etc.
074
075   <p>All option <em>values</em> admit variable substitution. The
076   syntax of variable substitution is similar to that of Unix
077   shells. The string between an opening <b>&quot;${&quot;</b> and
078   closing <b>&quot;}&quot;</b> is interpreted as a key. The value of
079   the substituted variable can be defined as a system property or in
080   the configuration file itself. The value of the key is first
081   searched in the system properties, and if not found there, it is
082   then searched in the configuration file being parsed.  The
083   corresponding value replaces the ${variableName} sequence. For
084   example, if <code>java.home</code> system property is set to
085   <code>/home/xyz</code>, then every occurrence of the sequence
086   <code>${java.home}</code> will be interpreted as
087   <code>/home/xyz</code>.
088
089
090   @author Ceki G&uuml;lc&uuml;
091   @author Anders Kristensen
092   @since 0.8.1 */
093public class PropertyConfigurator implements Configurator {
094
095  /**
096     Used internally to keep track of configured appenders.
097   */
098  protected Hashtable registry = new Hashtable(11);  
099  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*/
412public
413static
414void configure(java.net.URL configURL) {
415 new PropertyConfigurator().doConfigure(configURL,
416                    LogManager.getLoggerRepository());
417}
418
419/**
420Reads configuration options from an InputStream.
421
422@since 1.2.17
423*/
424public
425static
426void configure(InputStream inputStream) {
427new 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
944class 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
960class 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
971class 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}