1 /* 2 * Copyright 2001-2004 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 package org.apache.commons.logging.impl; 18 19 20 import java.lang.reflect.Constructor; 21 import java.lang.reflect.InvocationTargetException; 22 import java.lang.reflect.Method; 23 import java.net.URL; 24 import java.util.Enumeration; 25 import java.util.Hashtable; 26 import java.util.Vector; 27 28 import org.apache.commons.logging.Log; 29 import org.apache.commons.logging.LogConfigurationException; 30 import org.apache.commons.logging.LogFactory; 31 32 33 /** 34 * <p>Concrete subclass of {@link LogFactory} that implements the 35 * following algorithm to dynamically select a logging implementation 36 * class to instantiate a wrapper for.</p> 37 * <ul> 38 * <li>Use a factory configuration attribute named 39 * <code>org.apache.commons.logging.Log</code> to identify the 40 * requested implementation class.</li> 41 * <li>Use the <code>org.apache.commons.logging.Log</code> system property 42 * to identify the requested implementation class.</li> 43 * <li>If <em>Log4J</em> is available, return an instance of 44 * <code>org.apache.commons.logging.impl.Log4JLogger</code>.</li> 45 * <li>If <em>JDK 1.4 or later</em> is available, return an instance of 46 * <code>org.apache.commons.logging.impl.Jdk14Logger</code>.</li> 47 * <li>Otherwise, return an instance of 48 * <code>org.apache.commons.logging.impl.SimpleLog</code>.</li> 49 * </ul> 50 * 51 * <p>If the selected {@link Log} implementation class has a 52 * <code>setLogFactory()</code> method that accepts a {@link LogFactory} 53 * parameter, this method will be called on each newly created instance 54 * to identify the associated factory. This makes factory configuration 55 * attributes available to the Log instance, if it so desires.</p> 56 * 57 * <p>This factory will remember previously created <code>Log</code> instances 58 * for the same name, and will return them on repeated requests to the 59 * <code>getInstance()</code> method.</p> 60 * 61 * @author Rod Waldhoff 62 * @author Craig R. McClanahan 63 * @author Richard A. Sitze 64 * @author Brian Stansberry 65 * @version $Revision: 399224 $ $Date: 2006-05-03 10:25:54 +0100 (Wed, 03 May 2006) $ 66 */ 67 68 public class LogFactoryImpl extends LogFactory { 69 70 71 /** Log4JLogger class name */ 72 private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger"; 73 /** Jdk14Logger class name */ 74 private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger"; 75 /** Jdk13LumberjackLogger class name */ 76 private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger"; 77 /** SimpleLog class name */ 78 private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog"; 79 80 private static final String PKG_IMPL="org.apache.commons.logging.impl."; 81 private static final int PKG_LEN = PKG_IMPL.length(); 82 83 // ----------------------------------------------------------- Constructors 84 85 86 87 /** 88 * Public no-arguments constructor required by the lookup mechanism. 89 */ 90 public LogFactoryImpl() { 91 super(); 92 initDiagnostics(); // method on this object 93 if (isDiagnosticsEnabled()) { 94 logDiagnostic("Instance created."); 95 } 96 } 97 98 99 // ----------------------------------------------------- Manifest Constants 100 101 102 /** 103 * The name (<code>org.apache.commons.logging.Log</code>) of the system 104 * property identifying our {@link Log} implementation class. 105 */ 106 public static final String LOG_PROPERTY = 107 "org.apache.commons.logging.Log"; 108 109 110 /** 111 * The deprecated system property used for backwards compatibility with 112 * old versions of JCL. 113 */ 114 protected static final String LOG_PROPERTY_OLD = 115 "org.apache.commons.logging.log"; 116 117 /** 118 * The name (<code>org.apache.commons.logging.Log.allowFlawedContext</code>) 119 * of the system property which can be set true/false to 120 * determine system behaviour when a bad context-classloader is encountered. 121 * When set to false, a LogConfigurationException is thrown if 122 * LogFactoryImpl is loaded via a child classloader of the TCCL (this 123 * should never happen in sane systems). 124 * 125 * Default behaviour: true (tolerates bad context classloaders) 126 * 127 * See also method setAttribute. 128 */ 129 public static final String ALLOW_FLAWED_CONTEXT_PROPERTY = 130 "org.apache.commons.logging.Log.allowFlawedContext"; 131 132 /** 133 * The name (<code>org.apache.commons.logging.Log.allowFlawedDiscovery</code>) 134 * of the system property which can be set true/false to 135 * determine system behaviour when a bad logging adapter class is 136 * encountered during logging discovery. When set to false, an 137 * exception will be thrown and the app will fail to start. When set 138 * to true, discovery will continue (though the user might end up 139 * with a different logging implementation than they expected). 140 * 141 * Default behaviour: true (tolerates bad logging adapters) 142 * 143 * See also method setAttribute. 144 */ 145 public static final String ALLOW_FLAWED_DISCOVERY_PROPERTY = 146 "org.apache.commons.logging.Log.allowFlawedDiscovery"; 147 148 /** 149 * The name (<code>org.apache.commons.logging.Log.allowFlawedHierarchy</code>) 150 * of the system property which can be set true/false to 151 * determine system behaviour when a logging adapter class is 152 * encountered which has bound to the wrong Log class implementation. 153 * When set to false, an exception will be thrown and the app will fail 154 * to start. When set to true, discovery will continue (though the user 155 * might end up with a different logging implementation than they expected). 156 * 157 * Default behaviour: true (tolerates bad Log class hierarchy) 158 * 159 * See also method setAttribute. 160 */ 161 public static final String ALLOW_FLAWED_HIERARCHY_PROPERTY = 162 "org.apache.commons.logging.Log.allowFlawedHierarchy"; 163 164 165 /** 166 * The names of classes that will be tried (in order) as logging 167 * adapters. Each class is expected to implement the Log interface, 168 * and to throw NoClassDefFound or ExceptionInInitializerError when 169 * loaded if the underlying logging library is not available. Any 170 * other error indicates that the underlying logging library is available 171 * but broken/unusable for some reason. 172 */ 173 private static final String[] classesToDiscover = { 174 LOGGING_IMPL_LOG4J_LOGGER, 175 "org.apache.commons.logging.impl.Jdk14Logger", 176 "org.apache.commons.logging.impl.Jdk13LumberjackLogger", 177 "org.apache.commons.logging.impl.SimpleLog" 178 }; 179 180 181 // ----------------------------------------------------- Instance Variables 182 183 /** 184 * Determines whether logging classes should be loaded using the thread-context 185 * classloader, or via the classloader that loaded this LogFactoryImpl class. 186 */ 187 private boolean useTCCL = true; 188 189 /** 190 * The string prefixed to every message output by the logDiagnostic method. 191 */ 192 private String diagnosticPrefix; 193 194 195 /** 196 * Configuration attributes. 197 */ 198 protected Hashtable attributes = new Hashtable(); 199 200 201 /** 202 * The {@link org.apache.commons.logging.Log} instances that have 203 * already been created, keyed by logger name. 204 */ 205 protected Hashtable instances = new Hashtable(); 206 207 208 /** 209 * Name of the class implementing the Log interface. 210 */ 211 private String logClassName; 212 213 214 /** 215 * The one-argument constructor of the 216 * {@link org.apache.commons.logging.Log} 217 * implementation class that will be used to create new instances. 218 * This value is initialized by <code>getLogConstructor()</code>, 219 * and then returned repeatedly. 220 */ 221 protected Constructor logConstructor = null; 222 223 224 /** 225 * The signature of the Constructor to be used. 226 */ 227 protected Class logConstructorSignature[] = 228 { java.lang.String.class }; 229 230 231 /** 232 * The one-argument <code>setLogFactory</code> method of the selected 233 * {@link org.apache.commons.logging.Log} method, if it exists. 234 */ 235 protected Method logMethod = null; 236 237 238 /** 239 * The signature of the <code>setLogFactory</code> method to be used. 240 */ 241 protected Class logMethodSignature[] = 242 { LogFactory.class }; 243 244 /** 245 * See getBaseClassLoader and initConfiguration. 246 */ 247 private boolean allowFlawedContext; 248 249 /** 250 * See handleFlawedDiscovery and initConfiguration. 251 */ 252 private boolean allowFlawedDiscovery; 253 254 /** 255 * See handleFlawedHierarchy and initConfiguration. 256 */ 257 private boolean allowFlawedHierarchy; 258 259 // --------------------------------------------------------- Public Methods 260 261 262 /** 263 * Return the configuration attribute with the specified name (if any), 264 * or <code>null</code> if there is no such attribute. 265 * 266 * @param name Name of the attribute to return 267 */ 268 public Object getAttribute(String name) { 269 270 return (attributes.get(name)); 271 272 } 273 274 275 /** 276 * Return an array containing the names of all currently defined 277 * configuration attributes. If there are no such attributes, a zero 278 * length array is returned. 279 */ 280 public String[] getAttributeNames() { 281 282 Vector names = new Vector(); 283 Enumeration keys = attributes.keys(); 284 while (keys.hasMoreElements()) { 285 names.addElement((String) keys.nextElement()); 286 } 287 String results[] = new String[names.size()]; 288 for (int i = 0; i < results.length; i++) { 289 results[i] = (String) names.elementAt(i); 290 } 291 return (results); 292 293 } 294 295 296 /** 297 * Convenience method to derive a name from the specified class and 298 * call <code>getInstance(String)</code> with it. 299 * 300 * @param clazz Class for which a suitable Log name will be derived 301 * 302 * @exception LogConfigurationException if a suitable <code>Log</code> 303 * instance cannot be returned 304 */ 305 public Log getInstance(Class clazz) throws LogConfigurationException { 306 307 return (getInstance(clazz.getName())); 308 309 } 310 311 312 /** 313 * <p>Construct (if necessary) and return a <code>Log</code> instance, 314 * using the factory's current set of configuration attributes.</p> 315 * 316 * <p><strong>NOTE</strong> - Depending upon the implementation of 317 * the <code>LogFactory</code> you are using, the <code>Log</code> 318 * instance you are returned may or may not be local to the current 319 * application, and may or may not be returned again on a subsequent 320 * call with the same name argument.</p> 321 * 322 * @param name Logical name of the <code>Log</code> instance to be 323 * returned (the meaning of this name is only known to the underlying 324 * logging implementation that is being wrapped) 325 * 326 * @exception LogConfigurationException if a suitable <code>Log</code> 327 * instance cannot be returned 328 */ 329 public Log getInstance(String name) throws LogConfigurationException { 330 331 Log instance = (Log) instances.get(name); 332 if (instance == null) { 333 instance = newInstance(name); 334 instances.put(name, instance); 335 } 336 return (instance); 337 338 } 339 340 341 /** 342 * Release any internal references to previously created 343 * {@link org.apache.commons.logging.Log} 344 * instances returned by this factory. This is useful in environments 345 * like servlet containers, which implement application reloading by 346 * throwing away a ClassLoader. Dangling references to objects in that 347 * class loader would prevent garbage collection. 348 */ 349 public void release() { 350 351 logDiagnostic("Releasing all known loggers"); 352 instances.clear(); 353 } 354 355 356 /** 357 * Remove any configuration attribute associated with the specified name. 358 * If there is no such attribute, no action is taken. 359 * 360 * @param name Name of the attribute to remove 361 */ 362 public void removeAttribute(String name) { 363 364 attributes.remove(name); 365 366 } 367 368 369 /** 370 * Set the configuration attribute with the specified name. Calling 371 * this with a <code>null</code> value is equivalent to calling 372 * <code>removeAttribute(name)</code>. 373 * <p> 374 * This method can be used to set logging configuration programmatically 375 * rather than via system properties. It can also be used in code running 376 * within a container (such as a webapp) to configure behaviour on a 377 * per-component level instead of globally as system properties would do. 378 * To use this method instead of a system property, call 379 * <pre> 380 * LogFactory.getFactory().setAttribute(...) 381 * </pre> 382 * This must be done before the first Log object is created; configuration 383 * changes after that point will be ignored. 384 * <p> 385 * This method is also called automatically if LogFactory detects a 386 * commons-logging.properties file; every entry in that file is set 387 * automatically as an attribute here. 388 * 389 * @param name Name of the attribute to set 390 * @param value Value of the attribute to set, or <code>null</code> 391 * to remove any setting for this attribute 392 */ 393 public void setAttribute(String name, Object value) { 394 395 if (logConstructor != null) { 396 logDiagnostic("setAttribute: call too late; configuration already performed."); 397 } 398 399 if (value == null) { 400 attributes.remove(name); 401 } else { 402 attributes.put(name, value); 403 } 404 405 if (name.equals(TCCL_KEY)) { 406 useTCCL = Boolean.valueOf(value.toString()).booleanValue(); 407 } 408 409 } 410 411 412 // ------------------------------------------------------ 413 // Static Methods 414 // 415 // These methods only defined as workarounds for a java 1.2 bug; 416 // theoretically none of these are needed. 417 // ------------------------------------------------------ 418 419 /** 420 * Gets the context classloader. 421 * This method is a workaround for a java 1.2 compiler bug. 422 * @since 1.1 423 */ 424 protected static ClassLoader getContextClassLoader() throws LogConfigurationException { 425 return LogFactory.getContextClassLoader(); 426 } 427 428 429 /** 430 * Workaround for bug in Java1.2; in theory this method is not needed. 431 * See LogFactory.isDiagnosticsEnabled. 432 */ 433 protected static boolean isDiagnosticsEnabled() { 434 return LogFactory.isDiagnosticsEnabled(); 435 } 436 437 438 /** 439 * Workaround for bug in Java1.2; in theory this method is not needed. 440 * See LogFactory.getClassLoader. 441 * @since 1.1 442 */ 443 protected static ClassLoader getClassLoader(Class clazz) { 444 return LogFactory.getClassLoader(clazz); 445 } 446 447 448 // ------------------------------------------------------ Protected Methods 449 450 /** 451 * Calculate and cache a string that uniquely identifies this instance, 452 * including which classloader the object was loaded from. 453 * <p> 454 * This string will later be prefixed to each "internal logging" message 455 * emitted, so that users can clearly see any unexpected behaviour. 456 * <p> 457 * Note that this method does not detect whether internal logging is 458 * enabled or not, nor where to output stuff if it is; that is all 459 * handled by the parent LogFactory class. This method just computes 460 * its own unique prefix for log messages. 461 */ 462 private void initDiagnostics() { 463 // It would be nice to include an identifier of the context classloader 464 // that this LogFactoryImpl object is responsible for. However that 465 // isn't possible as that information isn't available. It is possible 466 // to figure this out by looking at the logging from LogFactory to 467 // see the context & impl ids from when this object was instantiated, 468 // in order to link the impl id output as this object's prefix back to 469 // the context it is intended to manage. 470 // Note that this prefix should be kept consistent with that 471 // in LogFactory. 472 Class clazz = this.getClass(); 473 ClassLoader classLoader = getClassLoader(clazz); 474 String classLoaderName; 475 try { 476 if (classLoader == null) { 477 classLoaderName = "BOOTLOADER"; 478 } else { 479 classLoaderName = objectId(classLoader); 480 } 481 } catch(SecurityException e) { 482 classLoaderName = "UNKNOWN"; 483 } 484 diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] "; 485 } 486 487 488 /** 489 * Output a diagnostic message to a user-specified destination (if the 490 * user has enabled diagnostic logging). 491 * 492 * @param msg diagnostic message 493 * @since 1.1 494 */ 495 protected void logDiagnostic(String msg) { 496 if (isDiagnosticsEnabled()) { 497 logRawDiagnostic(diagnosticPrefix + msg); 498 } 499 } 500 501 /** 502 * Return the fully qualified Java classname of the {@link Log} 503 * implementation we will be using. 504 * 505 * @deprecated Never invoked by this class; subclasses should not assume 506 * it will be. 507 */ 508 protected String getLogClassName() { 509 510 if (logClassName == null) { 511 discoverLogImplementation(getClass().getName()); 512 } 513 514 return logClassName; 515 } 516 517 518 /** 519 * <p>Return the <code>Constructor</code> that can be called to instantiate 520 * new {@link org.apache.commons.logging.Log} instances.</p> 521 * 522 * <p><strong>IMPLEMENTATION NOTE</strong> - Race conditions caused by 523 * calling this method from more than one thread are ignored, because 524 * the same <code>Constructor</code> instance will ultimately be derived 525 * in all circumstances.</p> 526 * 527 * @exception LogConfigurationException if a suitable constructor 528 * cannot be returned 529 * 530 * @deprecated Never invoked by this class; subclasses should not assume 531 * it will be. 532 */ 533 protected Constructor getLogConstructor() 534 throws LogConfigurationException { 535 536 // Return the previously identified Constructor (if any) 537 if (logConstructor == null) { 538 discoverLogImplementation(getClass().getName()); 539 } 540 541 return logConstructor; 542 } 543 544 545 /** 546 * Is <em>JDK 1.3 with Lumberjack</em> logging available? 547 * 548 * @deprecated Never invoked by this class; subclasses should not assume 549 * it will be. 550 */ 551 protected boolean isJdk13LumberjackAvailable() { 552 return isLogLibraryAvailable( 553 "Jdk13Lumberjack", 554 "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); 555 } 556 557 558 /** 559 * <p>Return <code>true</code> if <em>JDK 1.4 or later</em> logging 560 * is available. Also checks that the <code>Throwable</code> class 561 * supports <code>getStackTrace()</code>, which is required by 562 * Jdk14Logger.</p> 563 * 564 * @deprecated Never invoked by this class; subclasses should not assume 565 * it will be. 566 */ 567 protected boolean isJdk14Available() { 568 return isLogLibraryAvailable( 569 "Jdk14", 570 "org.apache.commons.logging.impl.Jdk14Logger"); 571 } 572 573 574 /** 575 * Is a <em>Log4J</em> implementation available? 576 * 577 * @deprecated Never invoked by this class; subclasses should not assume 578 * it will be. 579 */ 580 protected boolean isLog4JAvailable() { 581 return isLogLibraryAvailable( 582 "Log4J", 583 LOGGING_IMPL_LOG4J_LOGGER); 584 } 585 586 587 /** 588 * Create and return a new {@link org.apache.commons.logging.Log} 589 * instance for the specified name. 590 * 591 * @param name Name of the new logger 592 * 593 * @exception LogConfigurationException if a new instance cannot 594 * be created 595 */ 596 protected Log newInstance(String name) throws LogConfigurationException { 597 598 Log instance = null; 599 try { 600 if (logConstructor == null) { 601 instance = discoverLogImplementation(name); 602 } 603 else { 604 Object params[] = { name }; 605 instance = (Log) logConstructor.newInstance(params); 606 } 607 608 if (logMethod != null) { 609 Object params[] = { this }; 610 logMethod.invoke(instance, params); 611 } 612 613 return (instance); 614 615 } catch (LogConfigurationException lce) { 616 617 // this type of exception means there was a problem in discovery 618 // and we've already output diagnostics about the issue, etc.; 619 // just pass it on 620 throw (LogConfigurationException) lce; 621 622 } catch (InvocationTargetException e) { 623 // A problem occurred invoking the Constructor or Method 624 // previously discovered 625 Throwable c = e.getTargetException(); 626 if (c != null) { 627 throw new LogConfigurationException(c); 628 } else { 629 throw new LogConfigurationException(e); 630 } 631 } catch (Throwable t) { 632 // A problem occurred invoking the Constructor or Method 633 // previously discovered 634 throw new LogConfigurationException(t); 635 } 636 } 637 638 639 // ------------------------------------------------------ Private Methods 640 641 /** 642 * Utility method to check whether a particular logging library is 643 * present and available for use. Note that this does <i>not</i> 644 * affect the future behaviour of this class. 645 */ 646 private boolean isLogLibraryAvailable(String name, String classname) { 647 if (isDiagnosticsEnabled()) { 648 logDiagnostic("Checking for '" + name + "'."); 649 } 650 try { 651 Log log = createLogFromClass( 652 classname, 653 this.getClass().getName(), // dummy category 654 false); 655 656 if (log == null) { 657 if (isDiagnosticsEnabled()) { 658 logDiagnostic("Did not find '" + name + "'."); 659 } 660 return false; 661 } else { 662 if (isDiagnosticsEnabled()) { 663 logDiagnostic("Found '" + name + "'."); 664 } 665 return true; 666 } 667 } catch(LogConfigurationException e) { 668 if (isDiagnosticsEnabled()) { 669 logDiagnostic("Logging system '" + name + "' is available but not useable."); 670 } 671 return false; 672 } 673 } 674 675 /** 676 * Attempt to find an attribute (see method setAttribute) or a 677 * system property with the provided name and return its value. 678 * <p> 679 * The attributes associated with this object are checked before 680 * system properties in case someone has explicitly called setAttribute, 681 * or a configuration property has been set in a commons-logging.properties 682 * file. 683 * 684 * @return the value associated with the property, or null. 685 */ 686 private String getConfigurationValue(String property) { 687 if (isDiagnosticsEnabled()) { 688 logDiagnostic("[ENV] Trying to get configuration for item " + property); 689 } 690 691 Object valueObj = getAttribute(property); 692 if (valueObj != null) { 693 if (isDiagnosticsEnabled()) { 694 logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property); 695 } 696 return valueObj.toString(); 697 } 698 699 if (isDiagnosticsEnabled()) { 700 logDiagnostic("[ENV] No LogFactory attribute found for " + property); 701 } 702 703 try { 704 String value = System.getProperty(property); 705 if (value != null) { 706 if (isDiagnosticsEnabled()) { 707 logDiagnostic("[ENV] Found system property [" + value + "] for " + property); 708 } 709 return value; 710 } 711 712 if (isDiagnosticsEnabled()) { 713 logDiagnostic("[ENV] No system property found for property " + property); 714 } 715 } catch (SecurityException e) { 716 if (isDiagnosticsEnabled()) { 717 logDiagnostic("[ENV] Security prevented reading system property " + property); 718 } 719 } 720 721 if (isDiagnosticsEnabled()) { 722 logDiagnostic("[ENV] No configuration defined for item " + property); 723 } 724 725 return null; 726 } 727 728 /** 729 * Get the setting for the user-configurable behaviour specified by key. 730 * If nothing has explicitly been set, then return dflt. 731 */ 732 private boolean getBooleanConfiguration(String key, boolean dflt) { 733 String val = getConfigurationValue(key); 734 if (val == null) 735 return dflt; 736 return Boolean.valueOf(val).booleanValue(); 737 } 738 739 /** 740 * Initialize a number of variables that control the behaviour of this 741 * class and that can be tweaked by the user. This is done when the first 742 * logger is created, not in the constructor of this class, because we 743 * need to give the user a chance to call method setAttribute in order to 744 * configure this object. 745 */ 746 private void initConfiguration() { 747 allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true); 748 allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true); 749 allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true); 750 } 751 752 753 /** 754 * Attempts to create a Log instance for the given category name. 755 * Follows the discovery process described in the class javadoc. 756 * 757 * @param logCategory the name of the log category 758 * 759 * @throws LogConfigurationException if an error in discovery occurs, 760 * or if no adapter at all can be instantiated 761 */ 762 private Log discoverLogImplementation(String logCategory) 763 throws LogConfigurationException 764 { 765 if (isDiagnosticsEnabled()) { 766 logDiagnostic("Discovering a Log implementation..."); 767 } 768 769 initConfiguration(); 770 771 Log result = null; 772 773 // See if the user specified the Log implementation to use 774 String specifiedLogClassName = findUserSpecifiedLogClassName(); 775 776 if (specifiedLogClassName != null) { 777 if (isDiagnosticsEnabled()) { 778 logDiagnostic("Attempting to load user-specified log class '" + 779 specifiedLogClassName + "'..."); 780 } 781 782 result = createLogFromClass(specifiedLogClassName, 783 logCategory, 784 true); 785 if (result == null) { 786 StringBuffer messageBuffer = new StringBuffer("User-specified log class '"); 787 messageBuffer.append(specifiedLogClassName); 788 messageBuffer.append("' cannot be found or is not useable."); 789 790 // Mistyping or misspelling names is a common fault. 791 // Construct a good error message, if we can 792 if (specifiedLogClassName != null) { 793 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); 794 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); 795 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); 796 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); 797 } 798 throw new LogConfigurationException(messageBuffer.toString()); 799 } 800 801 return result; 802 } 803 804 // No user specified log; try to discover what's on the classpath 805 // 806 // Note that we deliberately loop here over classesToDiscover and 807 // expect method createLogFromClass to loop over the possible source 808 // classloaders. The effect is: 809 // for each discoverable log adapter 810 // for each possible classloader 811 // see if it works 812 // 813 // It appears reasonable at first glance to do the opposite: 814 // for each possible classloader 815 // for each discoverable log adapter 816 // see if it works 817 // 818 // The latter certainly has advantages for user-installable logging 819 // libraries such as log4j; in a webapp for example this code should 820 // first check whether the user has provided any of the possible 821 // logging libraries before looking in the parent classloader. 822 // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, 823 // and SimpleLog will always work in any JVM. So the loop would never 824 // ever look for logging libraries in the parent classpath. Yet many 825 // users would expect that putting log4j there would cause it to be 826 // detected (and this is the historical JCL behaviour). So we go with 827 // the first approach. A user that has bundled a specific logging lib 828 // in a webapp should use a commons-logging.properties file or a 829 // service file in META-INF to force use of that logging lib anyway, 830 // rather than relying on discovery. 831 832 if (isDiagnosticsEnabled()) { 833 logDiagnostic( 834 "No user-specified Log implementation; performing discovery" + 835 " using the standard supported logging implementations..."); 836 } 837 for(int i=0; (i<classesToDiscover.length) && (result == null); ++i) { 838 result = createLogFromClass(classesToDiscover[i], logCategory, true); 839 } 840 841 if (result == null) { 842 throw new LogConfigurationException 843 ("No suitable Log implementation"); 844 } 845 846 return result; 847 } 848 849 850 /** 851 * Appends message if the given name is similar to the candidate. 852 * @param messageBuffer <code>StringBuffer</code> the message should be appended to, 853 * not null 854 * @param name the (trimmed) name to be test against the candidate, not null 855 * @param candidate the candidate name (not null) 856 */ 857 private void informUponSimilarName(final StringBuffer messageBuffer, final String name, 858 final String candidate) { 859 if (name.equals(candidate)) { 860 // Don't suggest a name that is exactly the same as the one the 861 // user tried... 862 return; 863 } 864 865 // If the user provides a name that is in the right package, and gets 866 // the first 5 characters of the adapter class right (ignoring case), 867 // then suggest the candidate adapter class name. 868 if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) { 869 messageBuffer.append(" Did you mean '"); 870 messageBuffer.append(candidate); 871 messageBuffer.append("'?"); 872 } 873 } 874 875 876 /** 877 * Checks system properties and the attribute map for 878 * a Log implementation specified by the user under the 879 * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}. 880 * 881 * @return classname specified by the user, or <code>null</code> 882 */ 883 private String findUserSpecifiedLogClassName() 884 { 885 if (isDiagnosticsEnabled()) { 886 logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'"); 887 } 888 String specifiedClass = (String) getAttribute(LOG_PROPERTY); 889 890 if (specifiedClass == null) { // @deprecated 891 if (isDiagnosticsEnabled()) { 892 logDiagnostic("Trying to get log class from attribute '" + 893 LOG_PROPERTY_OLD + "'"); 894 } 895 specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); 896 } 897 898 if (specifiedClass == null) { 899 if (isDiagnosticsEnabled()) { 900 logDiagnostic("Trying to get log class from system property '" + 901 LOG_PROPERTY + "'"); 902 } 903 try { 904 specifiedClass = System.getProperty(LOG_PROPERTY); 905 } catch (SecurityException e) { 906 if (isDiagnosticsEnabled()) { 907 logDiagnostic("No access allowed to system property '" + 908 LOG_PROPERTY + "' - " + e.getMessage()); 909 } 910 } 911 } 912 913 if (specifiedClass == null) { // @deprecated 914 if (isDiagnosticsEnabled()) { 915 logDiagnostic("Trying to get log class from system property '" + 916 LOG_PROPERTY_OLD + "'"); 917 } 918 try { 919 specifiedClass = System.getProperty(LOG_PROPERTY_OLD); 920 } catch (SecurityException e) { 921 if (isDiagnosticsEnabled()) { 922 logDiagnostic("No access allowed to system property '" + 923 LOG_PROPERTY_OLD + "' - " + e.getMessage()); 924 } 925 } 926 } 927 928 // Remove any whitespace; it's never valid in a classname so its 929 // presence just means a user mistake. As we know what they meant, 930 // we may as well strip the spaces. 931 if (specifiedClass != null) { 932 specifiedClass = specifiedClass.trim(); 933 } 934 935 return specifiedClass; 936 } 937 938 939 /** 940 * Attempts to load the given class, find a suitable constructor, 941 * and instantiate an instance of Log. 942 * 943 * @param logAdapterClassName classname of the Log implementation 944 * 945 * @param logCategory argument to pass to the Log implementation's 946 * constructor 947 * 948 * @param affectState <code>true</code> if this object's state should 949 * be affected by this method call, <code>false</code> otherwise. 950 * 951 * @return an instance of the given class, or null if the logging 952 * library associated with the specified adapter is not available. 953 * 954 * @throws LogConfigurationException if there was a serious error with 955 * configuration and the handleFlawedDiscovery method decided this 956 * problem was fatal. 957 */ 958 private Log createLogFromClass(String logAdapterClassName, 959 String logCategory, 960 boolean affectState) 961 throws LogConfigurationException { 962 963 if (isDiagnosticsEnabled()) { 964 logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'"); 965 } 966 967 Object[] params = { logCategory }; 968 Log logAdapter = null; 969 Constructor constructor = null; 970 971 Class logAdapterClass = null; 972 ClassLoader currentCL = getBaseClassLoader(); 973 974 for(;;) { 975 // Loop through the classloader hierarchy trying to find 976 // a viable classloader. 977 logDiagnostic( 978 "Trying to load '" 979 + logAdapterClassName 980 + "' from classloader " 981 + objectId(currentCL)); 982 try { 983 if (isDiagnosticsEnabled()) { 984 // Show the location of the first occurrence of the .class file 985 // in the classpath. This is the location that ClassLoader.loadClass 986 // will load the class from -- unless the classloader is doing 987 // something weird. 988 URL url; 989 String resourceName = logAdapterClassName.replace('.', '/') + ".class"; 990 if (currentCL != null) { 991 url = currentCL.getResource(resourceName ); 992 } else { 993 url = ClassLoader.getSystemResource(resourceName + ".class"); 994 } 995 996 if (url == null) { 997 logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found."); 998 } else { 999 logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'"); 1000 } 1001 } 1002 1003 Class c = null; 1004 try { 1005 c = Class.forName(logAdapterClassName, true, currentCL); 1006 } catch (ClassNotFoundException originalClassNotFoundException) { 1007 // The current classloader was unable to find the log adapter 1008 // in this or any ancestor classloader. There's no point in 1009 // trying higher up in the hierarchy in this case.. 1010 String msg = "" + originalClassNotFoundException.getMessage(); 1011 logDiagnostic( 1012 "The log adapter '" 1013 + logAdapterClassName 1014 + "' is not available via classloader " 1015 + objectId(currentCL) 1016 + ": " 1017 + msg.trim()); 1018 try { 1019 // Try the class classloader. 1020 // This may work in cases where the TCCL 1021 // does not contain the code executed or JCL. 1022 // This behaviour indicates that the application 1023 // classloading strategy is not consistent with the 1024 // Java 1.2 classloading guidelines but JCL can 1025 // and so should handle this case. 1026 c = Class.forName(logAdapterClassName); 1027 } catch (ClassNotFoundException secondaryClassNotFoundException) { 1028 // no point continuing: this adapter isn't available 1029 msg = "" + secondaryClassNotFoundException.getMessage(); 1030 logDiagnostic( 1031 "The log adapter '" 1032 + logAdapterClassName 1033 + "' is not available via the LogFactoryImpl class classloader: " 1034 + msg.trim()); 1035 break; 1036 } 1037 } 1038 1039 constructor = c.getConstructor(logConstructorSignature); 1040 Object o = constructor.newInstance(params); 1041 1042 // Note that we do this test after trying to create an instance 1043 // [rather than testing Log.class.isAssignableFrom(c)] so that 1044 // we don't complain about Log hierarchy problems when the 1045 // adapter couldn't be instantiated anyway. 1046 if (o instanceof Log) { 1047 logAdapterClass = c; 1048 logAdapter = (Log) o; 1049 break; 1050 } 1051 1052 // Oops, we have a potential problem here. An adapter class 1053 // has been found and its underlying lib is present too, but 1054 // there are multiple Log interface classes available making it 1055 // impossible to cast to the type the caller wanted. We 1056 // certainly can't use this logger, but we need to know whether 1057 // to keep on discovering or terminate now. 1058 // 1059 // The handleFlawedHierarchy method will throw 1060 // LogConfigurationException if it regards this problem as 1061 // fatal, and just return if not. 1062 handleFlawedHierarchy(currentCL, c); 1063 } catch (NoClassDefFoundError e) { 1064 // We were able to load the adapter but it had references to 1065 // other classes that could not be found. This simply means that 1066 // the underlying logger library is not present in this or any 1067 // ancestor classloader. There's no point in trying higher up 1068 // in the hierarchy in this case.. 1069 String msg = "" + e.getMessage(); 1070 logDiagnostic( 1071 "The log adapter '" 1072 + logAdapterClassName 1073 + "' is missing dependencies when loaded via classloader " 1074 + objectId(currentCL) 1075 + ": " 1076 + msg.trim()); 1077 break; 1078 } catch (ExceptionInInitializerError e) { 1079 // A static initializer block or the initializer code associated 1080 // with a static variable on the log adapter class has thrown 1081 // an exception. 1082 // 1083 // We treat this as meaning the adapter's underlying logging 1084 // library could not be found. 1085 String msg = "" + e.getMessage(); 1086 logDiagnostic( 1087 "The log adapter '" 1088 + logAdapterClassName 1089 + "' is unable to initialize itself when loaded via classloader " 1090 + objectId(currentCL) 1091 + ": " 1092 + msg.trim()); 1093 break; 1094 } catch(LogConfigurationException e) { 1095 // call to handleFlawedHierarchy above must have thrown 1096 // a LogConfigurationException, so just throw it on 1097 throw e; 1098 } catch(Throwable t) { 1099 // handleFlawedDiscovery will determine whether this is a fatal 1100 // problem or not. If it is fatal, then a LogConfigurationException 1101 // will be thrown. 1102 handleFlawedDiscovery(logAdapterClassName, currentCL, t); 1103 } 1104 1105 if (currentCL == null) { 1106 break; 1107 } 1108 1109 // try the parent classloader 1110 currentCL = currentCL.getParent(); 1111 } 1112 1113 if ((logAdapter != null) && affectState) { 1114 // We've succeeded, so set instance fields 1115 this.logClassName = logAdapterClassName; 1116 this.logConstructor = constructor; 1117 1118 // Identify the <code>setLogFactory</code> method (if there is one) 1119 try { 1120 this.logMethod = logAdapterClass.getMethod("setLogFactory", 1121 logMethodSignature); 1122 logDiagnostic("Found method setLogFactory(LogFactory) in '" 1123 + logAdapterClassName + "'"); 1124 } catch (Throwable t) { 1125 this.logMethod = null; 1126 logDiagnostic( 1127 "[INFO] '" + logAdapterClassName 1128 + "' from classloader " + objectId(currentCL) 1129 + " does not declare optional method " 1130 + "setLogFactory(LogFactory)"); 1131 } 1132 1133 logDiagnostic( 1134 "Log adapter '" + logAdapterClassName 1135 + "' from classloader " + objectId(logAdapterClass.getClassLoader()) 1136 + " has been selected for use."); 1137 } 1138 1139 return logAdapter; 1140 } 1141 1142 1143 /** 1144 * Return the classloader from which we should try to load the logging 1145 * adapter classes. 1146 * <p> 1147 * This method usually returns the context classloader. However if it 1148 * is discovered that the classloader which loaded this class is a child 1149 * of the context classloader <i>and</i> the allowFlawedContext option 1150 * has been set then the classloader which loaded this class is returned 1151 * instead. 1152 * <p> 1153 * The only time when the classloader which loaded this class is a 1154 * descendant (rather than the same as or an ancestor of the context 1155 * classloader) is when an app has created custom classloaders but 1156 * failed to correctly set the context classloader. This is a bug in 1157 * the calling application; however we provide the option for JCL to 1158 * simply generate a warning rather than fail outright. 1159 * 1160 */ 1161 private ClassLoader getBaseClassLoader() throws LogConfigurationException { 1162 ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class); 1163 1164 if (useTCCL == false) { 1165 return thisClassLoader; 1166 } 1167 1168 ClassLoader contextClassLoader = getContextClassLoader(); 1169 1170 ClassLoader baseClassLoader = getLowestClassLoader( 1171 contextClassLoader, thisClassLoader); 1172 1173 if (baseClassLoader == null) { 1174 // The two classloaders are not part of a parent child relationship. 1175 // In some classloading setups (e.g. JBoss with its 1176 // UnifiedLoaderRepository) this can still work, so if user hasn't 1177 // forbidden it, just return the contextClassLoader. 1178 if (allowFlawedContext) { 1179 if (isDiagnosticsEnabled()) { 1180 logDiagnostic( 1181 "[WARNING] the context classloader is not part of a" 1182 + " parent-child relationship with the classloader that" 1183 + " loaded LogFactoryImpl."); 1184 } 1185 // If contextClassLoader were null, getLowestClassLoader() would 1186 // have returned thisClassLoader. The fact we are here means 1187 // contextClassLoader is not null, so we can just return it. 1188 return contextClassLoader; 1189 } 1190 else { 1191 throw new LogConfigurationException( 1192 "Bad classloader hierarchy; LogFactoryImpl was loaded via" 1193 + " a classloader that is not related to the current context" 1194 + " classloader."); 1195 } 1196 } 1197 1198 if (baseClassLoader != contextClassLoader) { 1199 // We really should just use the contextClassLoader as the starting 1200 // point for scanning for log adapter classes. However it is expected 1201 // that there are a number of broken systems out there which create 1202 // custom classloaders but fail to set the context classloader so 1203 // we handle those flawed systems anyway. 1204 if (allowFlawedContext) { 1205 if (isDiagnosticsEnabled()) { 1206 logDiagnostic( 1207 "Warning: the context classloader is an ancestor of the" 1208 + " classloader that loaded LogFactoryImpl; it should be" 1209 + " the same or a descendant. The application using" 1210 + " commons-logging should ensure the context classloader" 1211 + " is used correctly."); 1212 } 1213 } else { 1214 throw new LogConfigurationException( 1215 "Bad classloader hierarchy; LogFactoryImpl was loaded via" 1216 + " a classloader that is not related to the current context" 1217 + " classloader."); 1218 } 1219 } 1220 1221 return baseClassLoader; 1222 } 1223 1224 /** 1225 * Given two related classloaders, return the one which is a child of 1226 * the other. 1227 * <p> 1228 * @param c1 is a classloader (including the null classloader) 1229 * @param c2 is a classloader (including the null classloader) 1230 * 1231 * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor, 1232 * and null if neither is an ancestor of the other. 1233 */ 1234 private ClassLoader getLowestClassLoader(ClassLoader c1, ClassLoader c2) { 1235 // TODO: use AccessController when dealing with classloaders here 1236 1237 if (c1 == null) 1238 return c2; 1239 1240 if (c2 == null) 1241 return c1; 1242 1243 ClassLoader current; 1244 1245 // scan c1's ancestors to find c2 1246 current = c1; 1247 while (current != null) { 1248 if (current == c2) 1249 return c1; 1250 current = current.getParent(); 1251 } 1252 1253 // scan c2's ancestors to find c1 1254 current = c2; 1255 while (current != null) { 1256 if (current == c1) 1257 return c2; 1258 current = current.getParent(); 1259 } 1260 1261 return null; 1262 } 1263 1264 /** 1265 * Generates an internal diagnostic logging of the discovery failure and 1266 * then throws a <code>LogConfigurationException</code> that wraps 1267 * the passed <code>Throwable</code>. 1268 * 1269 * @param logAdapterClassName is the class name of the Log implementation 1270 * that could not be instantiated. Cannot be <code>null</code>. 1271 * 1272 * @param classLoader is the classloader that we were trying to load the 1273 * logAdapterClassName from when the exception occurred. 1274 * 1275 * @param discoveryFlaw is the Throwable created by the classloader 1276 * 1277 * @throws LogConfigurationException ALWAYS 1278 */ 1279 private void handleFlawedDiscovery(String logAdapterClassName, 1280 ClassLoader classLoader, 1281 Throwable discoveryFlaw) { 1282 1283 if (isDiagnosticsEnabled()) { 1284 logDiagnostic("Could not instantiate Log '" 1285 + logAdapterClassName + "' -- " 1286 + discoveryFlaw.getClass().getName() + ": " 1287 + discoveryFlaw.getLocalizedMessage()); 1288 } 1289 1290 if (!allowFlawedDiscovery) { 1291 throw new LogConfigurationException(discoveryFlaw); 1292 } 1293 } 1294 1295 1296 /** 1297 * Report a problem loading the log adapter, then either return 1298 * (if the situation is considered recoverable) or throw a 1299 * LogConfigurationException. 1300 * <p> 1301 * There are two possible reasons why we successfully loaded the 1302 * specified log adapter class then failed to cast it to a Log object: 1303 * <ol> 1304 * <li>the specific class just doesn't implement the Log interface 1305 * (user screwed up), or 1306 * <li> the specified class has bound to a Log class loaded by some other 1307 * classloader; Log@classloaderX cannot be cast to Log@classloaderY. 1308 * </ol> 1309 * <p> 1310 * Here we try to figure out which case has occurred so we can give the 1311 * user some reasonable feedback. 1312 * 1313 * @param badClassLoader is the classloader we loaded the problem class from, 1314 * ie it is equivalent to badClass.getClassLoader(). 1315 * 1316 * @param badClass is a Class object with the desired name, but which 1317 * does not implement Log correctly. 1318 * 1319 * @throws LogConfigurationException when the situation 1320 * should not be recovered from. 1321 */ 1322 private void handleFlawedHierarchy(ClassLoader badClassLoader, Class badClass) 1323 throws LogConfigurationException { 1324 1325 boolean implementsLog = false; 1326 String logInterfaceName = Log.class.getName(); 1327 Class interfaces[] = badClass.getInterfaces(); 1328 for (int i = 0; i < interfaces.length; i++) { 1329 if (logInterfaceName.equals(interfaces[i].getName())) { 1330 implementsLog = true; 1331 break; 1332 } 1333 } 1334 1335 if (implementsLog) { 1336 // the class does implement an interface called Log, but 1337 // it is in the wrong classloader 1338 if (isDiagnosticsEnabled()) { 1339 try { 1340 ClassLoader logInterfaceClassLoader = getClassLoader(Log.class); 1341 logDiagnostic( 1342 "Class '" + badClass.getName() 1343 + "' was found in classloader " 1344 + objectId(badClassLoader) 1345 + ". It is bound to a Log interface which is not" 1346 + " the one loaded from classloader " 1347 + objectId(logInterfaceClassLoader)); 1348 } catch (Throwable t) { 1349 logDiagnostic( 1350 "Error while trying to output diagnostics about" 1351 + " bad class '" + badClass + "'"); 1352 } 1353 } 1354 1355 if (!allowFlawedHierarchy) { 1356 StringBuffer msg = new StringBuffer(); 1357 msg.append("Terminating logging for this context "); 1358 msg.append("due to bad log hierarchy. "); 1359 msg.append("You have more than one version of '"); 1360 msg.append(Log.class.getName()); 1361 msg.append("' visible."); 1362 if (isDiagnosticsEnabled()) { 1363 logDiagnostic(msg.toString()); 1364 } 1365 throw new LogConfigurationException(msg.toString()); 1366 } 1367 1368 if (isDiagnosticsEnabled()) { 1369 StringBuffer msg = new StringBuffer(); 1370 msg.append("Warning: bad log hierarchy. "); 1371 msg.append("You have more than one version of '"); 1372 msg.append(Log.class.getName()); 1373 msg.append("' visible."); 1374 logDiagnostic(msg.toString()); 1375 } 1376 } else { 1377 // this is just a bad adapter class 1378 if (!allowFlawedDiscovery) { 1379 StringBuffer msg = new StringBuffer(); 1380 msg.append("Terminating logging for this context. "); 1381 msg.append("Log class '"); 1382 msg.append(badClass.getName()); 1383 msg.append("' does not implement the Log interface."); 1384 if (isDiagnosticsEnabled()) { 1385 logDiagnostic(msg.toString()); 1386 } 1387 1388 throw new LogConfigurationException(msg.toString()); 1389 } 1390 1391 if (isDiagnosticsEnabled()) { 1392 StringBuffer msg = new StringBuffer(); 1393 msg.append("[WARNING] Log class '"); 1394 msg.append(badClass.getName()); 1395 msg.append("' does not implement the Log interface."); 1396 logDiagnostic(msg.toString()); 1397 } 1398 } 1399 } 1400 } 1401