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 org.apache.log4j.config.PropertySetter;
    26  import org.apache.log4j.helpers.FileWatchdog;
    27  import org.apache.log4j.helpers.LogLog;
    28  import org.apache.log4j.helpers.OptionConverter;
    29  import org.apache.log4j.or.RendererMap;
    30  import org.apache.log4j.spi.Configurator;
    31  import org.apache.log4j.spi.LoggerFactory;
    32  import org.apache.log4j.spi.LoggerRepository;
    33  import org.apache.log4j.spi.OptionHandler;
    34  import org.apache.log4j.spi.RendererSupport;
    35
    36  import java.io.FileInputStream;
    37  import java.io.InputStream;
    38  import java.util.Enumeration;
    39  import java.util.Hashtable;
    40  import java.util.Properties;
    41  import java.util.StringTokenizer;
    42
    43  /***
    44     Allows the configuration of log4j from an external file.  See
    45     <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
    46     expected format.
    47
    48     <p>It is sometimes useful to see how log4j is reading configuration
    49     files. You can enable log4j internal logging by defining the
    50     <b>log4j.debug</b> variable.
    51
    52     <P>As of log4j version 0.8.5, at class initialization time class,
    53     the file <b>log4j.properties</b> will be searched from the search
    54     path used to load classes. If the file can be found, then it will
    55     be fed to the {@link PropertyConfigurator#configure(java.net.URL)}
    56     method.
    57
    58     <p>The <code>PropertyConfigurator</code> does not handle the
    59     advanced configuration features supported by the {@link
    60     org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as
    61     support for {@link org.apache.log4j.spi.Filter Filters}, custom
    62     {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers}, nested
    63     appenders such as the {@link org.apache.log4j.AsyncAppender
    64     AsyncAppender}, etc.
    65
    66     <p>All option <em>values</em> admit variable substitution. The
    67     syntax of variable substitution is similar to that of Unix
    68     shells. The string between an opening <b>&quot;${&quot;</b> and
    69     closing <b>&quot;}&quot;</b> is interpreted as a key. The value of
    70     the substituted variable can be defined as a system property or in
    71     the configuration file itself. The value of the key is first
    72     searched in the system properties, and if not found there, it is
    73     then searched in the configuration file being parsed.  The
    74     corresponding value replaces the ${variableName} sequence. For
    75     example, if <code>java.home</code> system property is set to
    76     <code>/home/xyz</code>, then every occurrence of the sequence
    77     <code>${java.home}</code> will be interpreted as
    78     <code>/home/xyz</code>.
    79
    80
    81     @author Ceki G&uuml;lc&uuml;
    82     @author Anders Kristensen
    83     @since 0.8.1 */
    84  public class PropertyConfigurator implements Configurator {
    85
    86    /***
    87       Used internally to keep track of configured appenders.
    88     */
    89    protected Hashtable registry = new Hashtable(11);
    90    protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
    91
    92    static final String      CATEGORY_PREFIX = "log4j.category.";
    93    static final String      LOGGER_PREFIX   = "log4j.logger.";
    94    static final String       FACTORY_PREFIX = "log4j.factory";
    95    static final String    ADDITIVITY_PREFIX = "log4j.additivity.";
    96    static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
    97    static final String ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
    98    static final String      APPENDER_PREFIX = "log4j.appender.";
    99    static final String      RENDERER_PREFIX = "log4j.renderer.";
    100   static final String      THRESHOLD_PREFIX = "log4j.threshold";
    101
    102   /*** Key for specifying the {@link org.apache.log4j.spi.LoggerFactory
    103       LoggerFactory}.  Currently set to "<code>log4j.loggerFactory</code>".  */
    104   public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
    105
    106     /***
    107      * If property set to true, then hierarchy will be reset before configuration.
    108      */
    109   private static final String RESET_KEY = "log4j.reset";
    110
    111   static final private String INTERNAL_ROOT_NAME = "root";
    112
    113   /***
    114     Read configuration from a file. <b>The existing configuration is
    115     not cleared nor reset.</b> If you require a different behavior,
    116     then call {@link  LogManager#resetConfiguration
    117     resetConfiguration} method before calling
    118     <code>doConfigure</code>.
    119
    120     <p>The configuration file consists of statements in the format
    121     <code>key=value</code>. The syntax of different configuration
    122     elements are discussed below.
    123
    124     <h3>Repository-wide threshold</h3>
    125
    126     <p>The repository-wide threshold filters logging requests by level
    127     regardless of logger. The syntax is:
    128
    129     <pre>
    130     log4j.threshold=[level]
    131     </pre>
    132
    133     <p>The level value can consist of the string values OFF, FATAL,
    134     ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
    135     custom level value can be specified in the form
    136     level#classname. By default the repository-wide threshold is set
    137     to the lowest possible value, namely the level <code>ALL</code>.
    138     </p>
    139
    140
    141     <h3>Appender configuration</h3>
    142
    143     <p>Appender configuration syntax is:
    144     <pre>
    145     # For appender named <i>appenderName</i>, set its class.
    146     # Note: The appender name can contain dots.
    147     log4j.appender.appenderName=fully.qualified.name.of.appender.class
    148
    149     # Set appender specific options.
    150     log4j.appender.appenderName.option1=value1
    151     ...
    152     log4j.appender.appenderName.optionN=valueN
    153     </pre>
    154
    155     For each named appender you can configure its {@link Layout}. The
    156     syntax for configuring an appender's layout is:
    157     <pre>
    158     log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
    159     log4j.appender.appenderName.layout.option1=value1
    160     ....
    161     log4j.appender.appenderName.layout.optionN=valueN
    162     </pre>
    163
    164     <h3>Configuring loggers</h3>
    165
    166     <p>The syntax for configuring the root logger is:
    167     <pre>
    168       log4j.rootLogger=[level], appenderName, appenderName, ...
    169     </pre>
    170
    171     <p>This syntax means that an optional <em>level</em> can be
    172     supplied followed by appender names separated by commas.
    173
    174     <p>The level value can consist of the string values OFF, FATAL,
    175     ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
    176     custom level value can be specified in the form
    177     <code>level#classname</code>.
    178
    179     <p>If a level value is specified, then the root level is set
    180     to the corresponding level.  If no level value is specified,
    181     then the root level remains untouched.
    182
    183     <p>The root logger can be assigned multiple appenders.
    184
    185     <p>Each <i>appenderName</i> (separated by commas) will be added to
    186     the root logger. The named appender is defined using the
    187     appender syntax defined above.
    188
    189     <p>For non-root categories the syntax is almost the same:
    190     <pre>
    191     log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
    192     </pre>
    193
    194     <p>The meaning of the optional level value is discussed above
    195     in relation to the root logger. In addition however, the value
    196     INHERITED can be specified meaning that the named logger should
    197     inherit its level from the logger hierarchy.
    198
    199     <p>If no level value is supplied, then the level of the
    200     named logger remains untouched.
    201
    202     <p>By default categories inherit their level from the
    203     hierarchy. However, if you set the level of a logger and later
    204     decide that that logger should inherit its level, then you should
    205     specify INHERITED as the value for the level value. NULL is a
    206     synonym for INHERITED.
    207
    208     <p>Similar to the root logger syntax, each <i>appenderName</i>
    209     (separated by commas) will be attached to the named logger.
    210
    211     <p>See the <a href="../../../../manual.html#additivity">appender
    212     additivity rule</a> in the user manual for the meaning of the
    213     <code>additivity</code> flag.
    214
    215     <h3>ObjectRenderers</h3>
    216
    217     You can customize the way message objects of a given type are
    218     converted to String before being logged. This is done by
    219     specifying an {@link org.apache.log4j.or.ObjectRenderer ObjectRenderer}
    220     for the object type would like to customize.
    221
    222     <p>The syntax is:
    223
    224     <pre>
    225     log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
    226     </pre>
    227
    228     As in,
    229     <pre>
    230     log4j.renderer.my.Fruit=my.FruitRenderer
    231     </pre>
    232
    233     <h3>Logger Factories</h3>
    234
    235     The usage of custom logger factories is discouraged and no longer
    236     documented.
    237
    238     <h3>Resetting Hierarchy</h3>
    239
    240     The hierarchy will be reset before configuration when
    241     log4j.reset=true is present in the properties file.
    242
    243     <h3>Example</h3>
    244
    245     <p>An example configuration is given below. Other configuration
    246     file examples are given in the <code>examples</code> folder.
    247
    248     <pre>
    249
    250     # Set options for appender named "A1".
    251     # Appender "A1" will be a SyslogAppender
    252     log4j.appender.A1=org.apache.log4j.net.SyslogAppender
    253
    254     # The syslog daemon resides on www.abc.net
    255     log4j.appender.A1.SyslogHost=www.abc.net
    256
    257     # A1's layout is a PatternLayout, using the conversion pattern
    258     # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
    259     # include # the relative time since the start of the application in
    260     # milliseconds, followed by the level of the log request,
    261     # followed by the two rightmost components of the logger name,
    262     # followed by the callers method name, followed by the line number,
    263     # the nested disgnostic context and finally the message itself.
    264     # Refer to the documentation of {@link PatternLayout} for further information
    265     # on the syntax of the ConversionPattern key.
    266     log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    267     log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
    268
    269     # Set options for appender named "A2"
    270     # A2 should be a RollingFileAppender, with maximum file size of 10 MB
    271     # using at most one backup file. A2's layout is TTCC, using the
    272     # ISO8061 date format with context printing enabled.
    273     log4j.appender.A2=org.apache.log4j.RollingFileAppender
    274     log4j.appender.A2.MaxFileSize=10MB
    275     log4j.appender.A2.MaxBackupIndex=1
    276     log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
    277     log4j.appender.A2.layout.ContextPrinting=enabled
    278     log4j.appender.A2.layout.DateFormat=ISO8601
    279
    280     # Root logger set to DEBUG using the A2 appender defined above.
    281     log4j.rootLogger=DEBUG, A2
    282
    283     # Logger definitions:
    284     # The SECURITY logger inherits is level from root. However, it's output
    285     # will go to A1 appender defined above. It's additivity is non-cumulative.
    286     log4j.logger.SECURITY=INHERIT, A1
    287     log4j.additivity.SECURITY=false
    288
    289     # Only warnings or above will be logged for the logger "SECURITY.access".
    290     # Output will go to A1.
    291     log4j.logger.SECURITY.access=WARN
    292
    293
    294     # The logger "class.of.the.day" inherits its level from the
    295     # logger hierarchy.  Output will go to the appender's of the root
    296     # logger, A2 in this case.
    297     log4j.logger.class.of.the.day=INHERIT
    298     </pre>
    299
    300     <p>Refer to the <b>setOption</b> method in each Appender and
    301     Layout for class specific options.
    302
    303     <p>Use the <code>#</code> or <code>!</code> characters at the
    304     beginning of a line for comments.
    305
    306    <p>
    307    @param configFileName The name of the configuration file where the
    308    configuration information is stored.
    309
    310   */
    311   public
    312   void doConfigure(String configFileName, LoggerRepository hierarchy) {
    313     Properties props = new Properties();
    314     FileInputStream istream = null;
    315     try {
    316       istream = new FileInputStream(configFileName);
    317       props.load(istream);
    318       istream.close();
    319     }
    320     catch (Exception e) {
    321       LogLog.error("Could not read configuration file ["+configFileName+"].", e);
    322       LogLog.error("Ignoring configuration file [" + configFileName+"].");
    323       return;
    324     } finally {
    325         if(istream != null) {
    326             try {
    327                 istream.close();
    328             } catch(Throwable ignore) {
    329             }
    330
    331         }
    332     }
    333     // If we reach here, then the config file is alright.
    334     doConfigure(props, hierarchy);
    335   }
    336
    337   /***
    338    */
    339   static
    340   public
    341   void configure(String configFilename) {
    342     new PropertyConfigurator().doConfigure(configFilename,
    343 					   LogManager.getLoggerRepository());
    344   }
    345
    346   /***
    347      Read configuration options from url <code>configURL</code>.
    348
    349      @since 0.8.2
    350    */
    351   public
    352   static
    353   void configure(java.net.URL configURL) {
    354     new PropertyConfigurator().doConfigure(configURL,
    355 					   LogManager.getLoggerRepository());
    356   }
    357
    358
    359   /***
    360      Read configuration options from <code>properties</code>.
    361
    362      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
    363   */
    364   static
    365   public
    366   void configure(Properties properties) {
    367     new PropertyConfigurator().doConfigure(properties,
    368 					   LogManager.getLoggerRepository());
    369   }
    370
    371   /***
    372      Like {@link #configureAndWatch(String, long)} except that the
    373      default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
    374      used.
    375
    376      @param configFilename A file in key=value format.
    377
    378   */
    379   static
    380   public
    381   void configureAndWatch(String configFilename) {
    382     configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
    383   }
    384
    385
    386   /***
    387      Read the configuration file <code>configFilename</code> if it
    388      exists. Moreover, a thread will be created that will periodically
    389      check if <code>configFilename</code> has been created or
    390      modified. The period is determined by the <code>delay</code>
    391      argument. If a change or file creation is detected, then
    392      <code>configFilename</code> is read to configure log4j.
    393
    394       @param configFilename A file in key=value format.
    395       @param delay The delay in milliseconds to wait between each check.
    396   */
    397   static
    398   public
    399   void configureAndWatch(String configFilename, long delay) {
    400     PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
    401     pdog.setDelay(delay);
    402     pdog.start();
    403   }
    404
    405
    406   /***
    407      Read configuration options from <code>properties</code>.
    408
    409      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
    410   */
    411   public
    412   void doConfigure(Properties properties, LoggerRepository hierarchy) {
    413     String value = properties.getProperty(LogLog.DEBUG_KEY);
    414     if(value == null) {
    415       value = properties.getProperty("log4j.configDebug");
    416       if(value != null)
    417 	LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
    418     }
    419
    420     if(value != null) {
    421       LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
    422     }
    423
    424       //
    425       //   if log4j.reset=true then
    426       //        reset hierarchy
    427     String reset = properties.getProperty(RESET_KEY);
    428     if (reset != null && OptionConverter.toBoolean(reset, false)) {
    429           hierarchy.resetConfiguration();
    430     }
    431
    432     String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
    433 						       properties);
    434     if(thresholdStr != null) {
    435       hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
    436 						     (Level) Level.ALL));
    437       LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
    438     }
    439
    440     configureRootCategory(properties, hierarchy);
    441     configureLoggerFactory(properties);
    442     parseCatsAndRenderers(properties, hierarchy);
    443
    444     LogLog.debug("Finished configuring.");
    445     // We don't want to hold references to appenders preventing their
    446     // garbage collection.
    447     registry.clear();
    448   }
    449
    450   /***
    451      Read configuration options from url <code>configURL</code>.
    452    */
    453   public
    454   void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
    455     Properties props = new Properties();
    456     LogLog.debug("Reading configuration from URL " + configURL);
    457     InputStream istream = null;
    458     try {
    459       istream = configURL.openStream();
    460       props.load(istream);
    461     }
    462     catch (Exception e) {
    463       LogLog.error("Could not read configuration file from URL [" + configURL
    464 		   + "].", e);
    465       LogLog.error("Ignoring configuration file [" + configURL +"].");
    466       return;
    467     }
    468     finally {
    469         if (istream != null) {
    470             try {
    471                 istream.close();
    472             } catch(Exception ignore) {
    473             }
    474         }
    475     }
    476     doConfigure(props, hierarchy);
    477   }
    478
    479
    480   // --------------------------------------------------------------------------
    481   // Internal stuff
    482   // --------------------------------------------------------------------------
    483
    484   /***
    485      Check the provided <code>Properties</code> object for a
    486      {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
    487      entry specified by {@link #LOGGER_FACTORY_KEY}.  If such an entry
    488      exists, an attempt is made to create an instance using the default
    489      constructor.  This instance is used for subsequent Category creations
    490      within this configurator.
    491
    492      @see #parseCatsAndRenderers
    493    */
    494   protected void configureLoggerFactory(Properties props) {
    495     String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
    496 							   props);
    497     if(factoryClassName != null) {
    498       LogLog.debug("Setting category factory to ["+factoryClassName+"].");
    499       loggerFactory = (LoggerFactory)
    500 	          OptionConverter.instantiateByClassName(factoryClassName,
    501 							 LoggerFactory.class,
    502 							 loggerFactory);
    503       PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
    504     }
    505   }
    506
    507   /*
    508   void configureOptionHandler(OptionHandler oh, String prefix,
    509 			      Properties props) {
    510     String[] options = oh.getOptionStrings();
    511     if(options == null)
    512       return;
    513
    514     String value;
    515     for(int i = 0; i < options.length; i++) {
    516       value =  OptionConverter.findAndSubst(prefix + options[i], props);
    517       LogLog.debug(
    518          "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"].");
    519       // Some option handlers assume that null value are not passed to them.
    520       // So don't remove this check
    521       if(value != null) {
    522 	oh.setOption(options[i], value);
    523       }
    524     }
    525     oh.activateOptions();
    526   }
    527   */
    528
    529
    530   void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    531     String effectiveFrefix = ROOT_LOGGER_PREFIX;
    532     String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
    533
    534     if(value == null) {
    535       value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
    536       effectiveFrefix = ROOT_CATEGORY_PREFIX;
    537     }
    538
    539     if(value == null)
    540       LogLog.debug("Could not find root logger information. Is this OK?");
    541     else {
    542       Logger root = hierarchy.getRootLogger();
    543       synchronized(root) {
    544 	parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
    545       }
    546     }
    547   }
    548
    549
    550   /***
    551      Parse non-root elements, such non-root categories and renderers.
    552   */
    553   protected
    554   void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
    555     Enumeration enumeration = props.propertyNames();
    556     while(enumeration.hasMoreElements()) {
    557       String key = (String) enumeration.nextElement();
    558       if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
    559 	String loggerName = null;
    560 	if(key.startsWith(CATEGORY_PREFIX)) {
    561 	  loggerName = key.substring(CATEGORY_PREFIX.length());
    562 	} else if(key.startsWith(LOGGER_PREFIX)) {
    563 	  loggerName = key.substring(LOGGER_PREFIX.length());
    564 	}
    565 	String value =  OptionConverter.findAndSubst(key, props);
    566 	Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
    567 	synchronized(logger) {
    568 	  parseCategory(props, logger, key, loggerName, value);
    569 	  parseAdditivityForLogger(props, logger, loggerName);
    570 	}
    571       } else if(key.startsWith(RENDERER_PREFIX)) {
    572 	String renderedClass = key.substring(RENDERER_PREFIX.length());
    573 	String renderingClass = OptionConverter.findAndSubst(key, props);
    574 	if(hierarchy instanceof RendererSupport) {
    575 	  RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
    576 				  renderingClass);
    577 	}
    578       }
    579     }
    580   }
    581
    582   /***
    583      Parse the additivity option for a non-root category.
    584    */
    585   void parseAdditivityForLogger(Properties props, Logger cat,
    586 				  String loggerName) {
    587     String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
    588 					     props);
    589     LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
    590     // touch additivity only if necessary
    591     if((value != null) && (!value.equals(""))) {
    592       boolean additivity = OptionConverter.toBoolean(value, true);
    593       LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
    594 		   additivity);
    595       cat.setAdditivity(additivity);
    596     }
    597   }
    598
    599   /***
    600      This method must work for the root category as well.
    601    */
    602   void parseCategory(Properties props, Logger logger, String optionKey,
    603 		     String loggerName, String value) {
    604
    605     LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
    606     // We must skip over ',' but not white space
    607     StringTokenizer st = new StringTokenizer(value, ",");
    608
    609     // If value is not in the form ", appender.." or "", then we should set
    610     // the level of the loggeregory.
    611
    612     if(!(value.startsWith(",") || value.equals(""))) {
    613
    614       // just to be on the safe side...
    615       if(!st.hasMoreTokens())
    616 	return;
    617
    618       String levelStr = st.nextToken();
    619       LogLog.debug("Level token is [" + levelStr + "].");
    620
    621       // If the level value is inherited, set category level value to
    622       // null. We also check that the user has not specified inherited for the
    623       // root category.
    624       if(INHERITED.equalsIgnoreCase(levelStr) ||
    625  	                                  NULL.equalsIgnoreCase(levelStr)) {
    626 	if(loggerName.equals(INTERNAL_ROOT_NAME)) {
    627 	  LogLog.warn("The root logger cannot be set to null.");
    628 	} else {
    629 	  logger.setLevel(null);
    630 	}
    631       } else {
    632 	logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
    633       }
    634       LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
    635     }
    636
    637     // Begin by removing all existing appenders.
    638     logger.removeAllAppenders();
    639
    640     Appender appender;
    641     String appenderName;
    642     while(st.hasMoreTokens()) {
    643       appenderName = st.nextToken().trim();
    644       if(appenderName == null || appenderName.equals(","))
    645 	continue;
    646       LogLog.debug("Parsing appender named \"" + appenderName +"\".");
    647       appender = parseAppender(props, appenderName);
    648       if(appender != null) {
    649 	logger.addAppender(appender);
    650       }
    651     }
    652   }
    653
    654   Appender parseAppender(Properties props, String appenderName) {
    655     Appender appender = registryGet(appenderName);
    656     if((appender != null)) {
    657       LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
    658       return appender;
    659     }
    660     // Appender was not previously initialized.
    661     String prefix = APPENDER_PREFIX + appenderName;
    662     String layoutPrefix = prefix + ".layout";
    663
    664     appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
    665 					      org.apache.log4j.Appender.class,
    666 					      null);
    667     if(appender == null) {
    668       LogLog.error(
    669               "Could not instantiate appender named \"" + appenderName+"\".");
    670       return null;
    671     }
    672     appender.setName(appenderName);
    673
    674     if(appender instanceof OptionHandler) {
    675       if(appender.requiresLayout()) {
    676 	Layout layout = (Layout) OptionConverter.instantiateByKey(props,
    677 								  layoutPrefix,
    678 								  Layout.class,
    679 								  null);
    680 	if(layout != null) {
    681 	  appender.setLayout(layout);
    682 	  LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
    683 	  //configureOptionHandler(layout, layoutPrefix + ".", props);
    684           PropertySetter.setProperties(layout, props, layoutPrefix + ".");
    685 	  LogLog.debug("End of parsing for \"" + appenderName +"\".");
    686 	}
    687       }
    688       //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
    689       PropertySetter.setProperties(appender, props, prefix + ".");
    690       LogLog.debug("Parsed \"" + appenderName +"\" options.");
    691     }
    692     registryPut(appender);
    693     return appender;
    694   }
    695
    696
    697   void  registryPut(Appender appender) {
    698     registry.put(appender.getName(), appender);
    699   }
    700
    701   Appender registryGet(String name) {
    702     return (Appender) registry.get(name);
    703   }
    704 }
    705
    706 class PropertyWatchdog extends FileWatchdog {
    707
    708   PropertyWatchdog(String filename) {
    709     super(filename);
    710   }
    711
    712   /***
    713      Call {@link PropertyConfigurator#configure(String)} with the
    714      <code>filename</code> to reconfigure log4j. */
    715   public
    716   void doOnChange() {
    717     new PropertyConfigurator().doConfigure(filename,
    718 					   LogManager.getLoggerRepository());
    719   }
    720 }