View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  
19  // Contibutors: "Luke Blanshard" <Luke@quiq.com>
20  //              "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>
21  //               Anders Kristensen <akristensen@dynamicsoft.com>
22  
23  package org.apache.log4j;
24  
25  import java.io.FileInputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InterruptedIOException;
29  import java.net.URLConnection;
30  import java.util.Enumeration;
31  import java.util.Hashtable;
32  import java.util.Properties;
33  import java.util.StringTokenizer;
34  import java.util.Vector;
35  import java.util.Iterator;
36  import java.util.Map;
37  
38  import org.apache.log4j.config.PropertySetter;
39  import org.apache.log4j.helpers.FileWatchdog;
40  import org.apache.log4j.helpers.LogLog;
41  import org.apache.log4j.helpers.OptionConverter;
42  import org.apache.log4j.or.RendererMap;
43  import org.apache.log4j.spi.Configurator;
44  import org.apache.log4j.spi.Filter;
45  import org.apache.log4j.spi.LoggerFactory;
46  import org.apache.log4j.spi.LoggerRepository;
47  import org.apache.log4j.spi.OptionHandler;
48  import org.apache.log4j.spi.RendererSupport;
49  import org.apache.log4j.spi.ThrowableRenderer;
50  import org.apache.log4j.spi.ThrowableRendererSupport;
51  import org.apache.log4j.spi.ErrorHandler;
52  
53  /**
54     Allows the configuration of log4j from an external file.  See
55     <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
56     expected format.
57  
58     <p>It is sometimes useful to see how log4j is reading configuration
59     files. You can enable log4j internal logging by defining the
60     <b>log4j.debug</b> variable.
61  
62     <P>As of log4j version 0.8.5, at class initialization time class,
63     the file <b>log4j.properties</b> will be searched from the search
64     path used to load classes. If the file can be found, then it will
65     be fed to the {@link PropertyConfigurator#configure(java.net.URL)}
66     method.
67  
68     <p>The <code>PropertyConfigurator</code> does not handle the
69     advanced configuration features supported by the {@link
70     org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as
71     support custom {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers},
72     nested appenders such as the {@link org.apache.log4j.AsyncAppender
73     AsyncAppender}, etc.
74  
75     <p>All option <em>values</em> admit variable substitution. The
76     syntax of variable substitution is similar to that of Unix
77     shells. The string between an opening <b>&quot;${&quot;</b> and
78     closing <b>&quot;}&quot;</b> is interpreted as a key. The value of
79     the substituted variable can be defined as a system property or in
80     the configuration file itself. The value of the key is first
81     searched in the system properties, and if not found there, it is
82     then searched in the configuration file being parsed.  The
83     corresponding value replaces the ${variableName} sequence. For
84     example, if <code>java.home</code> system property is set to
85     <code>/home/xyz</code>, then every occurrence of the sequence
86     <code>${java.home}</code> will be interpreted as
87     <code>/home/xyz</code>.
88  
89  
90     @author Ceki G&uuml;lc&uuml;
91     @author Anders Kristensen
92     @since 0.8.1 */
93  public class PropertyConfigurator implements Configurator {
94  
95    /**
96       Used internally to keep track of configured appenders.
97     */
98    protected Hashtable registry = new Hashtable(11);  
99    private LoggerRepository repository;
100   protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
101 
102   static final String      CATEGORY_PREFIX = "log4j.category.";
103   static final String      LOGGER_PREFIX   = "log4j.logger.";
104   static final String       FACTORY_PREFIX = "log4j.factory";
105   static final String    ADDITIVITY_PREFIX = "log4j.additivity.";
106   static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
107   static final String ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
108   static final String      APPENDER_PREFIX = "log4j.appender.";
109   static final String      RENDERER_PREFIX = "log4j.renderer.";
110   static final String      THRESHOLD_PREFIX = "log4j.threshold";
111   private static final String      THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
112   private static final String LOGGER_REF	= "logger-ref";
113   private static final String ROOT_REF		= "root-ref";
114   private static final String APPENDER_REF_TAG 	= "appender-ref";  
115   
116 
117   /** Key for specifying the {@link org.apache.log4j.spi.LoggerFactory
118       LoggerFactory}.  Currently set to "<code>log4j.loggerFactory</code>".  */
119   public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
120 
121     /**
122      * If property set to true, then hierarchy will be reset before configuration.
123      */
124   private static final String RESET_KEY = "log4j.reset";
125 
126   static final private String INTERNAL_ROOT_NAME = "root";
127 
128   /**
129     Read configuration from a file. <b>The existing configuration is
130     not cleared nor reset.</b> If you require a different behavior,
131     then call {@link  LogManager#resetConfiguration
132     resetConfiguration} method before calling
133     <code>doConfigure</code>.
134 
135     <p>The configuration file consists of statements in the format
136     <code>key=value</code>. The syntax of different configuration
137     elements are discussed below.
138 
139     <h3>Repository-wide threshold</h3>
140 
141     <p>The repository-wide threshold filters logging requests by level
142     regardless of logger. The syntax is:
143 
144     <pre>
145     log4j.threshold=[level]
146     </pre>
147 
148     <p>The level value can consist of the string values OFF, FATAL,
149     ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
150     custom level value can be specified in the form
151     level#classname. By default the repository-wide threshold is set
152     to the lowest possible value, namely the level <code>ALL</code>.
153     </p>
154 
155 
156     <h3>Appender configuration</h3>
157 
158     <p>Appender configuration syntax is:
159     <pre>
160     # For appender named <i>appenderName</i>, set its class.
161     # Note: The appender name can contain dots.
162     log4j.appender.appenderName=fully.qualified.name.of.appender.class
163 
164     # Set appender specific options.
165     log4j.appender.appenderName.option1=value1
166     ...
167     log4j.appender.appenderName.optionN=valueN
168     </pre>
169 
170     For each named appender you can configure its {@link Layout}. The
171     syntax for configuring an appender's layout is:
172     <pre>
173     log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
174     log4j.appender.appenderName.layout.option1=value1
175     ....
176     log4j.appender.appenderName.layout.optionN=valueN
177     </pre>
178 
179     The syntax for adding {@link Filter}s to an appender is:
180     <pre>
181     log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
182     log4j.appender.appenderName.filter.ID.option1=value1
183     ...
184     log4j.appender.appenderName.filter.ID.optionN=valueN
185     </pre>
186     The first line defines the class name of the filter identified by ID;
187     subsequent lines with the same ID specify filter option - value
188     paris. Multiple filters are added to the appender in the lexicographic
189     order of IDs.
190 
191     The syntax for adding an {@link ErrorHandler} to an appender is:
192     <pre>
193     log4j.appender.appenderName.errorhandler=fully.qualified.name.of.filter.class
194     log4j.appender.appenderName.errorhandler.root-ref={true|false}
195     log4j.appender.appenderName.errorhandler.logger-ref=loggerName
196     log4j.appender.appenderName.errorhandler.appender-ref=appenderName
197     log4j.appender.appenderName.errorhandler.option1=value1
198     ...
199     log4j.appender.appenderName.errorhandler.optionN=valueN
200     </pre>
201 
202     <h3>Configuring loggers</h3>
203 
204     <p>The syntax for configuring the root logger is:
205     <pre>
206       log4j.rootLogger=[level], appenderName, appenderName, ...
207     </pre>
208 
209     <p>This syntax means that an optional <em>level</em> can be
210     supplied followed by appender names separated by commas.
211 
212     <p>The level value can consist of the string values OFF, FATAL,
213     ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
214     custom level value can be specified in the form
215     <code>level#classname</code>.
216 
217     <p>If a level value is specified, then the root level is set
218     to the corresponding level.  If no level value is specified,
219     then the root level remains untouched.
220 
221     <p>The root logger can be assigned multiple appenders.
222 
223     <p>Each <i>appenderName</i> (separated by commas) will be added to
224     the root logger. The named appender is defined using the
225     appender syntax defined above.
226 
227     <p>For non-root categories the syntax is almost the same:
228     <pre>
229     log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
230     </pre>
231 
232     <p>The meaning of the optional level value is discussed above
233     in relation to the root logger. In addition however, the value
234     INHERITED can be specified meaning that the named logger should
235     inherit its level from the logger hierarchy.
236 
237     <p>If no level value is supplied, then the level of the
238     named logger remains untouched.
239 
240     <p>By default categories inherit their level from the
241     hierarchy. However, if you set the level of a logger and later
242     decide that that logger should inherit its level, then you should
243     specify INHERITED as the value for the level value. NULL is a
244     synonym for INHERITED.
245 
246     <p>Similar to the root logger syntax, each <i>appenderName</i>
247     (separated by commas) will be attached to the named logger.
248 
249     <p>See the <a href="../../../../manual.html#additivity">appender
250     additivity rule</a> in the user manual for the meaning of the
251     <code>additivity</code> flag.
252 
253     <h3>ObjectRenderers</h3>
254 
255     You can customize the way message objects of a given type are
256     converted to String before being logged. This is done by
257     specifying an {@link org.apache.log4j.or.ObjectRenderer ObjectRenderer}
258     for the object type would like to customize.
259 
260     <p>The syntax is:
261 
262     <pre>
263     log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
264     </pre>
265 
266     As in,
267     <pre>
268     log4j.renderer.my.Fruit=my.FruitRenderer
269     </pre>
270 
271    <h3>ThrowableRenderer</h3>
272 
273    You can customize the way an instance of Throwable is
274    converted to String before being logged. This is done by
275    specifying an {@link org.apache.log4j.spi.ThrowableRenderer ThrowableRenderer}.
276 
277    <p>The syntax is:
278 
279    <pre>
280    log4j.throwableRenderer=fully.qualified.name.of.rendering.class
281    log4j.throwableRenderer.paramName=paramValue
282    </pre>
283 
284    As in,
285    <pre>
286    log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer
287    </pre>
288 
289     <h3>Logger Factories</h3>
290 
291     The usage of custom logger factories is discouraged and no longer
292     documented.
293 
294     <h3>Resetting Hierarchy</h3>
295 
296     The hierarchy will be reset before configuration when
297     log4j.reset=true is present in the properties file.
298 
299     <h3>Example</h3>
300 
301     <p>An example configuration is given below. Other configuration
302     file examples are given in the <code>examples</code> folder.
303 
304     <pre>
305 
306     # Set options for appender named "A1".
307     # Appender "A1" will be a SyslogAppender
308     log4j.appender.A1=org.apache.log4j.net.SyslogAppender
309 
310     # The syslog daemon resides on www.abc.net
311     log4j.appender.A1.SyslogHost=www.abc.net
312 
313     # A1's layout is a PatternLayout, using the conversion pattern
314     # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
315     # include # the relative time since the start of the application in
316     # milliseconds, followed by the level of the log request,
317     # followed by the two rightmost components of the logger name,
318     # followed by the callers method name, followed by the line number,
319     # the nested disgnostic context and finally the message itself.
320     # Refer to the documentation of {@link PatternLayout} for further information
321     # on the syntax of the ConversionPattern key.
322     log4j.appender.A1.layout=org.apache.log4j.PatternLayout
323     log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
324 
325     # Set options for appender named "A2"
326     # A2 should be a RollingFileAppender, with maximum file size of 10 MB
327     # using at most one backup file. A2's layout is TTCC, using the
328     # ISO8061 date format with context printing enabled.
329     log4j.appender.A2=org.apache.log4j.RollingFileAppender
330     log4j.appender.A2.MaxFileSize=10MB
331     log4j.appender.A2.MaxBackupIndex=1
332     log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
333     log4j.appender.A2.layout.ContextPrinting=enabled
334     log4j.appender.A2.layout.DateFormat=ISO8601
335 
336     # Root logger set to DEBUG using the A2 appender defined above.
337     log4j.rootLogger=DEBUG, A2
338 
339     # Logger definitions:
340     # The SECURITY logger inherits is level from root. However, it's output
341     # will go to A1 appender defined above. It's additivity is non-cumulative.
342     log4j.logger.SECURITY=INHERIT, A1
343     log4j.additivity.SECURITY=false
344 
345     # Only warnings or above will be logged for the logger "SECURITY.access".
346     # Output will go to A1.
347     log4j.logger.SECURITY.access=WARN
348 
349 
350     # The logger "class.of.the.day" inherits its level from the
351     # logger hierarchy.  Output will go to the appender's of the root
352     # logger, A2 in this case.
353     log4j.logger.class.of.the.day=INHERIT
354     </pre>
355 
356     <p>Refer to the <b>setOption</b> method in each Appender and
357     Layout for class specific options.
358 
359     <p>Use the <code>#</code> or <code>!</code> characters at the
360     beginning of a line for comments.
361 
362    <p>
363    @param configFileName The name of the configuration file where the
364    configuration information is stored.
365 
366   */
367   public
368   void doConfigure(String configFileName, LoggerRepository hierarchy) {
369     Properties props = new Properties();
370     FileInputStream istream = null;
371     try {
372       istream = new FileInputStream(configFileName);
373       props.load(istream);
374       istream.close();
375     }
376     catch (Exception e) {
377       if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
378           Thread.currentThread().interrupt();
379       }
380       LogLog.error("Could not read configuration file ["+configFileName+"].", e);
381       LogLog.error("Ignoring configuration file [" + configFileName+"].");
382       return;
383     } finally {
384         if(istream != null) {
385             try {
386                 istream.close();
387             } catch(InterruptedIOException ignore) {
388                 Thread.currentThread().interrupt();
389             } catch(Throwable ignore) {
390             }
391 
392         }
393     }
394     // If we reach here, then the config file is alright.
395     doConfigure(props, hierarchy);
396   }
397 
398   /**
399    */
400   static
401   public
402   void configure(String configFilename) {
403     new PropertyConfigurator().doConfigure(configFilename,
404 					   LogManager.getLoggerRepository());
405   }
406 
407   /**
408   Read configuration options from url <code>configURL</code>.
409 
410   @since 0.8.2
411 */
412 public
413 static
414 void configure(java.net.URL configURL) {
415  new PropertyConfigurator().doConfigure(configURL,
416                     LogManager.getLoggerRepository());
417 }
418 
419 /**
420 Reads configuration options from an InputStream.
421 
422 @since 1.2.17
423 */
424 public
425 static
426 void configure(InputStream inputStream) {
427 new PropertyConfigurator().doConfigure(inputStream,
428                   LogManager.getLoggerRepository());
429 }
430 
431 
432   /**
433      Read configuration options from <code>properties</code>.
434 
435      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
436   */
437   static
438   public
439   void configure(Properties properties) {
440     new PropertyConfigurator().doConfigure(properties,
441 					   LogManager.getLoggerRepository());
442   }
443 
444   /**
445      Like {@link #configureAndWatch(String, long)} except that the
446      default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
447      used.
448 
449      @param configFilename A file in key=value format.
450 
451   */
452   static
453   public
454   void configureAndWatch(String configFilename) {
455     configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
456   }
457 
458 
459   /**
460      Read the configuration file <code>configFilename</code> if it
461      exists. Moreover, a thread will be created that will periodically
462      check if <code>configFilename</code> has been created or
463      modified. The period is determined by the <code>delay</code>
464      argument. If a change or file creation is detected, then
465      <code>configFilename</code> is read to configure log4j.
466 
467       @param configFilename A file in key=value format.
468       @param delay The delay in milliseconds to wait between each check.
469   */
470   static
471   public
472   void configureAndWatch(String configFilename, long delay) {
473     PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
474     pdog.setDelay(delay);
475     pdog.start();
476   }
477 
478 
479   /**
480      Read configuration options from <code>properties</code>.
481 
482      See {@link #doConfigure(String, LoggerRepository)} for the expected format.
483   */
484   public
485   void doConfigure(Properties properties, LoggerRepository hierarchy) {
486 	repository = hierarchy;
487     String value = properties.getProperty(LogLog.DEBUG_KEY);
488     if(value == null) {
489       value = properties.getProperty("log4j.configDebug");
490       if(value != null)
491 	LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
492     }
493 
494     if(value != null) {
495       LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
496     }
497 
498       //
499       //   if log4j.reset=true then
500       //        reset hierarchy
501     String reset = properties.getProperty(RESET_KEY);
502     if (reset != null && OptionConverter.toBoolean(reset, false)) {
503           hierarchy.resetConfiguration();
504     }
505 
506     String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
507 						       properties);
508     if(thresholdStr != null) {
509       hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
510 						     (Level) Level.ALL));
511       LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
512     }
513     
514     configureRootCategory(properties, hierarchy);
515     configureLoggerFactory(properties);
516     parseCatsAndRenderers(properties, hierarchy);
517 
518     LogLog.debug("Finished configuring.");
519     // We don't want to hold references to appenders preventing their
520     // garbage collection.
521     registry.clear();
522   }
523 
524     /**
525      * Read configuration options from url <code>configURL</code>.
526      * 
527      * @since 1.2.17
528      */
529     public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) {
530         Properties props = new Properties();
531         try {
532             props.load(inputStream);
533         } catch (IOException e) {
534             if (e instanceof InterruptedIOException) {
535                 Thread.currentThread().interrupt();
536             }
537             LogLog.error("Could not read configuration file from InputStream [" + inputStream
538                  + "].", e);
539             LogLog.error("Ignoring configuration InputStream [" + inputStream +"].");
540             return;
541           }
542         this.doConfigure(props, hierarchy);
543     }
544 
545   /**
546      Read configuration options from url <code>configURL</code>.
547    */
548   public
549   void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
550     Properties props = new Properties();
551     LogLog.debug("Reading configuration from URL " + configURL);
552     InputStream istream = null;
553     URLConnection uConn = null;
554     try {
555       uConn = configURL.openConnection();
556       uConn.setUseCaches(false);
557       istream = uConn.getInputStream();
558       props.load(istream);
559     }
560     catch (Exception e) {
561       if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
562           Thread.currentThread().interrupt();
563       }
564       LogLog.error("Could not read configuration file from URL [" + configURL
565 		   + "].", e);
566       LogLog.error("Ignoring configuration file [" + configURL +"].");
567       return;
568     }
569     finally {
570         if (istream != null) {
571             try {
572                 istream.close();
573             } catch(InterruptedIOException ignore) {
574                 Thread.currentThread().interrupt();
575             } catch(IOException ignore) {
576             } catch(RuntimeException ignore) {
577             }
578         }
579     }
580     doConfigure(props, hierarchy);
581   }
582 
583 
584   // --------------------------------------------------------------------------
585   // Internal stuff
586   // --------------------------------------------------------------------------
587 
588   /**
589      Check the provided <code>Properties</code> object for a
590      {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
591      entry specified by {@link #LOGGER_FACTORY_KEY}.  If such an entry
592      exists, an attempt is made to create an instance using the default
593      constructor.  This instance is used for subsequent Category creations
594      within this configurator.
595 
596      @see #parseCatsAndRenderers
597    */
598   protected void configureLoggerFactory(Properties props) {
599     String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
600 							   props);
601     if(factoryClassName != null) {
602       LogLog.debug("Setting category factory to ["+factoryClassName+"].");
603       loggerFactory = (LoggerFactory)
604 	          OptionConverter.instantiateByClassName(factoryClassName,
605 							 LoggerFactory.class,
606 							 loggerFactory);
607       PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
608     }
609   }
610 
611   /*
612   void configureOptionHandler(OptionHandler oh, String prefix,
613 			      Properties props) {
614     String[] options = oh.getOptionStrings();
615     if(options == null)
616       return;
617 
618     String value;
619     for(int i = 0; i < options.length; i++) {
620       value =  OptionConverter.findAndSubst(prefix + options[i], props);
621       LogLog.debug(
622          "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"].");
623       // Some option handlers assume that null value are not passed to them.
624       // So don't remove this check
625       if(value != null) {
626 	oh.setOption(options[i], value);
627       }
628     }
629     oh.activateOptions();
630   }
631   */
632 
633 
634   void configureRootCategory(Properties props, LoggerRepository hierarchy) {
635     String effectiveFrefix = ROOT_LOGGER_PREFIX;
636     String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
637 
638     if(value == null) {
639       value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
640       effectiveFrefix = ROOT_CATEGORY_PREFIX;
641     }
642 
643     if(value == null)
644       LogLog.debug("Could not find root logger information. Is this OK?");
645     else {
646       Logger root = hierarchy.getRootLogger();
647       synchronized(root) {
648 	parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
649       }
650     }
651   }
652 
653 
654   /**
655      Parse non-root elements, such non-root categories and renderers.
656   */
657   protected
658   void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
659     Enumeration enumeration = props.propertyNames();
660     while(enumeration.hasMoreElements()) {
661       String key = (String) enumeration.nextElement();
662       if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
663 	String loggerName = null;
664 	if(key.startsWith(CATEGORY_PREFIX)) {
665 	  loggerName = key.substring(CATEGORY_PREFIX.length());
666 	} else if(key.startsWith(LOGGER_PREFIX)) {
667 	  loggerName = key.substring(LOGGER_PREFIX.length());
668 	}
669 	String value =  OptionConverter.findAndSubst(key, props);
670 	Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
671 	synchronized(logger) {
672 	  parseCategory(props, logger, key, loggerName, value);
673 	  parseAdditivityForLogger(props, logger, loggerName);
674 	}
675       } else if(key.startsWith(RENDERER_PREFIX)) {
676 	String renderedClass = key.substring(RENDERER_PREFIX.length());
677 	String renderingClass = OptionConverter.findAndSubst(key, props);
678 	if(hierarchy instanceof RendererSupport) {
679 	  RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
680 				  renderingClass);
681 	}
682       } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
683           if (hierarchy instanceof ThrowableRendererSupport) {
684             ThrowableRenderer tr = (ThrowableRenderer)
685                   OptionConverter.instantiateByKey(props,
686                           THROWABLE_RENDERER_PREFIX,
687                           org.apache.log4j.spi.ThrowableRenderer.class,
688                           null);
689             if(tr == null) {
690                 LogLog.error(
691                     "Could not instantiate throwableRenderer.");
692             } else {
693                 PropertySetter setter = new PropertySetter(tr);
694                 setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
695                 ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
696 
697             }
698           }
699       }
700     }
701   }
702 
703   /**
704      Parse the additivity option for a non-root category.
705    */
706   void parseAdditivityForLogger(Properties props, Logger cat,
707 				  String loggerName) {
708     String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
709 					     props);
710     LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
711     // touch additivity only if necessary
712     if((value != null) && (!value.equals(""))) {
713       boolean additivity = OptionConverter.toBoolean(value, true);
714       LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
715 		   additivity);
716       cat.setAdditivity(additivity);
717     }
718   }
719 
720   /**
721      This method must work for the root category as well.
722    */
723   void parseCategory(Properties props, Logger logger, String optionKey,
724 		     String loggerName, String value) {
725 
726     LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
727     // We must skip over ',' but not white space
728     StringTokenizer st = new StringTokenizer(value, ",");
729 
730     // If value is not in the form ", appender.." or "", then we should set
731     // the level of the loggeregory.
732 
733     if(!(value.startsWith(",") || value.equals(""))) {
734 
735       // just to be on the safe side...
736       if(!st.hasMoreTokens())
737 	return;
738 
739       String levelStr = st.nextToken();
740       LogLog.debug("Level token is [" + levelStr + "].");
741 
742       // If the level value is inherited, set category level value to
743       // null. We also check that the user has not specified inherited for the
744       // root category.
745       if(INHERITED.equalsIgnoreCase(levelStr) || 
746  	                                  NULL.equalsIgnoreCase(levelStr)) {
747 	if(loggerName.equals(INTERNAL_ROOT_NAME)) {
748 	  LogLog.warn("The root logger cannot be set to null.");
749 	} else {
750 	  logger.setLevel(null);
751 	}
752       } else {
753 	logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
754       }
755       LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
756     }
757 
758     // Begin by removing all existing appenders.
759     logger.removeAllAppenders();
760 
761     Appender appender;
762     String appenderName;
763     while(st.hasMoreTokens()) {
764       appenderName = st.nextToken().trim();
765       if(appenderName == null || appenderName.equals(","))
766 	continue;
767       LogLog.debug("Parsing appender named \"" + appenderName +"\".");
768       appender = parseAppender(props, appenderName);
769       if(appender != null) {
770 	logger.addAppender(appender);
771       }
772     }
773   }
774 
775   Appender parseAppender(Properties props, String appenderName) {
776     Appender appender = registryGet(appenderName);
777     if((appender != null)) {
778       LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
779       return appender;
780     }
781     // Appender was not previously initialized.
782     String prefix = APPENDER_PREFIX + appenderName;
783     String layoutPrefix = prefix + ".layout";
784 
785     appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
786 					      org.apache.log4j.Appender.class,
787 					      null);
788     if(appender == null) {
789       LogLog.error(
790               "Could not instantiate appender named \"" + appenderName+"\".");
791       return null;
792     }
793     appender.setName(appenderName);
794 
795     if(appender instanceof OptionHandler) {
796       if(appender.requiresLayout()) {
797 	Layout layout = (Layout) OptionConverter.instantiateByKey(props,
798 								  layoutPrefix,
799 								  Layout.class,
800 								  null);
801 	if(layout != null) {
802 	  appender.setLayout(layout);
803 	  LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
804 	  //configureOptionHandler(layout, layoutPrefix + ".", props);
805           PropertySetter.setProperties(layout, props, layoutPrefix + ".");
806 	  LogLog.debug("End of parsing for \"" + appenderName +"\".");
807 	}
808       }
809       final String errorHandlerPrefix = prefix + ".errorhandler";
810       String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
811       if (errorHandlerClass != null) {
812     		ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
813 					  errorHandlerPrefix,
814 					  ErrorHandler.class,
815 					  null);
816     		if (eh != null) {
817     			  appender.setErrorHandler(eh);
818     			  LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
819     			  parseErrorHandler(eh, errorHandlerPrefix, props, repository);
820     			  final Properties edited = new Properties();
821     			  final String[] keys = new String[] { 
822     					  errorHandlerPrefix + "." + ROOT_REF,
823     					  errorHandlerPrefix + "." + LOGGER_REF,
824     					  errorHandlerPrefix + "." + APPENDER_REF_TAG
825     			  };
826     			  for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
827     				  Map.Entry entry = (Map.Entry) iter.next();
828     				  int i = 0;
829     				  for(; i < keys.length; i++) {
830     					  if(keys[i].equals(entry.getKey())) break;
831     				  }
832     				  if (i == keys.length) {
833     					  edited.put(entry.getKey(), entry.getValue());
834     				  }
835     			  }
836     		      PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
837     			  LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
838     		}
839     	  
840       }
841       //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
842       PropertySetter.setProperties(appender, props, prefix + ".");
843       LogLog.debug("Parsed \"" + appenderName +"\" options.");
844     }
845     parseAppenderFilters(props, appenderName, appender);
846     registryPut(appender);
847     return appender;
848   }
849   
850   private void parseErrorHandler(
851 		  final ErrorHandler eh,
852 		  final String errorHandlerPrefix,
853 		  final Properties props, 
854 		  final LoggerRepository hierarchy) {
855 		boolean rootRef = OptionConverter.toBoolean(
856 					  OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
857 		if (rootRef) {
858 				  eh.setLogger(hierarchy.getRootLogger());
859 	    }
860 		String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props);
861 		if (loggerName != null) {
862 			Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
863 			                : hierarchy.getLogger(loggerName, loggerFactory);
864 			eh.setLogger(logger);
865 		}
866 		String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
867 		if (appenderName != null) {
868 			Appender backup = parseAppender(props, appenderName);
869 			if (backup != null) {
870 				eh.setBackupAppender(backup);
871 			}
872 		}
873   }
874 				
875   
876   void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
877     // extract filters and filter options from props into a hashtable mapping
878     // the property name defining the filter class to a list of pre-parsed
879     // name-value pairs associated to that filter
880     final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
881     int fIdx = filterPrefix.length();
882     Hashtable filters = new Hashtable();
883     Enumeration e = props.keys();
884     String name = "";
885     while (e.hasMoreElements()) {
886       String key = (String) e.nextElement();
887       if (key.startsWith(filterPrefix)) {
888         int dotIdx = key.indexOf('.', fIdx);
889         String filterKey = key;
890         if (dotIdx != -1) {
891           filterKey = key.substring(0, dotIdx);
892           name = key.substring(dotIdx+1);
893         }
894         Vector filterOpts = (Vector) filters.get(filterKey);
895         if (filterOpts == null) {
896           filterOpts = new Vector();
897           filters.put(filterKey, filterOpts);
898         }
899         if (dotIdx != -1) {
900           String value = OptionConverter.findAndSubst(key, props);
901           filterOpts.add(new NameValue(name, value));
902         }
903       }
904     }
905 
906     // sort filters by IDs, insantiate filters, set filter options,
907     // add filters to the appender
908     Enumeration g = new SortedKeyEnumeration(filters);
909     while (g.hasMoreElements()) {
910       String key = (String) g.nextElement();
911       String clazz = props.getProperty(key);
912       if (clazz != null) {
913         LogLog.debug("Filter key: ["+key+"] class: ["+props.getProperty(key) +"] props: "+filters.get(key));
914         Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
915         if (filter != null) {
916           PropertySetter propSetter = new PropertySetter(filter);
917           Vector v = (Vector)filters.get(key);
918           Enumeration filterProps = v.elements();
919           while (filterProps.hasMoreElements()) {
920             NameValue kv = (NameValue)filterProps.nextElement();
921             propSetter.setProperty(kv.key, kv.value);
922           }
923           propSetter.activate();
924           LogLog.debug("Adding filter of type ["+filter.getClass()
925            +"] to appender named ["+appender.getName()+"].");
926           appender.addFilter(filter);
927         }
928       } else {
929         LogLog.warn("Missing class definition for filter: ["+key+"]");
930       }
931     }
932   }
933 
934 
935   void  registryPut(Appender appender) {
936     registry.put(appender.getName(), appender);
937   }
938 
939   Appender registryGet(String name) {
940     return (Appender) registry.get(name);
941   }
942 }
943 
944 class PropertyWatchdog extends FileWatchdog {
945 
946   PropertyWatchdog(String filename) {
947     super(filename);
948   }
949 
950   /**
951      Call {@link PropertyConfigurator#configure(String)} with the
952      <code>filename</code> to reconfigure log4j. */
953   public
954   void doOnChange() {
955     new PropertyConfigurator().doConfigure(filename,
956 					   LogManager.getLoggerRepository());
957   }
958 }
959 
960 class NameValue {
961   String key, value;
962   public NameValue(String key, String value) {
963     this.key = key;
964     this.value = value;
965   }
966   public String toString() {
967     return key + "=" + value;
968   }
969 }
970 
971 class SortedKeyEnumeration implements Enumeration {
972 
973   private Enumeration e;
974 
975   public SortedKeyEnumeration(Hashtable ht) {
976     Enumeration f = ht.keys();
977     Vector keys = new Vector(ht.size());
978     for (int i, last = 0; f.hasMoreElements(); ++last) {
979       String key = (String) f.nextElement();
980       for (i = 0; i < last; ++i) {
981         String s = (String) keys.get(i);
982         if (key.compareTo(s) <= 0) break;
983       }
984       keys.add(i, key);
985     }
986     e = keys.elements();
987   }
988 
989   public boolean hasMoreElements() {
990     return e.hasMoreElements();
991   }
992 
993   public Object nextElement() {
994     return e.nextElement();
995   }
996 }