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