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