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 }