1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.apache.log4j;
24
25 import org.apache.log4j.config.PropertySetter;
26 import org.apache.log4j.helpers.FileWatchdog;
27 import org.apache.log4j.helpers.LogLog;
28 import org.apache.log4j.helpers.OptionConverter;
29 import org.apache.log4j.or.RendererMap;
30 import org.apache.log4j.spi.Configurator;
31 import org.apache.log4j.spi.LoggerFactory;
32 import org.apache.log4j.spi.LoggerRepository;
33 import org.apache.log4j.spi.OptionHandler;
34 import org.apache.log4j.spi.RendererSupport;
35
36 import java.io.FileInputStream;
37 import java.io.InputStream;
38 import java.util.Enumeration;
39 import java.util.Hashtable;
40 import java.util.Properties;
41 import java.util.StringTokenizer;
42
43 /***
44 Allows the configuration of log4j from an external file. See
45 <b>{@link #doConfigure(String, LoggerRepository)}</b> for the
46 expected format.
47
48 <p>It is sometimes useful to see how log4j is reading configuration
49 files. You can enable log4j internal logging by defining the
50 <b>log4j.debug</b> variable.
51
52 <P>As of log4j version 0.8.5, at class initialization time class,
53 the file <b>log4j.properties</b> will be searched from the search
54 path used to load classes. If the file can be found, then it will
55 be fed to the {@link PropertyConfigurator#configure(java.net.URL)}
56 method.
57
58 <p>The <code>PropertyConfigurator</code> does not handle the
59 advanced configuration features supported by the {@link
60 org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as
61 support for {@link org.apache.log4j.spi.Filter Filters}, custom
62 {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers}, nested
63 appenders such as the {@link org.apache.log4j.AsyncAppender
64 AsyncAppender}, etc.
65
66 <p>All option <em>values</em> admit variable substitution. The
67 syntax of variable substitution is similar to that of Unix
68 shells. The string between an opening <b>"${"</b> and
69 closing <b>"}"</b> is interpreted as a key. The value of
70 the substituted variable can be defined as a system property or in
71 the configuration file itself. The value of the key is first
72 searched in the system properties, and if not found there, it is
73 then searched in the configuration file being parsed. The
74 corresponding value replaces the ${variableName} sequence. For
75 example, if <code>java.home</code> system property is set to
76 <code>/home/xyz</code>, then every occurrence of the sequence
77 <code>${java.home}</code> will be interpreted as
78 <code>/home/xyz</code>.
79
80
81 @author Ceki Gülcü
82 @author Anders Kristensen
83 @since 0.8.1 */
84 public class PropertyConfigurator implements Configurator {
85
86 /***
87 Used internally to keep track of configured appenders.
88 */
89 protected Hashtable registry = new Hashtable(11);
90 protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
91
92 static final String CATEGORY_PREFIX = "log4j.category.";
93 static final String LOGGER_PREFIX = "log4j.logger.";
94 static final String FACTORY_PREFIX = "log4j.factory";
95 static final String ADDITIVITY_PREFIX = "log4j.additivity.";
96 static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
97 static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
98 static final String APPENDER_PREFIX = "log4j.appender.";
99 static final String RENDERER_PREFIX = "log4j.renderer.";
100 static final String THRESHOLD_PREFIX = "log4j.threshold";
101
102 /*** Key for specifying the {@link org.apache.log4j.spi.LoggerFactory
103 LoggerFactory}. Currently set to "<code>log4j.loggerFactory</code>". */
104 public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
105
106 /***
107 * If property set to true, then hierarchy will be reset before configuration.
108 */
109 private static final String RESET_KEY = "log4j.reset";
110
111 static final private String INTERNAL_ROOT_NAME = "root";
112
113 /***
114 Read configuration from a file. <b>The existing configuration is
115 not cleared nor reset.</b> If you require a different behavior,
116 then call {@link LogManager#resetConfiguration
117 resetConfiguration} method before calling
118 <code>doConfigure</code>.
119
120 <p>The configuration file consists of statements in the format
121 <code>key=value</code>. The syntax of different configuration
122 elements are discussed below.
123
124 <h3>Repository-wide threshold</h3>
125
126 <p>The repository-wide threshold filters logging requests by level
127 regardless of logger. The syntax is:
128
129 <pre>
130 log4j.threshold=[level]
131 </pre>
132
133 <p>The level value can consist of the string values OFF, FATAL,
134 ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
135 custom level value can be specified in the form
136 level#classname. By default the repository-wide threshold is set
137 to the lowest possible value, namely the level <code>ALL</code>.
138 </p>
139
140
141 <h3>Appender configuration</h3>
142
143 <p>Appender configuration syntax is:
144 <pre>
145 # For appender named <i>appenderName</i>, set its class.
146 # Note: The appender name can contain dots.
147 log4j.appender.appenderName=fully.qualified.name.of.appender.class
148
149 # Set appender specific options.
150 log4j.appender.appenderName.option1=value1
151 ...
152 log4j.appender.appenderName.optionN=valueN
153 </pre>
154
155 For each named appender you can configure its {@link Layout}. The
156 syntax for configuring an appender's layout is:
157 <pre>
158 log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
159 log4j.appender.appenderName.layout.option1=value1
160 ....
161 log4j.appender.appenderName.layout.optionN=valueN
162 </pre>
163
164 <h3>Configuring loggers</h3>
165
166 <p>The syntax for configuring the root logger is:
167 <pre>
168 log4j.rootLogger=[level], appenderName, appenderName, ...
169 </pre>
170
171 <p>This syntax means that an optional <em>level</em> can be
172 supplied followed by appender names separated by commas.
173
174 <p>The level value can consist of the string values OFF, FATAL,
175 ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
176 custom level value can be specified in the form
177 <code>level#classname</code>.
178
179 <p>If a level value is specified, then the root level is set
180 to the corresponding level. If no level value is specified,
181 then the root level remains untouched.
182
183 <p>The root logger can be assigned multiple appenders.
184
185 <p>Each <i>appenderName</i> (separated by commas) will be added to
186 the root logger. The named appender is defined using the
187 appender syntax defined above.
188
189 <p>For non-root categories the syntax is almost the same:
190 <pre>
191 log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
192 </pre>
193
194 <p>The meaning of the optional level value is discussed above
195 in relation to the root logger. In addition however, the value
196 INHERITED can be specified meaning that the named logger should
197 inherit its level from the logger hierarchy.
198
199 <p>If no level value is supplied, then the level of the
200 named logger remains untouched.
201
202 <p>By default categories inherit their level from the
203 hierarchy. However, if you set the level of a logger and later
204 decide that that logger should inherit its level, then you should
205 specify INHERITED as the value for the level value. NULL is a
206 synonym for INHERITED.
207
208 <p>Similar to the root logger syntax, each <i>appenderName</i>
209 (separated by commas) will be attached to the named logger.
210
211 <p>See the <a href="../../../../manual.html#additivity">appender
212 additivity rule</a> in the user manual for the meaning of the
213 <code>additivity</code> flag.
214
215 <h3>ObjectRenderers</h3>
216
217 You can customize the way message objects of a given type are
218 converted to String before being logged. This is done by
219 specifying an {@link org.apache.log4j.or.ObjectRenderer ObjectRenderer}
220 for the object type would like to customize.
221
222 <p>The syntax is:
223
224 <pre>
225 log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class
226 </pre>
227
228 As in,
229 <pre>
230 log4j.renderer.my.Fruit=my.FruitRenderer
231 </pre>
232
233 <h3>Logger Factories</h3>
234
235 The usage of custom logger factories is discouraged and no longer
236 documented.
237
238 <h3>Resetting Hierarchy</h3>
239
240 The hierarchy will be reset before configuration when
241 log4j.reset=true is present in the properties file.
242
243 <h3>Example</h3>
244
245 <p>An example configuration is given below. Other configuration
246 file examples are given in the <code>examples</code> folder.
247
248 <pre>
249
250 # Set options for appender named "A1".
251 # Appender "A1" will be a SyslogAppender
252 log4j.appender.A1=org.apache.log4j.net.SyslogAppender
253
254 # The syslog daemon resides on www.abc.net
255 log4j.appender.A1.SyslogHost=www.abc.net
256
257 # A1's layout is a PatternLayout, using the conversion pattern
258 # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
259 # include # the relative time since the start of the application in
260 # milliseconds, followed by the level of the log request,
261 # followed by the two rightmost components of the logger name,
262 # followed by the callers method name, followed by the line number,
263 # the nested disgnostic context and finally the message itself.
264 # Refer to the documentation of {@link PatternLayout} for further information
265 # on the syntax of the ConversionPattern key.
266 log4j.appender.A1.layout=org.apache.log4j.PatternLayout
267 log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
268
269 # Set options for appender named "A2"
270 # A2 should be a RollingFileAppender, with maximum file size of 10 MB
271 # using at most one backup file. A2's layout is TTCC, using the
272 # ISO8061 date format with context printing enabled.
273 log4j.appender.A2=org.apache.log4j.RollingFileAppender
274 log4j.appender.A2.MaxFileSize=10MB
275 log4j.appender.A2.MaxBackupIndex=1
276 log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
277 log4j.appender.A2.layout.ContextPrinting=enabled
278 log4j.appender.A2.layout.DateFormat=ISO8601
279
280 # Root logger set to DEBUG using the A2 appender defined above.
281 log4j.rootLogger=DEBUG, A2
282
283 # Logger definitions:
284 # The SECURITY logger inherits is level from root. However, it's output
285 # will go to A1 appender defined above. It's additivity is non-cumulative.
286 log4j.logger.SECURITY=INHERIT, A1
287 log4j.additivity.SECURITY=false
288
289 # Only warnings or above will be logged for the logger "SECURITY.access".
290 # Output will go to A1.
291 log4j.logger.SECURITY.access=WARN
292
293
294 # The logger "class.of.the.day" inherits its level from the
295 # logger hierarchy. Output will go to the appender's of the root
296 # logger, A2 in this case.
297 log4j.logger.class.of.the.day=INHERIT
298 </pre>
299
300 <p>Refer to the <b>setOption</b> method in each Appender and
301 Layout for class specific options.
302
303 <p>Use the <code>#</code> or <code>!</code> characters at the
304 beginning of a line for comments.
305
306 <p>
307 @param configFileName The name of the configuration file where the
308 configuration information is stored.
309
310 */
311 public
312 void doConfigure(String configFileName, LoggerRepository hierarchy) {
313 Properties props = new Properties();
314 FileInputStream istream = null;
315 try {
316 istream = new FileInputStream(configFileName);
317 props.load(istream);
318 istream.close();
319 }
320 catch (Exception e) {
321 LogLog.error("Could not read configuration file ["+configFileName+"].", e);
322 LogLog.error("Ignoring configuration file [" + configFileName+"].");
323 return;
324 } finally {
325 if(istream != null) {
326 try {
327 istream.close();
328 } catch(Throwable ignore) {
329 }
330
331 }
332 }
333
334 doConfigure(props, hierarchy);
335 }
336
337 /***
338 */
339 static
340 public
341 void configure(String configFilename) {
342 new PropertyConfigurator().doConfigure(configFilename,
343 LogManager.getLoggerRepository());
344 }
345
346 /***
347 Read configuration options from url <code>configURL</code>.
348
349 @since 0.8.2
350 */
351 public
352 static
353 void configure(java.net.URL configURL) {
354 new PropertyConfigurator().doConfigure(configURL,
355 LogManager.getLoggerRepository());
356 }
357
358
359 /***
360 Read configuration options from <code>properties</code>.
361
362 See {@link #doConfigure(String, LoggerRepository)} for the expected format.
363 */
364 static
365 public
366 void configure(Properties properties) {
367 new PropertyConfigurator().doConfigure(properties,
368 LogManager.getLoggerRepository());
369 }
370
371 /***
372 Like {@link #configureAndWatch(String, long)} except that the
373 default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
374 used.
375
376 @param configFilename A file in key=value format.
377
378 */
379 static
380 public
381 void configureAndWatch(String configFilename) {
382 configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
383 }
384
385
386 /***
387 Read the configuration file <code>configFilename</code> if it
388 exists. Moreover, a thread will be created that will periodically
389 check if <code>configFilename</code> has been created or
390 modified. The period is determined by the <code>delay</code>
391 argument. If a change or file creation is detected, then
392 <code>configFilename</code> is read to configure log4j.
393
394 @param configFilename A file in key=value format.
395 @param delay The delay in milliseconds to wait between each check.
396 */
397 static
398 public
399 void configureAndWatch(String configFilename, long delay) {
400 PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
401 pdog.setDelay(delay);
402 pdog.start();
403 }
404
405
406 /***
407 Read configuration options from <code>properties</code>.
408
409 See {@link #doConfigure(String, LoggerRepository)} for the expected format.
410 */
411 public
412 void doConfigure(Properties properties, LoggerRepository hierarchy) {
413 String value = properties.getProperty(LogLog.DEBUG_KEY);
414 if(value == null) {
415 value = properties.getProperty("log4j.configDebug");
416 if(value != null)
417 LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
418 }
419
420 if(value != null) {
421 LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
422 }
423
424
425
426
427 String reset = properties.getProperty(RESET_KEY);
428 if (reset != null && OptionConverter.toBoolean(reset, false)) {
429 hierarchy.resetConfiguration();
430 }
431
432 String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
433 properties);
434 if(thresholdStr != null) {
435 hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
436 (Level) Level.ALL));
437 LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
438 }
439
440 configureRootCategory(properties, hierarchy);
441 configureLoggerFactory(properties);
442 parseCatsAndRenderers(properties, hierarchy);
443
444 LogLog.debug("Finished configuring.");
445
446
447 registry.clear();
448 }
449
450 /***
451 Read configuration options from url <code>configURL</code>.
452 */
453 public
454 void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
455 Properties props = new Properties();
456 LogLog.debug("Reading configuration from URL " + configURL);
457 InputStream istream = null;
458 try {
459 istream = configURL.openStream();
460 props.load(istream);
461 }
462 catch (Exception e) {
463 LogLog.error("Could not read configuration file from URL [" + configURL
464 + "].", e);
465 LogLog.error("Ignoring configuration file [" + configURL +"].");
466 return;
467 }
468 finally {
469 if (istream != null) {
470 try {
471 istream.close();
472 } catch(Exception ignore) {
473 }
474 }
475 }
476 doConfigure(props, hierarchy);
477 }
478
479
480
481
482
483
484 /***
485 Check the provided <code>Properties</code> object for a
486 {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
487 entry specified by {@link #LOGGER_FACTORY_KEY}. If such an entry
488 exists, an attempt is made to create an instance using the default
489 constructor. This instance is used for subsequent Category creations
490 within this configurator.
491
492 @see #parseCatsAndRenderers
493 */
494 protected void configureLoggerFactory(Properties props) {
495 String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY,
496 props);
497 if(factoryClassName != null) {
498 LogLog.debug("Setting category factory to ["+factoryClassName+"].");
499 loggerFactory = (LoggerFactory)
500 OptionConverter.instantiateByClassName(factoryClassName,
501 LoggerFactory.class,
502 loggerFactory);
503 PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
504 }
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530 void configureRootCategory(Properties props, LoggerRepository hierarchy) {
531 String effectiveFrefix = ROOT_LOGGER_PREFIX;
532 String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
533
534 if(value == null) {
535 value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
536 effectiveFrefix = ROOT_CATEGORY_PREFIX;
537 }
538
539 if(value == null)
540 LogLog.debug("Could not find root logger information. Is this OK?");
541 else {
542 Logger root = hierarchy.getRootLogger();
543 synchronized(root) {
544 parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
545 }
546 }
547 }
548
549
550 /***
551 Parse non-root elements, such non-root categories and renderers.
552 */
553 protected
554 void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
555 Enumeration enumeration = props.propertyNames();
556 while(enumeration.hasMoreElements()) {
557 String key = (String) enumeration.nextElement();
558 if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
559 String loggerName = null;
560 if(key.startsWith(CATEGORY_PREFIX)) {
561 loggerName = key.substring(CATEGORY_PREFIX.length());
562 } else if(key.startsWith(LOGGER_PREFIX)) {
563 loggerName = key.substring(LOGGER_PREFIX.length());
564 }
565 String value = OptionConverter.findAndSubst(key, props);
566 Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
567 synchronized(logger) {
568 parseCategory(props, logger, key, loggerName, value);
569 parseAdditivityForLogger(props, logger, loggerName);
570 }
571 } else if(key.startsWith(RENDERER_PREFIX)) {
572 String renderedClass = key.substring(RENDERER_PREFIX.length());
573 String renderingClass = OptionConverter.findAndSubst(key, props);
574 if(hierarchy instanceof RendererSupport) {
575 RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass,
576 renderingClass);
577 }
578 }
579 }
580 }
581
582 /***
583 Parse the additivity option for a non-root category.
584 */
585 void parseAdditivityForLogger(Properties props, Logger cat,
586 String loggerName) {
587 String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName,
588 props);
589 LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]");
590
591 if((value != null) && (!value.equals(""))) {
592 boolean additivity = OptionConverter.toBoolean(value, true);
593 LogLog.debug("Setting additivity for \""+loggerName+"\" to "+
594 additivity);
595 cat.setAdditivity(additivity);
596 }
597 }
598
599 /***
600 This method must work for the root category as well.
601 */
602 void parseCategory(Properties props, Logger logger, String optionKey,
603 String loggerName, String value) {
604
605 LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
606
607 StringTokenizer st = new StringTokenizer(value, ",");
608
609
610
611
612 if(!(value.startsWith(",") || value.equals(""))) {
613
614
615 if(!st.hasMoreTokens())
616 return;
617
618 String levelStr = st.nextToken();
619 LogLog.debug("Level token is [" + levelStr + "].");
620
621
622
623
624 if(INHERITED.equalsIgnoreCase(levelStr) ||
625 NULL.equalsIgnoreCase(levelStr)) {
626 if(loggerName.equals(INTERNAL_ROOT_NAME)) {
627 LogLog.warn("The root logger cannot be set to null.");
628 } else {
629 logger.setLevel(null);
630 }
631 } else {
632 logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
633 }
634 LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
635 }
636
637
638 logger.removeAllAppenders();
639
640 Appender appender;
641 String appenderName;
642 while(st.hasMoreTokens()) {
643 appenderName = st.nextToken().trim();
644 if(appenderName == null || appenderName.equals(","))
645 continue;
646 LogLog.debug("Parsing appender named \"" + appenderName +"\".");
647 appender = parseAppender(props, appenderName);
648 if(appender != null) {
649 logger.addAppender(appender);
650 }
651 }
652 }
653
654 Appender parseAppender(Properties props, String appenderName) {
655 Appender appender = registryGet(appenderName);
656 if((appender != null)) {
657 LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
658 return appender;
659 }
660
661 String prefix = APPENDER_PREFIX + appenderName;
662 String layoutPrefix = prefix + ".layout";
663
664 appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
665 org.apache.log4j.Appender.class,
666 null);
667 if(appender == null) {
668 LogLog.error(
669 "Could not instantiate appender named \"" + appenderName+"\".");
670 return null;
671 }
672 appender.setName(appenderName);
673
674 if(appender instanceof OptionHandler) {
675 if(appender.requiresLayout()) {
676 Layout layout = (Layout) OptionConverter.instantiateByKey(props,
677 layoutPrefix,
678 Layout.class,
679 null);
680 if(layout != null) {
681 appender.setLayout(layout);
682 LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
683
684 PropertySetter.setProperties(layout, props, layoutPrefix + ".");
685 LogLog.debug("End of parsing for \"" + appenderName +"\".");
686 }
687 }
688
689 PropertySetter.setProperties(appender, props, prefix + ".");
690 LogLog.debug("Parsed \"" + appenderName +"\" options.");
691 }
692 registryPut(appender);
693 return appender;
694 }
695
696
697 void registryPut(Appender appender) {
698 registry.put(appender.getName(), appender);
699 }
700
701 Appender registryGet(String name) {
702 return (Appender) registry.get(name);
703 }
704 }
705
706 class PropertyWatchdog extends FileWatchdog {
707
708 PropertyWatchdog(String filename) {
709 super(filename);
710 }
711
712 /***
713 Call {@link PropertyConfigurator#configure(String)} with the
714 <code>filename</code> to reconfigure log4j. */
715 public
716 void doOnChange() {
717 new PropertyConfigurator().doConfigure(filename,
718 LogManager.getLoggerRepository());
719 }
720 }