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