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