001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 019// Contibutors: "Luke Blanshard" <Luke@quiq.com> 020// "Mark DONSZELMANN" <Mark.Donszelmann@cern.ch> 021// Anders Kristensen <akristensen@dynamicsoft.com> 022 023package org.apache.log4j; 024 025import java.io.FileInputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.InterruptedIOException; 029import java.net.URLConnection; 030import java.util.Enumeration; 031import java.util.Hashtable; 032import java.util.Properties; 033import java.util.StringTokenizer; 034import java.util.Vector; 035import java.util.Iterator; 036import java.util.Map; 037 038import org.apache.log4j.config.PropertySetter; 039import org.apache.log4j.helpers.FileWatchdog; 040import org.apache.log4j.helpers.LogLog; 041import org.apache.log4j.helpers.OptionConverter; 042import org.apache.log4j.or.RendererMap; 043import org.apache.log4j.spi.Configurator; 044import org.apache.log4j.spi.Filter; 045import org.apache.log4j.spi.LoggerFactory; 046import org.apache.log4j.spi.LoggerRepository; 047import org.apache.log4j.spi.OptionHandler; 048import org.apache.log4j.spi.RendererSupport; 049import org.apache.log4j.spi.ThrowableRenderer; 050import org.apache.log4j.spi.ThrowableRendererSupport; 051import org.apache.log4j.spi.ErrorHandler; 052 053/** 054 Allows the configuration of log4j from an external file. See 055 <b>{@link #doConfigure(String, LoggerRepository)}</b> for the 056 expected format. 057 058 <p>It is sometimes useful to see how log4j is reading configuration 059 files. You can enable log4j internal logging by defining the 060 <b>log4j.debug</b> variable. 061 062 <P>As of log4j version 0.8.5, at class initialization time class, 063 the file <b>log4j.properties</b> will be searched from the search 064 path used to load classes. If the file can be found, then it will 065 be fed to the {@link PropertyConfigurator#configure(java.net.URL)} 066 method. 067 068 <p>The <code>PropertyConfigurator</code> does not handle the 069 advanced configuration features supported by the {@link 070 org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as 071 support custom {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers}, 072 nested appenders such as the {@link org.apache.log4j.AsyncAppender 073 AsyncAppender}, etc. 074 075 <p>All option <em>values</em> admit variable substitution. The 076 syntax of variable substitution is similar to that of Unix 077 shells. The string between an opening <b>"${"</b> and 078 closing <b>"}"</b> is interpreted as a key. The value of 079 the substituted variable can be defined as a system property or in 080 the configuration file itself. The value of the key is first 081 searched in the system properties, and if not found there, it is 082 then searched in the configuration file being parsed. The 083 corresponding value replaces the ${variableName} sequence. For 084 example, if <code>java.home</code> system property is set to 085 <code>/home/xyz</code>, then every occurrence of the sequence 086 <code>${java.home}</code> will be interpreted as 087 <code>/home/xyz</code>. 088 089 090 @author Ceki Gülcü 091 @author Anders Kristensen 092 @since 0.8.1 */ 093public class PropertyConfigurator implements Configurator { 094 095 /** 096 Used internally to keep track of configured appenders. 097 */ 098 protected Hashtable registry = new Hashtable(11); 099 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*/ 412public 413static 414void configure(java.net.URL configURL) { 415 new PropertyConfigurator().doConfigure(configURL, 416 LogManager.getLoggerRepository()); 417} 418 419/** 420Reads configuration options from an InputStream. 421 422@since 1.2.17 423*/ 424public 425static 426void configure(InputStream inputStream) { 427new PropertyConfigurator().doConfigure(inputStream, 428 LogManager.getLoggerRepository()); 429} 430 431 432 /** 433 Read configuration options from <code>properties</code>. 434 435 See {@link #doConfigure(String, LoggerRepository)} for the expected format. 436 */ 437 static 438 public 439 void configure(Properties properties) { 440 new PropertyConfigurator().doConfigure(properties, 441 LogManager.getLoggerRepository()); 442 } 443 444 /** 445 Like {@link #configureAndWatch(String, long)} except that the 446 default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is 447 used. 448 449 @param configFilename A file in key=value format. 450 451 */ 452 static 453 public 454 void configureAndWatch(String configFilename) { 455 configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY); 456 } 457 458 459 /** 460 Read the configuration file <code>configFilename</code> if it 461 exists. Moreover, a thread will be created that will periodically 462 check if <code>configFilename</code> has been created or 463 modified. The period is determined by the <code>delay</code> 464 argument. If a change or file creation is detected, then 465 <code>configFilename</code> is read to configure log4j. 466 467 @param configFilename A file in key=value format. 468 @param delay The delay in milliseconds to wait between each check. 469 */ 470 static 471 public 472 void configureAndWatch(String configFilename, long delay) { 473 PropertyWatchdog pdog = new PropertyWatchdog(configFilename); 474 pdog.setDelay(delay); 475 pdog.start(); 476 } 477 478 479 /** 480 Read configuration options from <code>properties</code>. 481 482 See {@link #doConfigure(String, LoggerRepository)} for the expected format. 483 */ 484 public 485 void doConfigure(Properties properties, LoggerRepository hierarchy) { 486 repository = hierarchy; 487 String value = properties.getProperty(LogLog.DEBUG_KEY); 488 if(value == null) { 489 value = properties.getProperty("log4j.configDebug"); 490 if(value != null) 491 LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); 492 } 493 494 if(value != null) { 495 LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); 496 } 497 498 // 499 // if log4j.reset=true then 500 // reset hierarchy 501 String reset = properties.getProperty(RESET_KEY); 502 if (reset != null && OptionConverter.toBoolean(reset, false)) { 503 hierarchy.resetConfiguration(); 504 } 505 506 String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, 507 properties); 508 if(thresholdStr != null) { 509 hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, 510 (Level) Level.ALL)); 511 LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"]."); 512 } 513 514 configureRootCategory(properties, hierarchy); 515 configureLoggerFactory(properties); 516 parseCatsAndRenderers(properties, hierarchy); 517 518 LogLog.debug("Finished configuring."); 519 // We don't want to hold references to appenders preventing their 520 // garbage collection. 521 registry.clear(); 522 } 523 524 /** 525 * Read configuration options from url <code>configURL</code>. 526 * 527 * @since 1.2.17 528 */ 529 public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) { 530 Properties props = new Properties(); 531 try { 532 props.load(inputStream); 533 } catch (IOException e) { 534 if (e instanceof InterruptedIOException) { 535 Thread.currentThread().interrupt(); 536 } 537 LogLog.error("Could not read configuration file from InputStream [" + inputStream 538 + "].", e); 539 LogLog.error("Ignoring configuration InputStream [" + inputStream +"]."); 540 return; 541 } 542 this.doConfigure(props, hierarchy); 543 } 544 545 /** 546 Read configuration options from url <code>configURL</code>. 547 */ 548 public 549 void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) { 550 Properties props = new Properties(); 551 LogLog.debug("Reading configuration from URL " + configURL); 552 InputStream istream = null; 553 URLConnection uConn = null; 554 try { 555 uConn = configURL.openConnection(); 556 uConn.setUseCaches(false); 557 istream = uConn.getInputStream(); 558 props.load(istream); 559 } 560 catch (Exception e) { 561 if (e instanceof InterruptedIOException || e instanceof InterruptedException) { 562 Thread.currentThread().interrupt(); 563 } 564 LogLog.error("Could not read configuration file from URL [" + configURL 565 + "].", e); 566 LogLog.error("Ignoring configuration file [" + configURL +"]."); 567 return; 568 } 569 finally { 570 if (istream != null) { 571 try { 572 istream.close(); 573 } catch(InterruptedIOException ignore) { 574 Thread.currentThread().interrupt(); 575 } catch(IOException ignore) { 576 } catch(RuntimeException ignore) { 577 } 578 } 579 } 580 doConfigure(props, hierarchy); 581 } 582 583 584 // -------------------------------------------------------------------------- 585 // Internal stuff 586 // -------------------------------------------------------------------------- 587 588 /** 589 Check the provided <code>Properties</code> object for a 590 {@link org.apache.log4j.spi.LoggerFactory LoggerFactory} 591 entry specified by {@link #LOGGER_FACTORY_KEY}. If such an entry 592 exists, an attempt is made to create an instance using the default 593 constructor. This instance is used for subsequent Category creations 594 within this configurator. 595 596 @see #parseCatsAndRenderers 597 */ 598 protected void configureLoggerFactory(Properties props) { 599 String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, 600 props); 601 if(factoryClassName != null) { 602 LogLog.debug("Setting category factory to ["+factoryClassName+"]."); 603 loggerFactory = (LoggerFactory) 604 OptionConverter.instantiateByClassName(factoryClassName, 605 LoggerFactory.class, 606 loggerFactory); 607 PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + "."); 608 } 609 } 610 611 /* 612 void configureOptionHandler(OptionHandler oh, String prefix, 613 Properties props) { 614 String[] options = oh.getOptionStrings(); 615 if(options == null) 616 return; 617 618 String value; 619 for(int i = 0; i < options.length; i++) { 620 value = OptionConverter.findAndSubst(prefix + options[i], props); 621 LogLog.debug( 622 "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"]."); 623 // Some option handlers assume that null value are not passed to them. 624 // So don't remove this check 625 if(value != null) { 626 oh.setOption(options[i], value); 627 } 628 } 629 oh.activateOptions(); 630 } 631 */ 632 633 634 void configureRootCategory(Properties props, LoggerRepository hierarchy) { 635 String effectiveFrefix = ROOT_LOGGER_PREFIX; 636 String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props); 637 638 if(value == null) { 639 value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props); 640 effectiveFrefix = ROOT_CATEGORY_PREFIX; 641 } 642 643 if(value == null) 644 LogLog.debug("Could not find root logger information. Is this OK?"); 645 else { 646 Logger root = hierarchy.getRootLogger(); 647 synchronized(root) { 648 parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value); 649 } 650 } 651 } 652 653 654 /** 655 Parse non-root elements, such non-root categories and renderers. 656 */ 657 protected 658 void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) { 659 Enumeration enumeration = props.propertyNames(); 660 while(enumeration.hasMoreElements()) { 661 String key = (String) enumeration.nextElement(); 662 if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) { 663 String loggerName = null; 664 if(key.startsWith(CATEGORY_PREFIX)) { 665 loggerName = key.substring(CATEGORY_PREFIX.length()); 666 } else if(key.startsWith(LOGGER_PREFIX)) { 667 loggerName = key.substring(LOGGER_PREFIX.length()); 668 } 669 String value = OptionConverter.findAndSubst(key, props); 670 Logger logger = hierarchy.getLogger(loggerName, loggerFactory); 671 synchronized(logger) { 672 parseCategory(props, logger, key, loggerName, value); 673 parseAdditivityForLogger(props, logger, loggerName); 674 } 675 } else if(key.startsWith(RENDERER_PREFIX)) { 676 String renderedClass = key.substring(RENDERER_PREFIX.length()); 677 String renderingClass = OptionConverter.findAndSubst(key, props); 678 if(hierarchy instanceof RendererSupport) { 679 RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass, 680 renderingClass); 681 } 682 } else if (key.equals(THROWABLE_RENDERER_PREFIX)) { 683 if (hierarchy instanceof ThrowableRendererSupport) { 684 ThrowableRenderer tr = (ThrowableRenderer) 685 OptionConverter.instantiateByKey(props, 686 THROWABLE_RENDERER_PREFIX, 687 org.apache.log4j.spi.ThrowableRenderer.class, 688 null); 689 if(tr == null) { 690 LogLog.error( 691 "Could not instantiate throwableRenderer."); 692 } else { 693 PropertySetter setter = new PropertySetter(tr); 694 setter.setProperties(props, THROWABLE_RENDERER_PREFIX + "."); 695 ((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr); 696 697 } 698 } 699 } 700 } 701 } 702 703 /** 704 Parse the additivity option for a non-root category. 705 */ 706 void parseAdditivityForLogger(Properties props, Logger cat, 707 String loggerName) { 708 String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, 709 props); 710 LogLog.debug("Handling "+ADDITIVITY_PREFIX + loggerName+"=["+value+"]"); 711 // touch additivity only if necessary 712 if((value != null) && (!value.equals(""))) { 713 boolean additivity = OptionConverter.toBoolean(value, true); 714 LogLog.debug("Setting additivity for \""+loggerName+"\" to "+ 715 additivity); 716 cat.setAdditivity(additivity); 717 } 718 } 719 720 /** 721 This method must work for the root category as well. 722 */ 723 void parseCategory(Properties props, Logger logger, String optionKey, 724 String loggerName, String value) { 725 726 LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"]."); 727 // We must skip over ',' but not white space 728 StringTokenizer st = new StringTokenizer(value, ","); 729 730 // If value is not in the form ", appender.." or "", then we should set 731 // the level of the loggeregory. 732 733 if(!(value.startsWith(",") || value.equals(""))) { 734 735 // just to be on the safe side... 736 if(!st.hasMoreTokens()) 737 return; 738 739 String levelStr = st.nextToken(); 740 LogLog.debug("Level token is [" + levelStr + "]."); 741 742 // If the level value is inherited, set category level value to 743 // null. We also check that the user has not specified inherited for the 744 // root category. 745 if(INHERITED.equalsIgnoreCase(levelStr) || 746 NULL.equalsIgnoreCase(levelStr)) { 747 if(loggerName.equals(INTERNAL_ROOT_NAME)) { 748 LogLog.warn("The root logger cannot be set to null."); 749 } else { 750 logger.setLevel(null); 751 } 752 } else { 753 logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG)); 754 } 755 LogLog.debug("Category " + loggerName + " set to " + logger.getLevel()); 756 } 757 758 // Begin by removing all existing appenders. 759 logger.removeAllAppenders(); 760 761 Appender appender; 762 String appenderName; 763 while(st.hasMoreTokens()) { 764 appenderName = st.nextToken().trim(); 765 if(appenderName == null || appenderName.equals(",")) 766 continue; 767 LogLog.debug("Parsing appender named \"" + appenderName +"\"."); 768 appender = parseAppender(props, appenderName); 769 if(appender != null) { 770 logger.addAppender(appender); 771 } 772 } 773 } 774 775 Appender parseAppender(Properties props, String appenderName) { 776 Appender appender = registryGet(appenderName); 777 if((appender != null)) { 778 LogLog.debug("Appender \"" + appenderName + "\" was already parsed."); 779 return appender; 780 } 781 // Appender was not previously initialized. 782 String prefix = APPENDER_PREFIX + appenderName; 783 String layoutPrefix = prefix + ".layout"; 784 785 appender = (Appender) OptionConverter.instantiateByKey(props, prefix, 786 org.apache.log4j.Appender.class, 787 null); 788 if(appender == null) { 789 LogLog.error( 790 "Could not instantiate appender named \"" + appenderName+"\"."); 791 return null; 792 } 793 appender.setName(appenderName); 794 795 if(appender instanceof OptionHandler) { 796 if(appender.requiresLayout()) { 797 Layout layout = (Layout) OptionConverter.instantiateByKey(props, 798 layoutPrefix, 799 Layout.class, 800 null); 801 if(layout != null) { 802 appender.setLayout(layout); 803 LogLog.debug("Parsing layout options for \"" + appenderName +"\"."); 804 //configureOptionHandler(layout, layoutPrefix + ".", props); 805 PropertySetter.setProperties(layout, props, layoutPrefix + "."); 806 LogLog.debug("End of parsing for \"" + appenderName +"\"."); 807 } 808 } 809 final String errorHandlerPrefix = prefix + ".errorhandler"; 810 String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props); 811 if (errorHandlerClass != null) { 812 ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props, 813 errorHandlerPrefix, 814 ErrorHandler.class, 815 null); 816 if (eh != null) { 817 appender.setErrorHandler(eh); 818 LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\"."); 819 parseErrorHandler(eh, errorHandlerPrefix, props, repository); 820 final Properties edited = new Properties(); 821 final String[] keys = new String[] { 822 errorHandlerPrefix + "." + ROOT_REF, 823 errorHandlerPrefix + "." + LOGGER_REF, 824 errorHandlerPrefix + "." + APPENDER_REF_TAG 825 }; 826 for(Iterator iter = props.entrySet().iterator();iter.hasNext();) { 827 Map.Entry entry = (Map.Entry) iter.next(); 828 int i = 0; 829 for(; i < keys.length; i++) { 830 if(keys[i].equals(entry.getKey())) break; 831 } 832 if (i == keys.length) { 833 edited.put(entry.getKey(), entry.getValue()); 834 } 835 } 836 PropertySetter.setProperties(eh, edited, errorHandlerPrefix + "."); 837 LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\"."); 838 } 839 840 } 841 //configureOptionHandler((OptionHandler) appender, prefix + ".", props); 842 PropertySetter.setProperties(appender, props, prefix + "."); 843 LogLog.debug("Parsed \"" + appenderName +"\" options."); 844 } 845 parseAppenderFilters(props, appenderName, appender); 846 registryPut(appender); 847 return appender; 848 } 849 850 private void parseErrorHandler( 851 final ErrorHandler eh, 852 final String errorHandlerPrefix, 853 final Properties props, 854 final LoggerRepository hierarchy) { 855 boolean rootRef = OptionConverter.toBoolean( 856 OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false); 857 if (rootRef) { 858 eh.setLogger(hierarchy.getRootLogger()); 859 } 860 String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF , props); 861 if (loggerName != null) { 862 Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName) 863 : hierarchy.getLogger(loggerName, loggerFactory); 864 eh.setLogger(logger); 865 } 866 String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props); 867 if (appenderName != null) { 868 Appender backup = parseAppender(props, appenderName); 869 if (backup != null) { 870 eh.setBackupAppender(backup); 871 } 872 } 873 } 874 875 876 void parseAppenderFilters(Properties props, String appenderName, Appender appender) { 877 // extract filters and filter options from props into a hashtable mapping 878 // the property name defining the filter class to a list of pre-parsed 879 // name-value pairs associated to that filter 880 final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter."; 881 int fIdx = filterPrefix.length(); 882 Hashtable filters = new Hashtable(); 883 Enumeration e = props.keys(); 884 String name = ""; 885 while (e.hasMoreElements()) { 886 String key = (String) e.nextElement(); 887 if (key.startsWith(filterPrefix)) { 888 int dotIdx = key.indexOf('.', fIdx); 889 String filterKey = key; 890 if (dotIdx != -1) { 891 filterKey = key.substring(0, dotIdx); 892 name = key.substring(dotIdx+1); 893 } 894 Vector filterOpts = (Vector) filters.get(filterKey); 895 if (filterOpts == null) { 896 filterOpts = new Vector(); 897 filters.put(filterKey, filterOpts); 898 } 899 if (dotIdx != -1) { 900 String value = OptionConverter.findAndSubst(key, props); 901 filterOpts.add(new NameValue(name, value)); 902 } 903 } 904 } 905 906 // sort filters by IDs, insantiate filters, set filter options, 907 // add filters to the appender 908 Enumeration g = new SortedKeyEnumeration(filters); 909 while (g.hasMoreElements()) { 910 String key = (String) g.nextElement(); 911 String clazz = props.getProperty(key); 912 if (clazz != null) { 913 LogLog.debug("Filter key: ["+key+"] class: ["+props.getProperty(key) +"] props: "+filters.get(key)); 914 Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null); 915 if (filter != null) { 916 PropertySetter propSetter = new PropertySetter(filter); 917 Vector v = (Vector)filters.get(key); 918 Enumeration filterProps = v.elements(); 919 while (filterProps.hasMoreElements()) { 920 NameValue kv = (NameValue)filterProps.nextElement(); 921 propSetter.setProperty(kv.key, kv.value); 922 } 923 propSetter.activate(); 924 LogLog.debug("Adding filter of type ["+filter.getClass() 925 +"] to appender named ["+appender.getName()+"]."); 926 appender.addFilter(filter); 927 } 928 } else { 929 LogLog.warn("Missing class definition for filter: ["+key+"]"); 930 } 931 } 932 } 933 934 935 void registryPut(Appender appender) { 936 registry.put(appender.getName(), appender); 937 } 938 939 Appender registryGet(String name) { 940 return (Appender) registry.get(name); 941 } 942} 943 944class PropertyWatchdog extends FileWatchdog { 945 946 PropertyWatchdog(String filename) { 947 super(filename); 948 } 949 950 /** 951 Call {@link PropertyConfigurator#configure(String)} with the 952 <code>filename</code> to reconfigure log4j. */ 953 public 954 void doOnChange() { 955 new PropertyConfigurator().doConfigure(filename, 956 LogManager.getLoggerRepository()); 957 } 958} 959 960class NameValue { 961 String key, value; 962 public NameValue(String key, String value) { 963 this.key = key; 964 this.value = value; 965 } 966 public String toString() { 967 return key + "=" + value; 968 } 969} 970 971class SortedKeyEnumeration implements Enumeration { 972 973 private Enumeration e; 974 975 public SortedKeyEnumeration(Hashtable ht) { 976 Enumeration f = ht.keys(); 977 Vector keys = new Vector(ht.size()); 978 for (int i, last = 0; f.hasMoreElements(); ++last) { 979 String key = (String) f.nextElement(); 980 for (i = 0; i < last; ++i) { 981 String s = (String) keys.get(i); 982 if (key.compareTo(s) <= 0) break; 983 } 984 keys.add(i, key); 985 } 986 e = keys.elements(); 987 } 988 989 public boolean hasMoreElements() { 990 return e.hasMoreElements(); 991 } 992 993 public Object nextElement() { 994 return e.nextElement(); 995 } 996}