3e5cc261470ea58524423719bfa72f3d0aa92674
[jalview.git] / srcjar2 / org / apache / log4j / PropertyConfigurator.java
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 // Contributors: "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     pairs. 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 diagnostic 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
495     if(value != null) {
496       LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
497     }
498
499       //
500       //   if log4j.reset=true then
501       //        reset hierarchy
502     String reset = properties.getProperty(RESET_KEY);
503     if (reset != null && OptionConverter.toBoolean(reset, false)) {
504           hierarchy.resetConfiguration();
505     }
506
507     String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
508                                                        properties);
509     if(thresholdStr != null) {
510       hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
511                                                      Level.ALL));
512       LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
513     }
514     
515     configureRootCategory(properties, hierarchy);
516     configureLoggerFactory(properties);
517     parseCatsAndRenderers(properties, hierarchy);
518
519     LogLog.debug("Finished configuring.");
520     // We don't want to hold references to appenders preventing their
521     // garbage collection.
522     registry.clear();
523   }
524
525     /**
526      * Read configuration options from an InputStream.
527      * 
528      * @since 1.2.17
529      */
530     public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) {
531         Properties props = new Properties();
532         try {
533             props.load(inputStream);
534         } catch (IOException e) {
535             if (e instanceof InterruptedIOException) {
536                 Thread.currentThread().interrupt();
537             }
538             LogLog.error("Could not read configuration file from InputStream [" + inputStream
539                  + "].", e);
540             LogLog.error("Ignoring configuration InputStream [" + inputStream +"].");
541             return;
542           }
543         this.doConfigure(props, hierarchy);
544     }
545
546   /**
547      Read configuration options from url <code>configURL</code>.
548    */
549   public
550   void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
551     Properties props = new Properties();
552     LogLog.debug("Reading configuration from URL " + configURL);
553     InputStream istream = null;
554     URLConnection uConn = null;
555     try {
556       uConn = configURL.openConnection();
557       uConn.setUseCaches(false);
558       istream = uConn.getInputStream();
559       props.load(istream);
560     }
561     catch (Exception e) {
562       if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
563           Thread.currentThread().interrupt();
564       }
565       LogLog.error("Could not read configuration file from URL [" + configURL
566                    + "].", e);
567       LogLog.error("Ignoring configuration file [" + configURL +"].");
568       return;
569     }
570     finally {
571         if (istream != null) {
572             try {
573                 istream.close();
574             } catch(InterruptedIOException ignore) {
575                 Thread.currentThread().interrupt();
576             } catch(IOException ignore) {
577             } catch(RuntimeException ignore) {
578             }
579         }
580     }
581     doConfigure(props, hierarchy);
582   }
583
584
585   // --------------------------------------------------------------------------
586   // Internal stuff
587   // --------------------------------------------------------------------------
588
589   /**
590      Check the provided <code>Properties</code> object for a
591      {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
592      entry specified by {@link #LOGGER_FACTORY_KEY}.  If such an entry
593      exists, an attempt is made to create an instance using the default
594      constructor.  This instance is used for subsequent Category creations
595      within this configurator.
596
597      @see #parseCatsAndRenderers
598    */
599   protected void configureLoggerFactory(Properties props) {
600     String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
601                                                            props);
602     if(factoryClassName != null) {
603       LogLog.debug("Setting category factory to ["+factoryClassName+"].");
604       loggerFactory = (LoggerFactory)
605                   OptionConverter.instantiateByClassName(factoryClassName,
606                                                          LoggerFactory.class,
607                                                          loggerFactory);
608       PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
609     }
610   }
611
612   /*
613   void configureOptionHandler(OptionHandler oh, String prefix,
614                               Properties props) {
615     String[] options = oh.getOptionStrings();
616     if(options == null)
617       return;
618
619     String value;
620     for(int i = 0; i < options.length; i++) {
621       value =  OptionConverter.findAndSubst(prefix + options[i], props);
622       LogLog.debug(
623          "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"].");
624       // Some option handlers assume that null value are not passed to them.
625       // So don't remove this check
626       if(value != null) {
627         oh.setOption(options[i], value);
628       }
629     }
630     oh.activateOptions();
631   }
632   */
633
634
635   void configureRootCategory(Properties props, LoggerRepository hierarchy) {
636     String effectiveFrefix = ROOT_LOGGER_PREFIX;
637     String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
638
639     if(value == null) {
640       value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
641       effectiveFrefix = ROOT_CATEGORY_PREFIX;
642     }
643
644     if(value == null) {
645         LogLog.debug("Could not find root logger information. Is this OK?");
646     } else {
647       Logger root = hierarchy.getRootLogger();
648       synchronized(root) {
649         parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
650       }
651     }
652   }
653
654
655   /**
656      Parse non-root elements, such non-root categories and renderers.
657   */
658   protected
659   void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
660     Enumeration enumeration = props.propertyNames();
661     while(enumeration.hasMoreElements()) {
662       String key = (String) enumeration.nextElement();
663       if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
664         String loggerName = null;
665         if(key.startsWith(CATEGORY_PREFIX)) {
666           loggerName = key.substring(CATEGORY_PREFIX.length());
667         } else if(key.startsWith(LOGGER_PREFIX)) {
668           loggerName = key.substring(LOGGER_PREFIX.length());
669         }
670         String value =  OptionConverter.findAndSubst(key, props);
671         Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
672         synchronized(logger) {
673           parseCategory(props, logger, key, loggerName, value);
674           parseAdditivityForLogger(props, logger, loggerName);
675         }
676       } else if(key.startsWith(RENDERER_PREFIX)) {
677         String renderedClass = key.substring(RENDERER_PREFIX.length());
678         String renderingClass = OptionConverter.findAndSubst(key, props);
679         if(hierarchy instanceof RendererSupport) {
680           RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
681                                   renderingClass);
682         }
683       } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
684           if (hierarchy instanceof ThrowableRendererSupport) {
685             ThrowableRenderer tr = (ThrowableRenderer)
686                   OptionConverter.instantiateByKey(props,
687                           THROWABLE_RENDERER_PREFIX,
688                           org.apache.log4j.spi.ThrowableRenderer.class,
689                           null);
690             if(tr == null) {
691                 LogLog.error(
692                     "Could not instantiate throwableRenderer.");
693             } else {
694                 PropertySetter setter = new PropertySetter(tr);
695                 setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
696                 ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
697
698             }
699           }
700       }
701     }
702   }
703
704   /**
705      Parse the additivity option for a non-root category.
706    */
707   void parseAdditivityForLogger(Properties props, Logger cat,
708                                   String loggerName) {
709     String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
710                                              props);
711     LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
712     // touch additivity only if necessary
713     if((value != null) && (!value.equals(""))) {
714       boolean additivity = OptionConverter.toBoolean(value, true);
715       LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
716                    additivity);
717       cat.setAdditivity(additivity);
718     }
719   }
720
721   /**
722      This method must work for the root category as well.
723    */
724   void parseCategory(Properties props, Logger logger, String optionKey,
725                      String loggerName, String value) {
726
727     LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
728     // We must skip over ',' but not white space
729     StringTokenizer st = new StringTokenizer(value, ",");
730
731     // If value is not in the form ", appender.." or "", then we should set
732     // the level of the loggeregory.
733
734     if(!(value.startsWith(",") || value.equals(""))) {
735
736       // just to be on the safe side...
737       if(!st.hasMoreTokens()) {
738         return;
739     }
740
741       String levelStr = st.nextToken();
742       LogLog.debug("Level token is [" + levelStr + "].");
743
744       // If the level value is inherited, set category level value to
745       // null. We also check that the user has not specified inherited for the
746       // root category.
747       if(INHERITED.equalsIgnoreCase(levelStr) || 
748                                           NULL.equalsIgnoreCase(levelStr)) {
749         if(loggerName.equals(INTERNAL_ROOT_NAME)) {
750           LogLog.warn("The root logger cannot be set to null.");
751         } else {
752           logger.setLevel(null);
753         }
754       } else {
755         logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG));
756       }
757       LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
758     }
759
760     // Begin by removing all existing appenders.
761     logger.removeAllAppenders();
762
763     Appender appender;
764     String appenderName;
765     while(st.hasMoreTokens()) {
766       appenderName = st.nextToken().trim();
767       if(appenderName == null || appenderName.equals(",")) {
768         continue;
769     }
770       LogLog.debug("Parsing appender named \"" + appenderName +"\".");
771       appender = parseAppender(props, appenderName);
772       if(appender != null) {
773         logger.addAppender(appender);
774       }
775     }
776   }
777
778   Appender parseAppender(Properties props, String appenderName) {
779     Appender appender = registryGet(appenderName);
780     if((appender != null)) {
781       LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
782       return appender;
783     }
784     // Appender was not previously initialized.
785     String prefix = APPENDER_PREFIX + appenderName;
786     String layoutPrefix = prefix + ".layout";
787
788     appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
789                                               org.apache.log4j.Appender.class,
790                                               null);
791     if(appender == null) {
792       LogLog.error(
793               "Could not instantiate appender named \"" + appenderName+"\".");
794       return null;
795     }
796     appender.setName(appenderName);
797
798     if(appender instanceof OptionHandler) {
799       if(appender.requiresLayout()) {
800         Layout layout = (Layout) OptionConverter.instantiateByKey(props,
801                                                                   layoutPrefix,
802                                                                   Layout.class,
803                                                                   null);
804         if(layout != null) {
805           appender.setLayout(layout);
806           LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
807           //configureOptionHandler(layout, layoutPrefix + ".", props);
808           PropertySetter.setProperties(layout, props, layoutPrefix + ".");
809           LogLog.debug("End of parsing for \"" + appenderName +"\".");
810         }
811       }
812       final String errorHandlerPrefix = prefix + ".errorhandler";
813       String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
814       if (errorHandlerClass != null) {
815                 ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
816                                           errorHandlerPrefix,
817                                           ErrorHandler.class,
818                                           null);
819                 if (eh != null) {
820                           appender.setErrorHandler(eh);
821                           LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
822                           parseErrorHandler(eh, errorHandlerPrefix, props, repository);
823                           final Properties edited = new Properties();
824                           final String[] keys = new String[] { 
825                                           errorHandlerPrefix + "." + ROOT_REF,
826                                           errorHandlerPrefix + "." + LOGGER_REF,
827                                           errorHandlerPrefix + "." + APPENDER_REF_TAG
828                           };
829                           for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
830                                   Map.Entry entry = (Map.Entry) iter.next();
831                                   int i = 0;
832                                   for(; i < keys.length; i++) {
833                                           if(keys[i].equals(entry.getKey())) {
834                             break;
835                         }
836                                   }
837                                   if (i == keys.length) {
838                                           edited.put(entry.getKey(), entry.getValue());
839                                   }
840                           }
841                       PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
842                           LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
843                 }
844           
845       }
846       //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
847       PropertySetter.setProperties(appender, props, prefix + ".");
848       LogLog.debug("Parsed \"" + appenderName +"\" options.");
849     }
850     parseAppenderFilters(props, appenderName, appender);
851     registryPut(appender);
852     return appender;
853   }
854   
855   private void parseErrorHandler(
856                   final ErrorHandler eh,
857                   final String errorHandlerPrefix,
858                   final Properties props, 
859                   final LoggerRepository hierarchy) {
860                 boolean rootRef = OptionConverter.toBoolean(
861                                           OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
862                 if (rootRef) {
863                                   eh.setLogger(hierarchy.getRootLogger());
864             }
865                 String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props);
866                 if (loggerName != null) {
867                         Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
868                                         : hierarchy.getLogger(loggerName, loggerFactory);
869                         eh.setLogger(logger);
870                 }
871                 String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
872                 if (appenderName != null) {
873                         Appender backup = parseAppender(props, appenderName);
874                         if (backup != null) {
875                                 eh.setBackupAppender(backup);
876                         }
877                 }
878   }
879                                 
880   
881   void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
882     // extract filters and filter options from props into a hashtable mapping
883     // the property name defining the filter class to a list of pre-parsed
884     // name-value pairs associated to that filter
885     final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
886     int fIdx = filterPrefix.length();
887     Hashtable filters = new Hashtable();
888     Enumeration e = props.keys();
889     String name = "";
890     while (e.hasMoreElements()) {
891       String key = (String) e.nextElement();
892       if (key.startsWith(filterPrefix)) {
893         int dotIdx = key.indexOf('.', fIdx);
894         String filterKey = key;
895         if (dotIdx != -1) {
896           filterKey = key.substring(0, dotIdx);
897           name = key.substring(dotIdx+1);
898         }
899         Vector filterOpts = (Vector) filters.get(filterKey);
900         if (filterOpts == null) {
901           filterOpts = new Vector();
902           filters.put(filterKey, filterOpts);
903         }
904         if (dotIdx != -1) {
905           String value = OptionConverter.findAndSubst(key, props);
906           filterOpts.add(new NameValue(name, value));
907         }
908       }
909     }
910
911     // sort filters by IDs, insantiate filters, set filter options,
912     // add filters to the appender
913     Enumeration g = new SortedKeyEnumeration(filters);
914     while (g.hasMoreElements()) {
915       String key = (String) g.nextElement();
916       String clazz = props.getProperty(key);
917       if (clazz != null) {
918         LogLog.debug("Filter key: ["+key+"] class: ["+props.getProperty(key) +"] props: "+filters.get(key));
919         Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
920         if (filter != null) {
921           PropertySetter propSetter = new PropertySetter(filter);
922           Vector v = (Vector)filters.get(key);
923           Enumeration filterProps = v.elements();
924           while (filterProps.hasMoreElements()) {
925             NameValue kv = (NameValue)filterProps.nextElement();
926             propSetter.setProperty(kv.key, kv.value);
927           }
928           propSetter.activate();
929           LogLog.debug("Adding filter of type ["+filter.getClass()
930            +"] to appender named ["+appender.getName()+"].");
931           appender.addFilter(filter);
932         }
933       } else {
934         LogLog.warn("Missing class definition for filter: ["+key+"]");
935       }
936     }
937   }
938
939
940   void  registryPut(Appender appender) {
941     registry.put(appender.getName(), appender);
942   }
943
944   Appender registryGet(String name) {
945     return (Appender) registry.get(name);
946   }
947 }
948
949 class PropertyWatchdog extends FileWatchdog {
950
951   PropertyWatchdog(String filename) {
952     super(filename);
953   }
954
955   /**
956      Call {@link PropertyConfigurator#configure(String)} with the
957      <code>filename</code> to reconfigure log4j. */
958   public
959   void doOnChange() {
960     new PropertyConfigurator().doConfigure(filename,
961                                            LogManager.getLoggerRepository());
962   }
963 }
964
965 class NameValue {
966   String key, value;
967   public NameValue(String key, String value) {
968     this.key = key;
969     this.value = value;
970   }
971   public String toString() {
972     return key + "=" + value;
973   }
974 }
975
976 class SortedKeyEnumeration implements Enumeration {
977
978   private Enumeration e;
979
980   public SortedKeyEnumeration(Hashtable ht) {
981     Enumeration f = ht.keys();
982     Vector keys = new Vector(ht.size());
983     for (int i, last = 0; f.hasMoreElements(); ++last) {
984       String key = (String) f.nextElement();
985       for (i = 0; i < last; ++i) {
986         String s = (String) keys.get(i);
987         if (key.compareTo(s) <= 0) {
988             break;
989         }
990       }
991       keys.add(i, key);
992     }
993     e = keys.elements();
994   }
995
996   public boolean hasMoreElements() {
997     return e.hasMoreElements();
998   }
999
1000   public Object nextElement() {
1001     return e.nextElement();
1002   }
1003 }