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 018package org.apache.log4j.xml; 019 020import org.apache.log4j.Appender; 021import org.apache.log4j.Layout; 022import org.apache.log4j.Level; 023import org.apache.log4j.LogManager; 024import org.apache.log4j.Logger; 025import org.apache.log4j.config.PropertySetter; 026import org.apache.log4j.helpers.FileWatchdog; 027import org.apache.log4j.helpers.Loader; 028import org.apache.log4j.helpers.LogLog; 029import org.apache.log4j.helpers.OptionConverter; 030import org.apache.log4j.or.RendererMap; 031import org.apache.log4j.spi.AppenderAttachable; 032import org.apache.log4j.spi.Configurator; 033import org.apache.log4j.spi.ErrorHandler; 034import org.apache.log4j.spi.Filter; 035import org.apache.log4j.spi.LoggerFactory; 036import org.apache.log4j.spi.LoggerRepository; 037import org.apache.log4j.spi.RendererSupport; 038import org.apache.log4j.spi.ThrowableRenderer; 039import org.apache.log4j.spi.ThrowableRendererSupport; 040import org.w3c.dom.Document; 041import org.w3c.dom.Element; 042import org.w3c.dom.NamedNodeMap; 043import org.w3c.dom.Node; 044import org.w3c.dom.NodeList; 045import org.xml.sax.InputSource; 046import org.xml.sax.SAXException; 047 048import javax.xml.parsers.DocumentBuilder; 049import javax.xml.parsers.DocumentBuilderFactory; 050import javax.xml.parsers.FactoryConfigurationError; 051import java.io.File; 052import java.io.IOException; 053import java.io.InputStream; 054import java.io.InterruptedIOException; 055import java.io.Reader; 056import java.lang.reflect.Method; 057import java.lang.reflect.InvocationTargetException; 058import java.net.URL; 059import java.net.URLConnection; 060import java.util.Hashtable; 061import java.util.Properties; 062 063// Contributors: Mark Womack 064// Arun Katkere 065 066/** 067 Use this class to initialize the log4j environment using a DOM tree. 068 069 <p>The DTD is specified in <a 070 href="doc-files/log4j.dtd"><b>log4j.dtd</b></a>. 071 072 <p>Sometimes it is useful to see how log4j is reading configuration 073 files. You can enable log4j internal logging by defining the 074 <b>log4j.debug</b> variable on the java command 075 line. Alternatively, set the <code>debug</code> attribute in the 076 <code>log4j:configuration</code> element. As in 077<pre> 078 <log4j:configuration <b>debug="true"</b> xmlns:log4j="http://jakarta.apache.org/log4j/"> 079 ... 080 </log4j:configuration> 081</pre> 082 083 <p>There are sample XML files included in the package. 084 085 @author Christopher Taylor 086 @author Ceki Gülcü 087 @author Anders Kristensen 088 089 @since 0.8.3 */ 090public class DOMConfigurator implements Configurator { 091 092 static final String CONFIGURATION_TAG = "log4j:configuration"; 093 static final String OLD_CONFIGURATION_TAG = "configuration"; 094 static final String RENDERER_TAG = "renderer"; 095 private static final String THROWABLE_RENDERER_TAG = "throwableRenderer"; 096 static final String APPENDER_TAG = "appender"; 097 static final String APPENDER_REF_TAG = "appender-ref"; 098 static final String PARAM_TAG = "param"; 099 static final String LAYOUT_TAG = "layout"; 100 static final String CATEGORY = "category"; 101 static final String LOGGER = "logger"; 102 static final String LOGGER_REF = "logger-ref"; 103 static final String CATEGORY_FACTORY_TAG = "categoryFactory"; 104 static final String LOGGER_FACTORY_TAG = "loggerFactory"; 105 static final String NAME_ATTR = "name"; 106 static final String CLASS_ATTR = "class"; 107 static final String VALUE_ATTR = "value"; 108 static final String ROOT_TAG = "root"; 109 static final String ROOT_REF = "root-ref"; 110 static final String LEVEL_TAG = "level"; 111 static final String PRIORITY_TAG = "priority"; 112 static final String FILTER_TAG = "filter"; 113 static final String ERROR_HANDLER_TAG = "errorHandler"; 114 static final String REF_ATTR = "ref"; 115 static final String ADDITIVITY_ATTR = "additivity"; 116 static final String THRESHOLD_ATTR = "threshold"; 117 static final String CONFIG_DEBUG_ATTR = "configDebug"; 118 static final String INTERNAL_DEBUG_ATTR = "debug"; 119 private static final String RESET_ATTR = "reset"; 120 static final String RENDERING_CLASS_ATTR = "renderingClass"; 121 static final String RENDERED_CLASS_ATTR = "renderedClass"; 122 123 static final String EMPTY_STR = ""; 124 static final Class[] ONE_STRING_PARAM = new Class[] {String.class}; 125 126 final static String dbfKey = "javax.xml.parsers.DocumentBuilderFactory"; 127 128 129 // key: appenderName, value: appender 130 Hashtable appenderBag; 131 132 Properties props; 133 LoggerRepository repository; 134 135 protected LoggerFactory catFactory = null; 136 137 /** 138 No argument constructor. 139 */ 140 public 141 DOMConfigurator () { 142 appenderBag = new Hashtable(); 143 } 144 145 /** 146 Used internally to parse appenders by IDREF name. 147 */ 148 protected 149 Appender findAppenderByName(Document doc, String appenderName) { 150 Appender appender = (Appender) appenderBag.get(appenderName); 151 152 if(appender != null) { 153 return appender; 154 } else { 155 // Doesn't work on DOM Level 1 : 156 // Element element = doc.getElementById(appenderName); 157 158 // Endre's hack: 159 Element element = null; 160 NodeList list = doc.getElementsByTagName("appender"); 161 for (int t=0; t < list.getLength(); t++) { 162 Node node = list.item(t); 163 NamedNodeMap map= node.getAttributes(); 164 Node attrNode = map.getNamedItem("name"); 165 if (appenderName.equals(attrNode.getNodeValue())) { 166 element = (Element) node; 167 break; 168 } 169 } 170 // Hack finished. 171 172 if(element == null) { 173 LogLog.error("No appender named ["+appenderName+"] could be found."); 174 return null; 175 } else { 176 appender = parseAppender(element); 177 if (appender != null) { 178 appenderBag.put(appenderName, appender); 179 } 180 return appender; 181 } 182 } 183 } 184 /** 185 Used internally to parse appenders by IDREF element. 186 */ 187 protected 188 Appender findAppenderByReference(Element appenderRef) { 189 String appenderName = subst(appenderRef.getAttribute(REF_ATTR)); 190 Document doc = appenderRef.getOwnerDocument(); 191 return findAppenderByName(doc, appenderName); 192 } 193 194 /** 195 * Delegates unrecognized content to created instance if 196 * it supports UnrecognizedElementParser. 197 * @since 1.2.15 198 * @param instance instance, may be null. 199 * @param element element, may not be null. 200 * @param props properties 201 * @throws IOException thrown if configuration of owner object 202 * should be abandoned. 203 */ 204 private static void parseUnrecognizedElement(final Object instance, 205 final Element element, 206 final Properties props) throws Exception { 207 boolean recognized = false; 208 if (instance instanceof UnrecognizedElementHandler) { 209 recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement( 210 element, props); 211 } 212 if (!recognized) { 213 LogLog.warn("Unrecognized element " + element.getNodeName()); 214 } 215 } 216 217 /** 218 * Delegates unrecognized content to created instance if 219 * it supports UnrecognizedElementParser and catches and 220 * logs any exception. 221 * @since 1.2.15 222 * @param instance instance, may be null. 223 * @param element element, may not be null. 224 * @param props properties 225 */ 226 private static void quietParseUnrecognizedElement(final Object instance, 227 final Element element, 228 final Properties props) { 229 try { 230 parseUnrecognizedElement(instance, element, props); 231 } catch (Exception ex) { 232 if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) { 233 Thread.currentThread().interrupt(); 234 } 235 LogLog.error("Error in extension content: ", ex); 236 } 237 } 238 239 /** 240 Used internally to parse an appender element. 241 */ 242 protected 243 Appender parseAppender (Element appenderElement) { 244 String className = subst(appenderElement.getAttribute(CLASS_ATTR)); 245 LogLog.debug("Class name: [" + className+']'); 246 try { 247 Object instance = Loader.loadClass(className).newInstance(); 248 Appender appender = (Appender)instance; 249 PropertySetter propSetter = new PropertySetter(appender); 250 251 appender.setName(subst(appenderElement.getAttribute(NAME_ATTR))); 252 253 NodeList children = appenderElement.getChildNodes(); 254 final int length = children.getLength(); 255 256 for (int loop = 0; loop < length; loop++) { 257 Node currentNode = children.item(loop); 258 259 /* We're only interested in Elements */ 260 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 261 Element currentElement = (Element)currentNode; 262 263 // Parse appender parameters 264 if (currentElement.getTagName().equals(PARAM_TAG)) { 265 setParameter(currentElement, propSetter); 266 } 267 // Set appender layout 268 else if (currentElement.getTagName().equals(LAYOUT_TAG)) { 269 appender.setLayout(parseLayout(currentElement)); 270 } 271 // Add filters 272 else if (currentElement.getTagName().equals(FILTER_TAG)) { 273 parseFilters(currentElement, appender); 274 } 275 else if (currentElement.getTagName().equals(ERROR_HANDLER_TAG)) { 276 parseErrorHandler(currentElement, appender); 277 } 278 else if (currentElement.getTagName().equals(APPENDER_REF_TAG)) { 279 String refName = subst(currentElement.getAttribute(REF_ATTR)); 280 if(appender instanceof AppenderAttachable) { 281 AppenderAttachable aa = (AppenderAttachable) appender; 282 LogLog.debug("Attaching appender named ["+ refName+ 283 "] to appender named ["+ appender.getName()+"]."); 284 aa.addAppender(findAppenderByReference(currentElement)); 285 } else { 286 LogLog.error("Requesting attachment of appender named ["+ 287 refName+ "] to appender named ["+ appender.getName()+ 288 "] which does not implement org.apache.log4j.spi.AppenderAttachable."); 289 } 290 } else { 291 parseUnrecognizedElement(instance, currentElement, props); 292 } 293 } 294 } 295 propSetter.activate(); 296 return appender; 297 } 298 /* Yes, it's ugly. But all of these exceptions point to the same 299 problem: we can't create an Appender */ 300 catch (Exception oops) { 301 if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) { 302 Thread.currentThread().interrupt(); 303 } 304 LogLog.error("Could not create an Appender. Reported error follows.", 305 oops); 306 return null; 307 } 308 } 309 310 /** 311 Used internally to parse an {@link ErrorHandler} element. 312 */ 313 protected 314 void parseErrorHandler(Element element, Appender appender) { 315 ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName( 316 subst(element.getAttribute(CLASS_ATTR)), 317 org.apache.log4j.spi.ErrorHandler.class, 318 null); 319 320 if(eh != null) { 321 eh.setAppender(appender); 322 323 PropertySetter propSetter = new PropertySetter(eh); 324 NodeList children = element.getChildNodes(); 325 final int length = children.getLength(); 326 327 for (int loop = 0; loop < length; loop++) { 328 Node currentNode = children.item(loop); 329 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 330 Element currentElement = (Element) currentNode; 331 String tagName = currentElement.getTagName(); 332 if(tagName.equals(PARAM_TAG)) { 333 setParameter(currentElement, propSetter); 334 } else if(tagName.equals(APPENDER_REF_TAG)) { 335 eh.setBackupAppender(findAppenderByReference(currentElement)); 336 } else if(tagName.equals(LOGGER_REF)) { 337 String loggerName = currentElement.getAttribute(REF_ATTR); 338 Logger logger = (catFactory == null) ? repository.getLogger(loggerName) 339 : repository.getLogger(loggerName, catFactory); 340 eh.setLogger(logger); 341 } else if(tagName.equals(ROOT_REF)) { 342 Logger root = repository.getRootLogger(); 343 eh.setLogger(root); 344 } else { 345 quietParseUnrecognizedElement(eh, currentElement, props); 346 } 347 } 348 } 349 propSetter.activate(); 350 appender.setErrorHandler(eh); 351 } 352 } 353 354 /** 355 Used internally to parse a filter element. 356 */ 357 protected 358 void parseFilters(Element element, Appender appender) { 359 String clazz = subst(element.getAttribute(CLASS_ATTR)); 360 Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, 361 Filter.class, null); 362 363 if(filter != null) { 364 PropertySetter propSetter = new PropertySetter(filter); 365 NodeList children = element.getChildNodes(); 366 final int length = children.getLength(); 367 368 for (int loop = 0; loop < length; loop++) { 369 Node currentNode = children.item(loop); 370 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 371 Element currentElement = (Element) currentNode; 372 String tagName = currentElement.getTagName(); 373 if(tagName.equals(PARAM_TAG)) { 374 setParameter(currentElement, propSetter); 375 } else { 376 quietParseUnrecognizedElement(filter, currentElement, props); 377 } 378 } 379 } 380 propSetter.activate(); 381 LogLog.debug("Adding filter of type ["+filter.getClass() 382 +"] to appender named ["+appender.getName()+"]."); 383 appender.addFilter(filter); 384 } 385 } 386 387 /** 388 Used internally to parse an category element. 389 */ 390 protected 391 void parseCategory (Element loggerElement) { 392 // Create a new org.apache.log4j.Category object from the <category> element. 393 String catName = subst(loggerElement.getAttribute(NAME_ATTR)); 394 395 Logger cat; 396 397 String className = subst(loggerElement.getAttribute(CLASS_ATTR)); 398 399 400 if(EMPTY_STR.equals(className)) { 401 LogLog.debug("Retreiving an instance of org.apache.log4j.Logger."); 402 cat = (catFactory == null) ? repository.getLogger(catName) : repository.getLogger(catName, catFactory); 403 } 404 else { 405 LogLog.debug("Desired logger sub-class: ["+className+']'); 406 try { 407 Class clazz = Loader.loadClass(className); 408 Method getInstanceMethod = clazz.getMethod("getLogger", 409 ONE_STRING_PARAM); 410 cat = (Logger) getInstanceMethod.invoke(null, new Object[] {catName}); 411 } catch (InvocationTargetException oops) { 412 if (oops.getTargetException() instanceof InterruptedException 413 || oops.getTargetException() instanceof InterruptedIOException) { 414 Thread.currentThread().interrupt(); 415 } 416 LogLog.error("Could not retrieve category ["+catName+ 417 "]. Reported error follows.", oops); 418 return; 419 } catch (Exception oops) { 420 LogLog.error("Could not retrieve category ["+catName+ 421 "]. Reported error follows.", oops); 422 return; 423 } 424 } 425 426 // Setting up a category needs to be an atomic operation, in order 427 // to protect potential log operations while category 428 // configuration is in progress. 429 synchronized(cat) { 430 boolean additivity = OptionConverter.toBoolean( 431 subst(loggerElement.getAttribute(ADDITIVITY_ATTR)), 432 true); 433 434 LogLog.debug("Setting ["+cat.getName()+"] additivity to ["+additivity+"]."); 435 cat.setAdditivity(additivity); 436 parseChildrenOfLoggerElement(loggerElement, cat, false); 437 } 438 } 439 440 441 /** 442 Used internally to parse the category factory element. 443 */ 444 protected 445 void parseCategoryFactory(Element factoryElement) { 446 String className = subst(factoryElement.getAttribute(CLASS_ATTR)); 447 448 if(EMPTY_STR.equals(className)) { 449 LogLog.error("Category Factory tag " + CLASS_ATTR + " attribute not found."); 450 LogLog.debug("No Category Factory configured."); 451 } 452 else { 453 LogLog.debug("Desired category factory: ["+className+']'); 454 Object factory = OptionConverter.instantiateByClassName(className, 455 LoggerFactory.class, 456 null); 457 if (factory instanceof LoggerFactory) { 458 catFactory = (LoggerFactory) factory; 459 } else { 460 LogLog.error("Category Factory class " + className + " does not implement org.apache.log4j.LoggerFactory"); 461 } 462 PropertySetter propSetter = new PropertySetter(factory); 463 464 Element currentElement = null; 465 Node currentNode = null; 466 NodeList children = factoryElement.getChildNodes(); 467 final int length = children.getLength(); 468 469 for (int loop=0; loop < length; loop++) { 470 currentNode = children.item(loop); 471 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 472 currentElement = (Element)currentNode; 473 if (currentElement.getTagName().equals(PARAM_TAG)) { 474 setParameter(currentElement, propSetter); 475 } else { 476 quietParseUnrecognizedElement(factory, currentElement, props); 477 } 478 } 479 } 480 } 481 } 482 483 484 /** 485 Used internally to parse the roor category element. 486 */ 487 protected 488 void parseRoot (Element rootElement) { 489 Logger root = repository.getRootLogger(); 490 // category configuration needs to be atomic 491 synchronized(root) { 492 parseChildrenOfLoggerElement(rootElement, root, true); 493 } 494 } 495 496 497 /** 498 Used internally to parse the children of a category element. 499 */ 500 protected 501 void parseChildrenOfLoggerElement(Element catElement, 502 Logger cat, boolean isRoot) { 503 504 PropertySetter propSetter = new PropertySetter(cat); 505 506 // Remove all existing appenders from cat. They will be 507 // reconstructed if need be. 508 cat.removeAllAppenders(); 509 510 511 NodeList children = catElement.getChildNodes(); 512 final int length = children.getLength(); 513 514 for (int loop = 0; loop < length; loop++) { 515 Node currentNode = children.item(loop); 516 517 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 518 Element currentElement = (Element) currentNode; 519 String tagName = currentElement.getTagName(); 520 521 if (tagName.equals(APPENDER_REF_TAG)) { 522 Element appenderRef = (Element) currentNode; 523 Appender appender = findAppenderByReference(appenderRef); 524 String refName = subst(appenderRef.getAttribute(REF_ATTR)); 525 if(appender != null) 526 LogLog.debug("Adding appender named ["+ refName+ 527 "] to category ["+cat.getName()+"]."); 528 else 529 LogLog.debug("Appender named ["+ refName + "] not found."); 530 531 cat.addAppender(appender); 532 533 } else if(tagName.equals(LEVEL_TAG)) { 534 parseLevel(currentElement, cat, isRoot); 535 } else if(tagName.equals(PRIORITY_TAG)) { 536 parseLevel(currentElement, cat, isRoot); 537 } else if(tagName.equals(PARAM_TAG)) { 538 setParameter(currentElement, propSetter); 539 } else { 540 quietParseUnrecognizedElement(cat, currentElement, props); 541 } 542 } 543 } 544 propSetter.activate(); 545 } 546 547 /** 548 Used internally to parse a layout element. 549 */ 550 protected 551 Layout parseLayout (Element layout_element) { 552 String className = subst(layout_element.getAttribute(CLASS_ATTR)); 553 LogLog.debug("Parsing layout of class: \""+className+"\""); 554 try { 555 Object instance = Loader.loadClass(className).newInstance(); 556 Layout layout = (Layout)instance; 557 PropertySetter propSetter = new PropertySetter(layout); 558 559 NodeList params = layout_element.getChildNodes(); 560 final int length = params.getLength(); 561 562 for (int loop = 0; loop < length; loop++) { 563 Node currentNode = (Node)params.item(loop); 564 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 565 Element currentElement = (Element) currentNode; 566 String tagName = currentElement.getTagName(); 567 if(tagName.equals(PARAM_TAG)) { 568 setParameter(currentElement, propSetter); 569 } else { 570 parseUnrecognizedElement(instance, currentElement, props); 571 } 572 } 573 } 574 575 propSetter.activate(); 576 return layout; 577 } 578 catch (Exception oops) { 579 if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) { 580 Thread.currentThread().interrupt(); 581 } 582 LogLog.error("Could not create the Layout. Reported error follows.", 583 oops); 584 return null; 585 } 586 } 587 588 protected 589 void parseRenderer(Element element) { 590 String renderingClass = subst(element.getAttribute(RENDERING_CLASS_ATTR)); 591 String renderedClass = subst(element.getAttribute(RENDERED_CLASS_ATTR)); 592 if(repository instanceof RendererSupport) { 593 RendererMap.addRenderer((RendererSupport) repository, renderedClass, 594 renderingClass); 595 } 596 } 597 598 /** 599 * Parses throwable renderer. 600 * @param element throwableRenderer element. 601 * @return configured throwable renderer. 602 * @since 1.2.16. 603 */ 604 protected ThrowableRenderer parseThrowableRenderer(final Element element) { 605 String className = subst(element.getAttribute(CLASS_ATTR)); 606 LogLog.debug("Parsing throwableRenderer of class: \""+className+"\""); 607 try { 608 Object instance = Loader.loadClass(className).newInstance(); 609 ThrowableRenderer tr = (ThrowableRenderer)instance; 610 PropertySetter propSetter = new PropertySetter(tr); 611 612 NodeList params = element.getChildNodes(); 613 final int length = params.getLength(); 614 615 for (int loop = 0; loop < length; loop++) { 616 Node currentNode = (Node)params.item(loop); 617 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 618 Element currentElement = (Element) currentNode; 619 String tagName = currentElement.getTagName(); 620 if(tagName.equals(PARAM_TAG)) { 621 setParameter(currentElement, propSetter); 622 } else { 623 parseUnrecognizedElement(instance, currentElement, props); 624 } 625 } 626 } 627 628 propSetter.activate(); 629 return tr; 630 } 631 catch (Exception oops) { 632 if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) { 633 Thread.currentThread().interrupt(); 634 } 635 LogLog.error("Could not create the ThrowableRenderer. Reported error follows.", 636 oops); 637 return null; 638 } 639 } 640 641 /** 642 Used internally to parse a level element. 643 */ 644 protected 645 void parseLevel(Element element, Logger logger, boolean isRoot) { 646 String catName = logger.getName(); 647 if(isRoot) { 648 catName = "root"; 649 } 650 651 String priStr = subst(element.getAttribute(VALUE_ATTR)); 652 LogLog.debug("Level value for "+catName+" is ["+priStr+"]."); 653 654 if(INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) { 655 if(isRoot) { 656 LogLog.error("Root level cannot be inherited. Ignoring directive."); 657 } else { 658 logger.setLevel(null); 659 } 660 } else { 661 String className = subst(element.getAttribute(CLASS_ATTR)); 662 if(EMPTY_STR.equals(className)) { 663 logger.setLevel(OptionConverter.toLevel(priStr, Level.DEBUG)); 664 } else { 665 LogLog.debug("Desired Level sub-class: ["+className+']'); 666 try { 667 Class clazz = Loader.loadClass(className); 668 Method toLevelMethod = clazz.getMethod("toLevel", 669 ONE_STRING_PARAM); 670 Level pri = (Level) toLevelMethod.invoke(null, 671 new Object[] {priStr}); 672 logger.setLevel(pri); 673 } catch (Exception oops) { 674 if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) { 675 Thread.currentThread().interrupt(); 676 } 677 LogLog.error("Could not create level ["+priStr+ 678 "]. Reported error follows.", oops); 679 return; 680 } 681 } 682 } 683 LogLog.debug(catName + " level set to " + logger.getLevel()); 684 } 685 686 protected 687 void setParameter(Element elem, PropertySetter propSetter) { 688 String name = subst(elem.getAttribute(NAME_ATTR)); 689 String value = (elem.getAttribute(VALUE_ATTR)); 690 value = subst(OptionConverter.convertSpecialChars(value)); 691 propSetter.setProperty(name, value); 692 } 693 694 695 /** 696 Configure log4j using a <code>configuration</code> element as 697 defined in the log4j.dtd. 698 699 */ 700 static 701 public 702 void configure (Element element) { 703 DOMConfigurator configurator = new DOMConfigurator(); 704 configurator.doConfigure(element, LogManager.getLoggerRepository()); 705 } 706 707 /** 708 Like {@link #configureAndWatch(String, long)} except that the 709 default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is 710 used. 711 712 @param configFilename A log4j configuration file in XML format. 713 714 */ 715 static 716 public 717 void configureAndWatch(String configFilename) { 718 configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY); 719 } 720 721 /** 722 Read the configuration file <code>configFilename</code> if it 723 exists. Moreover, a thread will be created that will periodically 724 check if <code>configFilename</code> has been created or 725 modified. The period is determined by the <code>delay</code> 726 argument. If a change or file creation is detected, then 727 <code>configFilename</code> is read to configure log4j. 728 729 @param configFilename A log4j configuration file in XML format. 730 @param delay The delay in milliseconds to wait between each check. 731 */ 732 static 733 public 734 void configureAndWatch(String configFilename, long delay) { 735 XMLWatchdog xdog = new XMLWatchdog(configFilename); 736 xdog.setDelay(delay); 737 xdog.start(); 738 } 739 740 private interface ParseAction { 741 Document parse(final DocumentBuilder parser) throws SAXException, IOException; 742 } 743 744 745 public 746 void doConfigure(final String filename, LoggerRepository repository) { 747 ParseAction action = new ParseAction() { 748 public Document parse(final DocumentBuilder parser) throws SAXException, IOException { 749 return parser.parse(new File(filename)); 750 } 751 public String toString() { 752 return "file [" + filename + "]"; 753 } 754 }; 755 doConfigure(action, repository); 756 } 757 758 759 public 760 void doConfigure(final URL url, LoggerRepository repository) { 761 ParseAction action = new ParseAction() { 762 public Document parse(final DocumentBuilder parser) throws SAXException, IOException { 763 URLConnection uConn = url.openConnection(); 764 uConn.setUseCaches(false); 765 InputStream stream = uConn.getInputStream(); 766 try { 767 InputSource src = new InputSource(stream); 768 src.setSystemId(url.toString()); 769 return parser.parse(src); 770 } finally { 771 stream.close(); 772 } 773 } 774 public String toString() { 775 return "url [" + url.toString() + "]"; 776 } 777 }; 778 doConfigure(action, repository); 779 } 780 781 /** 782 Configure log4j by reading in a log4j.dtd compliant XML 783 configuration file. 784 785 */ 786 public 787 void doConfigure(final InputStream inputStream, LoggerRepository repository) 788 throws FactoryConfigurationError { 789 ParseAction action = new ParseAction() { 790 public Document parse(final DocumentBuilder parser) throws SAXException, IOException { 791 InputSource inputSource = new InputSource(inputStream); 792 inputSource.setSystemId("dummy://log4j.dtd"); 793 return parser.parse(inputSource); 794 } 795 public String toString() { 796 return "input stream [" + inputStream.toString() + "]"; 797 } 798 }; 799 doConfigure(action, repository); 800 } 801 802 /** 803 Configure log4j by reading in a log4j.dtd compliant XML 804 configuration file. 805 806 */ 807 public 808 void doConfigure(final Reader reader, LoggerRepository repository) 809 throws FactoryConfigurationError { 810 ParseAction action = new ParseAction() { 811 public Document parse(final DocumentBuilder parser) throws SAXException, IOException { 812 InputSource inputSource = new InputSource(reader); 813 inputSource.setSystemId("dummy://log4j.dtd"); 814 return parser.parse(inputSource); 815 } 816 public String toString() { 817 return "reader [" + reader.toString() + "]"; 818 } 819 }; 820 doConfigure(action, repository); 821 } 822 823 /** 824 Configure log4j by reading in a log4j.dtd compliant XML 825 configuration file. 826 827 */ 828 protected 829 void doConfigure(final InputSource inputSource, LoggerRepository repository) 830 throws FactoryConfigurationError { 831 if (inputSource.getSystemId() == null) { 832 inputSource.setSystemId("dummy://log4j.dtd"); 833 } 834 ParseAction action = new ParseAction() { 835 public Document parse(final DocumentBuilder parser) throws SAXException, IOException { 836 return parser.parse(inputSource); 837 } 838 public String toString() { 839 return "input source [" + inputSource.toString() + "]"; 840 } 841 }; 842 doConfigure(action, repository); 843 } 844 845 846 private final void doConfigure(final ParseAction action, final LoggerRepository repository) 847 throws FactoryConfigurationError { 848 DocumentBuilderFactory dbf = null; 849 this.repository = repository; 850 try { 851 LogLog.debug("System property is :"+ 852 OptionConverter.getSystemProperty(dbfKey, 853 null)); 854 dbf = DocumentBuilderFactory.newInstance(); 855 LogLog.debug("Standard DocumentBuilderFactory search succeded."); 856 LogLog.debug("DocumentBuilderFactory is: "+dbf.getClass().getName()); 857 } catch(FactoryConfigurationError fce) { 858 Exception e = fce.getException(); 859 LogLog.debug("Could not instantiate a DocumentBuilderFactory.", e); 860 throw fce; 861 } 862 863 try { 864 dbf.setValidating(true); 865 866 DocumentBuilder docBuilder = dbf.newDocumentBuilder(); 867 868 docBuilder.setErrorHandler(new SAXErrorHandler()); 869 docBuilder.setEntityResolver(new Log4jEntityResolver()); 870 871 Document doc = action.parse(docBuilder); 872 parse(doc.getDocumentElement()); 873 } catch (Exception e) { 874 if (e instanceof InterruptedException || e instanceof InterruptedIOException) { 875 Thread.currentThread().interrupt(); 876 } 877 // I know this is miserable... 878 LogLog.error("Could not parse "+ action.toString() + ".", e); 879 } 880 } 881 882 /** 883 Configure by taking in an DOM element. 884 */ 885 public void doConfigure(Element element, LoggerRepository repository) { 886 this.repository = repository; 887 parse(element); 888 } 889 890 891 /** 892 A static version of {@link #doConfigure(String, LoggerRepository)}. */ 893 static 894 public 895 void configure(String filename) throws FactoryConfigurationError { 896 new DOMConfigurator().doConfigure(filename, 897 LogManager.getLoggerRepository()); 898 } 899 900 /** 901 A static version of {@link #doConfigure(URL, LoggerRepository)}. 902 */ 903 static 904 public 905 void configure(URL url) throws FactoryConfigurationError { 906 new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository()); 907 } 908 909 /** 910 Used internally to configure the log4j framework by parsing a DOM 911 tree of XML elements based on <a 912 href="doc-files/log4j.dtd">log4j.dtd</a>. 913 914 */ 915 protected 916 void parse(Element element) { 917 918 String rootElementName = element.getTagName(); 919 920 if (!rootElementName.equals(CONFIGURATION_TAG)) { 921 if(rootElementName.equals(OLD_CONFIGURATION_TAG)) { 922 LogLog.warn("The <"+OLD_CONFIGURATION_TAG+ 923 "> element has been deprecated."); 924 LogLog.warn("Use the <"+CONFIGURATION_TAG+"> element instead."); 925 } else { 926 LogLog.error("DOM element is - not a <"+CONFIGURATION_TAG+"> element."); 927 return; 928 } 929 } 930 931 932 String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR)); 933 934 LogLog.debug("debug attribute= \"" + debugAttrib +"\"."); 935 // if the log4j.dtd is not specified in the XML file, then the 936 // "debug" attribute is returned as the empty string. 937 if(!debugAttrib.equals("") && !debugAttrib.equals("null")) { 938 LogLog.setInternalDebugging(OptionConverter.toBoolean(debugAttrib, true)); 939 } else { 940 LogLog.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute."); 941 } 942 943 // 944 // reset repository before configuration if reset="true" 945 // on configuration element. 946 // 947 String resetAttrib = subst(element.getAttribute(RESET_ATTR)); 948 LogLog.debug("reset attribute= \"" + resetAttrib +"\"."); 949 if(!("".equals(resetAttrib))) { 950 if (OptionConverter.toBoolean(resetAttrib, false)) { 951 repository.resetConfiguration(); 952 } 953 } 954 955 956 957 String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR)); 958 if(!confDebug.equals("") && !confDebug.equals("null")) { 959 LogLog.warn("The \""+CONFIG_DEBUG_ATTR+"\" attribute is deprecated."); 960 LogLog.warn("Use the \""+INTERNAL_DEBUG_ATTR+"\" attribute instead."); 961 LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true)); 962 } 963 964 String thresholdStr = subst(element.getAttribute(THRESHOLD_ATTR)); 965 LogLog.debug("Threshold =\"" + thresholdStr +"\"."); 966 if(!"".equals(thresholdStr) && !"null".equals(thresholdStr)) { 967 repository.setThreshold(thresholdStr); 968 } 969 970 //Hashtable appenderBag = new Hashtable(11); 971 972 /* Building Appender objects, placing them in a local namespace 973 for future reference */ 974 975 // First configure each category factory under the root element. 976 // Category factories need to be configured before any of 977 // categories they support. 978 // 979 String tagName = null; 980 Element currentElement = null; 981 Node currentNode = null; 982 NodeList children = element.getChildNodes(); 983 final int length = children.getLength(); 984 985 for (int loop = 0; loop < length; loop++) { 986 currentNode = children.item(loop); 987 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 988 currentElement = (Element) currentNode; 989 tagName = currentElement.getTagName(); 990 991 if (tagName.equals(CATEGORY_FACTORY_TAG) || tagName.equals(LOGGER_FACTORY_TAG)) { 992 parseCategoryFactory(currentElement); 993 } 994 } 995 } 996 997 for (int loop = 0; loop < length; loop++) { 998 currentNode = children.item(loop); 999 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 1000 currentElement = (Element) currentNode; 1001 tagName = currentElement.getTagName(); 1002 1003 if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) { 1004 parseCategory(currentElement); 1005 } else if (tagName.equals(ROOT_TAG)) { 1006 parseRoot(currentElement); 1007 } else if(tagName.equals(RENDERER_TAG)) { 1008 parseRenderer(currentElement); 1009 } else if(tagName.equals(THROWABLE_RENDERER_TAG)) { 1010 if (repository instanceof ThrowableRendererSupport) { 1011 ThrowableRenderer tr = parseThrowableRenderer(currentElement); 1012 if (tr != null) { 1013 ((ThrowableRendererSupport) repository).setThrowableRenderer(tr); 1014 } 1015 } 1016 } else if (!(tagName.equals(APPENDER_TAG) 1017 || tagName.equals(CATEGORY_FACTORY_TAG) 1018 || tagName.equals(LOGGER_FACTORY_TAG))) { 1019 quietParseUnrecognizedElement(repository, currentElement, props); 1020 } 1021 } 1022 } 1023 } 1024 1025 1026 protected 1027 String subst(final String value) { 1028 return subst(value, props); 1029 } 1030 1031 /** 1032 * Substitutes property value for any references in expression. 1033 * 1034 * @param value value from configuration file, may contain 1035 * literal text, property references or both 1036 * @param props properties. 1037 * @return evaluated expression, may still contain expressions 1038 * if unable to expand. 1039 * @since 1.2.15 1040 */ 1041 public static String subst(final String value, final Properties props) { 1042 try { 1043 return OptionConverter.substVars(value, props); 1044 } catch (IllegalArgumentException e) { 1045 LogLog.warn("Could not perform variable substitution.", e); 1046 return value; 1047 } 1048 } 1049 1050 1051 /** 1052 * Sets a parameter based from configuration file content. 1053 * 1054 * @param elem param element, may not be null. 1055 * @param propSetter property setter, may not be null. 1056 * @param props properties 1057 * @since 1.2.15 1058 */ 1059 public static void setParameter(final Element elem, 1060 final PropertySetter propSetter, 1061 final Properties props) { 1062 String name = subst(elem.getAttribute("name"), props); 1063 String value = (elem.getAttribute("value")); 1064 value = subst(OptionConverter.convertSpecialChars(value), props); 1065 propSetter.setProperty(name, value); 1066 } 1067 1068 /** 1069 * Creates an object and processes any nested param elements 1070 * but does not call activateOptions. If the class also supports 1071 * UnrecognizedElementParser, the parseUnrecognizedElement method 1072 * will be call for any child elements other than param. 1073 * 1074 * @param element element, may not be null. 1075 * @param props properties 1076 * @param expectedClass interface or class expected to be implemented 1077 * by created class 1078 * @return created class or null. 1079 * @throws Exception thrown if the contain object should be abandoned. 1080 * @since 1.2.15 1081 */ 1082 public static Object parseElement(final Element element, 1083 final Properties props, 1084 final Class expectedClass) throws Exception { 1085 String clazz = subst(element.getAttribute("class"), props); 1086 Object instance = OptionConverter.instantiateByClassName(clazz, 1087 expectedClass, null); 1088 1089 if (instance != null) { 1090 PropertySetter propSetter = new PropertySetter(instance); 1091 NodeList children = element.getChildNodes(); 1092 final int length = children.getLength(); 1093 1094 for (int loop = 0; loop < length; loop++) { 1095 Node currentNode = children.item(loop); 1096 if (currentNode.getNodeType() == Node.ELEMENT_NODE) { 1097 Element currentElement = (Element) currentNode; 1098 String tagName = currentElement.getTagName(); 1099 if (tagName.equals("param")) { 1100 setParameter(currentElement, propSetter, props); 1101 } else { 1102 parseUnrecognizedElement(instance, currentElement, props); 1103 } 1104 } 1105 } 1106 return instance; 1107 } 1108 return null; 1109 } 1110 1111} 1112 1113 1114class XMLWatchdog extends FileWatchdog { 1115 1116 XMLWatchdog(String filename) { 1117 super(filename); 1118 } 1119 1120 /** 1121 Call {@link DOMConfigurator#configure(String)} with the 1122 <code>filename</code> to reconfigure log4j. */ 1123 public 1124 void doOnChange() { 1125 new DOMConfigurator().doConfigure(filename, 1126 LogManager.getLoggerRepository()); 1127 } 1128}