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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 // Contributors: "Luke Blanshard" <Luke@quiq.com>
20 // "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>
21 // Anders Kristensen <akristensen@dynamicsoft.com>
23 package org.apache.log4j;
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;
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;
54 Allows the configuration of log4j from an external file. See
55 <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
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.
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)}
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
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>"${"</b> and
78 closing <b>"}"</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>.
90 @author Ceki Gülcü
91 @author Anders Kristensen
93 public class PropertyConfigurator implements Configurator {
96 Used internally to keep track of configured appenders.
98 protected Hashtable registry = new Hashtable(11);
99 private LoggerRepository repository;
100 protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
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";
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";
122 * If property set to true, then hierarchy will be reset before configuration.
124 private static final String RESET_KEY = "log4j.reset";
126 static final private String INTERNAL_ROOT_NAME = "root";
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>.
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.
139 <h3>Repository-wide threshold</h3>
141 <p>The repository-wide threshold filters logging requests by level
142 regardless of logger. The syntax is:
145 log4j.threshold=[level]
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>.
156 <h3>Appender configuration</h3>
158 <p>Appender configuration syntax is:
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
164 # Set appender specific options.
165 log4j.appender.appenderName.option1=value1
167 log4j.appender.appenderName.optionN=valueN
170 For each named appender you can configure its {@link Layout}. The
171 syntax for configuring an appender's layout is:
173 log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
174 log4j.appender.appenderName.layout.option1=value1
176 log4j.appender.appenderName.layout.optionN=valueN
179 The syntax for adding {@link Filter}s to an appender is:
181 log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
182 log4j.appender.appenderName.filter.ID.option1=value1
184 log4j.appender.appenderName.filter.ID.optionN=valueN
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
191 The syntax for adding an {@link ErrorHandler} to an appender is:
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
199 log4j.appender.appenderName.errorhandler.optionN=valueN
202 <h3>Configuring loggers</h3>
204 <p>The syntax for configuring the root logger is:
206 log4j.rootLogger=[level], appenderName, appenderName, ...
209 <p>This syntax means that an optional <em>level</em> can be
210 supplied followed by appender names separated by commas.
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>.
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.
221 <p>The root logger can be assigned multiple appenders.
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.
227 <p>For non-root categories the syntax is almost the same:
229 log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
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.
237 <p>If no level value is supplied, then the level of the
238 named logger remains untouched.
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.
246 <p>Similar to the root logger syntax, each <i>appenderName</i>
247 (separated by commas) will be attached to the named logger.
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.
253 <h3>ObjectRenderers</h3>
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.
263 log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
268 log4j.renderer.my.Fruit=my.FruitRenderer
271 <h3>ThrowableRenderer</h3>
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}.
280 log4j.throwableRenderer=fully.qualified.name.of.rendering.class
281 log4j.throwableRenderer.paramName=paramValue
286 log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer
289 <h3>Logger Factories</h3>
291 The usage of custom logger factories is discouraged and no longer
294 <h3>Resetting Hierarchy</h3>
296 The hierarchy will be reset before configuration when
297 log4j.reset=true is present in the properties file.
301 <p>An example configuration is given below. Other configuration
302 file examples are given in the <code>examples</code> folder.
306 # Set options for appender named "A1".
307 # Appender "A1" will be a SyslogAppender
308 log4j.appender.A1=org.apache.log4j.net.SyslogAppender
310 # The syslog daemon resides on www.abc.net
311 log4j.appender.A1.SyslogHost=www.abc.net
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
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
336 # Root logger set to DEBUG using the A2 appender defined above.
337 log4j.rootLogger=DEBUG, A2
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
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
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
356 <p>Refer to the <b>setOption</b> method in each Appender and
357 Layout for class specific options.
359 <p>Use the <code>#</code> or <code>!</code> characters at the
360 beginning of a line for comments.
363 @param configFileName The name of the configuration file where the
364 configuration information is stored.
368 void doConfigure(String configFileName, LoggerRepository hierarchy) {
369 Properties props = new Properties();
370 FileInputStream istream = null;
372 istream = new FileInputStream(configFileName);
376 catch (Exception e) {
377 if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
378 Thread.currentThread().interrupt();
380 LogLog.error("Could not read configuration file ["+configFileName+"].", e);
381 LogLog.error("Ignoring configuration file [" + configFileName+"].");
384 if(istream != null) {
387 } catch(InterruptedIOException ignore) {
388 Thread.currentThread().interrupt();
389 } catch(Throwable ignore) {
394 // If we reach here, then the config file is alright.
395 doConfigure(props, hierarchy);
402 void configure(String configFilename) {
403 new PropertyConfigurator().doConfigure(configFilename,
404 LogManager.getLoggerRepository());
408 Read configuration options from url <code>configURL</code>.
414 void configure(java.net.URL configURL) {
415 new PropertyConfigurator().doConfigure(configURL,
416 LogManager.getLoggerRepository());
420 Reads configuration options from an InputStream.
426 void configure(InputStream inputStream) {
427 new PropertyConfigurator().doConfigure(inputStream,
428 LogManager.getLoggerRepository());
433 Read configuration options from <code>properties</code>.
435 See {@link #doConfigure(String, LoggerRepository)} for the expected format.
439 void configure(Properties properties) {
440 new PropertyConfigurator().doConfigure(properties,
441 LogManager.getLoggerRepository());
445 Like {@link #configureAndWatch(String, long)} except that the
446 default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
449 @param configFilename A file in key=value format.
454 void configureAndWatch(String configFilename) {
455 configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
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.
467 @param configFilename A file in key=value format.
468 @param delay The delay in milliseconds to wait between each check.
472 void configureAndWatch(String configFilename, long delay) {
473 PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
474 pdog.setDelay(delay);
480 Read configuration options from <code>properties</code>.
482 See {@link #doConfigure(String, LoggerRepository)} for the expected format.
485 void doConfigure(Properties properties, LoggerRepository hierarchy) {
486 repository = hierarchy;
487 String value = properties.getProperty(LogLog.DEBUG_KEY);
489 value = properties.getProperty("log4j.configDebug");
491 LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
496 LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
500 // if log4j.reset=true then
502 String reset = properties.getProperty(RESET_KEY);
503 if (reset != null && OptionConverter.toBoolean(reset, false)) {
504 hierarchy.resetConfiguration();
507 String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
509 if(thresholdStr != null) {
510 hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
512 LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
515 configureRootCategory(properties, hierarchy);
516 configureLoggerFactory(properties);
517 parseCatsAndRenderers(properties, hierarchy);
519 LogLog.debug("Finished configuring.");
520 // We don't want to hold references to appenders preventing their
521 // garbage collection.
526 * Read configuration options from an InputStream.
530 public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) {
531 Properties props = new Properties();
533 props.load(inputStream);
534 } catch (IOException e) {
535 if (e instanceof InterruptedIOException) {
536 Thread.currentThread().interrupt();
538 LogLog.error("Could not read configuration file from InputStream [" + inputStream
540 LogLog.error("Ignoring configuration InputStream [" + inputStream +"].");
543 this.doConfigure(props, hierarchy);
547 Read configuration options from url <code>configURL</code>.
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;
556 uConn = configURL.openConnection();
557 uConn.setUseCaches(false);
558 istream = uConn.getInputStream();
561 catch (Exception e) {
562 if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
563 Thread.currentThread().interrupt();
565 LogLog.error("Could not read configuration file from URL [" + configURL
567 LogLog.error("Ignoring configuration file [" + configURL +"].");
571 if (istream != null) {
574 } catch(InterruptedIOException ignore) {
575 Thread.currentThread().interrupt();
576 } catch(IOException ignore) {
577 } catch(RuntimeException ignore) {
581 doConfigure(props, hierarchy);
585 // --------------------------------------------------------------------------
587 // --------------------------------------------------------------------------
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.
597 @see #parseCatsAndRenderers
599 protected void configureLoggerFactory(Properties props) {
600 String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
602 if(factoryClassName != null) {
603 LogLog.debug("Setting category factory to ["+factoryClassName+"].");
604 loggerFactory = (LoggerFactory)
605 OptionConverter.instantiateByClassName(factoryClassName,
608 PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
613 void configureOptionHandler(OptionHandler oh, String prefix,
615 String[] options = oh.getOptionStrings();
620 for(int i = 0; i < options.length; i++) {
621 value = OptionConverter.findAndSubst(prefix + options[i], props);
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
627 oh.setOption(options[i], value);
630 oh.activateOptions();
635 void configureRootCategory(Properties props, LoggerRepository hierarchy) {
636 String effectiveFrefix = ROOT_LOGGER_PREFIX;
637 String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
640 value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
641 effectiveFrefix = ROOT_CATEGORY_PREFIX;
645 LogLog.debug("Could not find root logger information. Is this OK?");
647 Logger root = hierarchy.getRootLogger();
649 parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
656 Parse non-root elements, such non-root categories and renderers.
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());
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);
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,
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,
692 "Could not instantiate throwableRenderer.");
694 PropertySetter setter = new PropertySetter(tr);
695 setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
696 ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
705 Parse the additivity option for a non-root category.
707 void parseAdditivityForLogger(Properties props, Logger cat,
709 String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
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 "+
717 cat.setAdditivity(additivity);
722 This method must work for the root category as well.
724 void parseCategory(Properties props, Logger logger, String optionKey,
725 String loggerName, String value) {
727 LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
728 // We must skip over ',' but not white space
729 StringTokenizer st = new StringTokenizer(value, ",");
731 // If value is not in the form ", appender.." or "", then we should set
732 // the level of the loggeregory.
734 if(!(value.startsWith(",") || value.equals(""))) {
736 // just to be on the safe side...
737 if(!st.hasMoreTokens()) {
741 String levelStr = st.nextToken();
742 LogLog.debug("Level token is [" + levelStr + "].");
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
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.");
752 logger.setLevel(null);
755 logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG));
757 LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
760 // Begin by removing all existing appenders.
761 logger.removeAllAppenders();
765 while(st.hasMoreTokens()) {
766 appenderName = st.nextToken().trim();
767 if(appenderName == null || appenderName.equals(",")) {
770 LogLog.debug("Parsing appender named \"" + appenderName +"\".");
771 appender = parseAppender(props, appenderName);
772 if(appender != null) {
773 logger.addAppender(appender);
778 Appender parseAppender(Properties props, String appenderName) {
779 Appender appender = registryGet(appenderName);
780 if((appender != null)) {
781 LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
784 // Appender was not previously initialized.
785 String prefix = APPENDER_PREFIX + appenderName;
786 String layoutPrefix = prefix + ".layout";
788 appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
789 org.apache.log4j.Appender.class,
791 if(appender == null) {
793 "Could not instantiate appender named \"" + appenderName+"\".");
796 appender.setName(appenderName);
798 if(appender instanceof OptionHandler) {
799 if(appender.requiresLayout()) {
800 Layout layout = (Layout) OptionConverter.instantiateByKey(props,
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 +"\".");
812 final String errorHandlerPrefix = prefix + ".errorhandler";
813 String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
814 if (errorHandlerClass != null) {
815 ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
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
829 for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
830 Map.Entry entry = (Map.Entry) iter.next();
832 for(; i < keys.length; i++) {
833 if(keys[i].equals(entry.getKey())) {
837 if (i == keys.length) {
838 edited.put(entry.getKey(), entry.getValue());
841 PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
842 LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
846 //configureOptionHandler((OptionHandler) appender, prefix + ".", props);
847 PropertySetter.setProperties(appender, props, prefix + ".");
848 LogLog.debug("Parsed \"" + appenderName +"\" options.");
850 parseAppenderFilters(props, appenderName, appender);
851 registryPut(appender);
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);
863 eh.setLogger(hierarchy.getRootLogger());
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);
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);
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();
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;
896 filterKey = key.substring(0, dotIdx);
897 name = key.substring(dotIdx+1);
899 Vector filterOpts = (Vector) filters.get(filterKey);
900 if (filterOpts == null) {
901 filterOpts = new Vector();
902 filters.put(filterKey, filterOpts);
905 String value = OptionConverter.findAndSubst(key, props);
906 filterOpts.add(new NameValue(name, value));
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);
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);
928 propSetter.activate();
929 LogLog.debug("Adding filter of type ["+filter.getClass()
930 +"] to appender named ["+appender.getName()+"].");
931 appender.addFilter(filter);
934 LogLog.warn("Missing class definition for filter: ["+key+"]");
940 void registryPut(Appender appender) {
941 registry.put(appender.getName(), appender);
944 Appender registryGet(String name) {
945 return (Appender) registry.get(name);
949 class PropertyWatchdog extends FileWatchdog {
951 PropertyWatchdog(String filename) {
956 Call {@link PropertyConfigurator#configure(String)} with the
957 <code>filename</code> to reconfigure log4j. */
960 new PropertyConfigurator().doConfigure(filename,
961 LogManager.getLoggerRepository());
967 public NameValue(String key, String value) {
971 public String toString() {
972 return key + "=" + value;
976 class SortedKeyEnumeration implements Enumeration {
978 private Enumeration e;
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) {
996 public boolean hasMoreElements() {
997 return e.hasMoreElements();
1000 public Object nextElement() {
1001 return e.nextElement();