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 * @throws LogConfigurationException if an error in discovery occurs, 494 * or if no adapter at all can be instantiated 495 */ 496 private Log discoverLogImplementation(final String logCategory) 497 throws LogConfigurationException { 498 if (isDiagnosticsEnabled()) { 499 logDiagnostic("Discovering a Log implementation..."); 500 } 501 502 initConfiguration(); 503 504 Log result = null; 505 506 // See if the user specified the Log implementation to use 507 final String specifiedLogClassName = findUserSpecifiedLogClassName(); 508 509 if (specifiedLogClassName != null) { 510 if (isDiagnosticsEnabled()) { 511 logDiagnostic("Attempting to load user-specified log class '" + 512 specifiedLogClassName + "'..."); 513 } 514 515 result = createLogFromClass(specifiedLogClassName, 516 logCategory, 517 true); 518 if (result == null) { 519 final StringBuilder messageBuffer = new StringBuilder("User-specified log class '"); 520 messageBuffer.append(specifiedLogClassName); 521 messageBuffer.append("' cannot be found or is not useable."); 522 523 // Mistyping or misspelling names is a common fault. 524 // Construct a good error message, if we can 525 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); 526 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); 527 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); 528 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); 529 throw new LogConfigurationException(messageBuffer.toString()); 530 } 531 532 return result; 533 } 534 535 // No user specified log; try to discover what's on the classpath 536 // 537 // Note that we deliberately loop here over classesToDiscover and 538 // expect method createLogFromClass to loop over the possible source 539 // class loaders. The effect is: 540 // for each discoverable log adapter 541 // for each possible class loader 542 // see if it works 543 // 544 // It appears reasonable at first glance to do the opposite: 545 // for each possible class loader 546 // for each discoverable log adapter 547 // see if it works 548 // 549 // The latter certainly has advantages for user-installable logging 550 // libraries such as Log4j; in a webapp for example this code should 551 // first check whether the user has provided any of the possible 552 // logging libraries before looking in the parent class loader. 553 // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, 554 // and SimpleLog will always work in any JVM. So the loop would never 555 // ever look for logging libraries in the parent classpath. Yet many 556 // users would expect that putting Log4j there would cause it to be 557 // detected (and this is the historical JCL behavior). So we go with 558 // the first approach. A user that has bundled a specific logging lib 559 // in a webapp should use a commons-logging.properties file or a 560 // service file in META-INF to force use of that logging lib anyway, 561 // rather than relying on discovery. 562 563 if (isDiagnosticsEnabled()) { 564 logDiagnostic( 565 "No user-specified Log implementation; performing discovery" + 566 " using the standard supported logging implementations..."); 567 } 568 for(int i=0; i<classesToDiscover.length && result == null; ++i) { 569 result = createLogFromClass(classesToDiscover[i], logCategory, true); 570 } 571 572 if (result == null) { 573 throw new LogConfigurationException 574 ("No suitable Log implementation"); 575 } 576 577 return result; 578 } 579 580 /** 581 * Checks system properties and the attribute map for 582 * a Log implementation specified by the user under the 583 * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}. 584 * 585 * @return class name specified by the user, or {@code null} 586 */ 587 private String findUserSpecifiedLogClassName() { 588 if (isDiagnosticsEnabled()) { 589 logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'"); 590 } 591 String specifiedClass = (String) getAttribute(LOG_PROPERTY); 592 593 if (specifiedClass == null) { // @deprecated 594 if (isDiagnosticsEnabled()) { 595 logDiagnostic("Trying to get log class from attribute '" + 596 LOG_PROPERTY_OLD + "'"); 597 } 598 specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); 599 } 600 601 if (specifiedClass == null) { 602 if (isDiagnosticsEnabled()) { 603 logDiagnostic("Trying to get log class from system property '" + 604 LOG_PROPERTY + "'"); 605 } 606 try { 607 specifiedClass = getSystemProperty(LOG_PROPERTY, null); 608 } catch (final SecurityException e) { 609 if (isDiagnosticsEnabled()) { 610 logDiagnostic("No access allowed to system property '" + 611 LOG_PROPERTY + "' - " + e.getMessage()); 612 } 613 } 614 } 615 616 if (specifiedClass == null) { // @deprecated 617 if (isDiagnosticsEnabled()) { 618 logDiagnostic("Trying to get log class from system property '" + 619 LOG_PROPERTY_OLD + "'"); 620 } 621 try { 622 specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null); 623 } catch (final SecurityException e) { 624 if (isDiagnosticsEnabled()) { 625 logDiagnostic("No access allowed to system property '" + 626 LOG_PROPERTY_OLD + "' - " + e.getMessage()); 627 } 628 } 629 } 630 631 // Remove any whitespace; it's never valid in a class name so its 632 // presence just means a user mistake. As we know what they meant, 633 // we may as well strip the spaces. 634 if (specifiedClass != null) { 635 specifiedClass = specifiedClass.trim(); 636 } 637 638 return specifiedClass; 639 } 640 641 /** 642 * Gets the configuration attribute with the specified name (if any), 643 * or {@code null} if there is no such attribute. 644 * 645 * @param name Name of the attribute to return 646 */ 647 @Override 648 public Object getAttribute(final String name) { 649 return attributes.get(name); 650 } 651 652 /** 653 * Gets an array containing the names of all currently defined 654 * configuration attributes. If there are no such attributes, a zero 655 * length array is returned. 656 */ 657 @Override 658 public String[] getAttributeNames() { 659 return attributes.keySet().toArray(EMPTY_STRING_ARRAY); 660 } 661 662 /** 663 * Gets the class loader from which we should try to load the logging 664 * adapter classes. 665 * <p> 666 * This method usually returns the context class loader. However if it 667 * is discovered that the class loader which loaded this class is a child 668 * of the context class loader <em>and</em> the allowFlawedContext option 669 * has been set then the class loader which loaded this class is returned 670 * instead. 671 * <p> 672 * The only time when the class loader which loaded this class is a 673 * descendant (rather than the same as or an ancestor of the context 674 * class loader) is when an app has created custom class loaders but 675 * failed to correctly set the context class loader. This is a bug in 676 * the calling application; however we provide the option for JCL to 677 * simply generate a warning rather than fail outright. 678 */ 679 private ClassLoader getBaseClassLoader() throws LogConfigurationException { 680 final ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class); 681 682 if (!useTCCL) { 683 return thisClassLoader; 684 } 685 686 final ClassLoader contextClassLoader = getContextClassLoaderInternal(); 687 688 final ClassLoader baseClassLoader = getLowestClassLoader( 689 contextClassLoader, thisClassLoader); 690 691 if (baseClassLoader == null) { 692 // The two class loaders are not part of a parent child relationship. 693 // In some classloading setups (e.g. JBoss with its 694 // UnifiedLoaderRepository) this can still work, so if user hasn't 695 // forbidden it, just return the contextClassLoader. 696 if (!allowFlawedContext) { 697 throw new LogConfigurationException("Bad class loader hierarchy; LogFactoryImpl was loaded via" + 698 " a class loader that is not related to the current context" + 699 " class loader."); 700 } 701 if (isDiagnosticsEnabled()) { 702 logDiagnostic("[WARNING] the context class loader is not part of a" + 703 " parent-child relationship with the class loader that" + 704 " loaded LogFactoryImpl."); 705 } 706 // If contextClassLoader were null, getLowestClassLoader() would 707 // have returned thisClassLoader. The fact we are here means 708 // contextClassLoader is not null, so we can just return it. 709 return contextClassLoader; 710 } 711 712 if (baseClassLoader != contextClassLoader) { 713 // We really should just use the contextClassLoader as the starting 714 // point for scanning for log adapter classes. However it is expected 715 // that there are a number of broken systems out there which create 716 // custom class loaders but fail to set the context class loader so 717 // we handle those flawed systems anyway. 718 if (!allowFlawedContext) { 719 throw new LogConfigurationException( 720 "Bad class loader hierarchy; LogFactoryImpl was loaded via" + 721 " a class loader that is not related to the current context" + 722 " class loader."); 723 } 724 if (isDiagnosticsEnabled()) { 725 logDiagnostic( 726 "Warning: the context class loader is an ancestor of the" + 727 " class loader that loaded LogFactoryImpl; it should be" + 728 " the same or a descendant. The application using" + 729 " commons-logging should ensure the context class loader" + 730 " is used correctly."); 731 } 732 } 733 734 return baseClassLoader; 735 } 736 737 /** 738 * Gets the setting for the user-configurable behavior specified by key. 739 * If nothing has explicitly been set, then return dflt. 740 */ 741 private boolean getBooleanConfiguration(final String key, final boolean dflt) { 742 final String val = getConfigurationValue(key); 743 if (val == null) { 744 return dflt; 745 } 746 return Boolean.parseBoolean(val); 747 } 748 749 /** 750 * Attempt to find an attribute (see method setAttribute) or a 751 * system property with the provided name and return its value. 752 * <p> 753 * The attributes associated with this object are checked before 754 * system properties in case someone has explicitly called setAttribute, 755 * or a configuration property has been set in a commons-logging.properties 756 * file. 757 * 758 * @return the value associated with the property, or null. 759 */ 760 private String getConfigurationValue(final String property) { 761 if (isDiagnosticsEnabled()) { 762 logDiagnostic("[ENV] Trying to get configuration for item " + property); 763 } 764 765 final Object valueObj = getAttribute(property); 766 if (valueObj != null) { 767 if (isDiagnosticsEnabled()) { 768 logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property); 769 } 770 return valueObj.toString(); 771 } 772 773 if (isDiagnosticsEnabled()) { 774 logDiagnostic("[ENV] No LogFactory attribute found for " + property); 775 } 776 777 try { 778 // warning: minor security hole here, in that we potentially read a system 779 // property that the caller cannot, then output it in readable form as a 780 // diagnostic message. However it's only ever JCL-specific properties 781 // involved here, so the harm is truly trivial. 782 final String value = getSystemProperty(property, null); 783 if (value != null) { 784 if (isDiagnosticsEnabled()) { 785 logDiagnostic("[ENV] Found system property [" + value + "] for " + property); 786 } 787 return value; 788 } 789 790 if (isDiagnosticsEnabled()) { 791 logDiagnostic("[ENV] No system property found for property " + property); 792 } 793 } catch (final SecurityException e) { 794 if (isDiagnosticsEnabled()) { 795 logDiagnostic("[ENV] Security prevented reading system property " + property); 796 } 797 } 798 799 if (isDiagnosticsEnabled()) { 800 logDiagnostic("[ENV] No configuration defined for item " + property); 801 } 802 803 return null; 804 } 805 806 /** 807 * Convenience method to derive a name from the specified class and 808 * call {@code getInstance(String)} with it. 809 * 810 * @param clazz Class for which a suitable Log name will be derived 811 * @throws LogConfigurationException if a suitable {@code Log} 812 * instance cannot be returned 813 */ 814 @Override 815 public Log getInstance(final Class<?> clazz) throws LogConfigurationException { 816 return getInstance(clazz.getName()); 817 } 818 819 /** 820 * <p>Construct (if necessary) and return a {@code Log} instance, 821 * using the factory's current set of configuration attributes.</p> 822 * 823 * <p><strong>NOTE</strong> - Depending upon the implementation of 824 * the {@code LogFactory} you are using, the {@code Log} 825 * instance you are returned may or may not be local to the current 826 * application, and may or may not be returned again on a subsequent 827 * call with the same name argument.</p> 828 * 829 * @param name Logical name of the {@code Log} instance to be 830 * returned (the meaning of this name is only known to the underlying 831 * logging implementation that is being wrapped) 832 * 833 * @throws LogConfigurationException if a suitable {@code Log} 834 * instance cannot be returned 835 */ 836 @Override 837 public Log getInstance(final String name) throws LogConfigurationException { 838 return instances.computeIfAbsent(name, this::newInstance); 839 } 840 841 /** 842 * Gets the fully qualified Java class name of the {@link Log} implementation we will be using. 843 * 844 * @return the fully qualified Java class name of the {@link Log} implementation we will be using. 845 * @deprecated Never invoked by this class; subclasses should not assume it will be. 846 */ 847 @Deprecated 848 protected String getLogClassName() { 849 if (logClassName == null) { 850 discoverLogImplementation(getClass().getName()); 851 } 852 853 return logClassName; 854 } 855 856 /** 857 * <p> 858 * Gets the {@code Constructor} that can be called to instantiate new {@link org.apache.commons.logging.Log} instances. 859 * </p> 860 * 861 * <p> 862 * <strong>IMPLEMENTATION NOTE</strong> - Race conditions caused by calling this method from more than one thread are ignored, because the same 863 * {@code Constructor} instance will ultimately be derived in all circumstances. 864 * </p> 865 * 866 * @return the {@code Constructor} that can be called to instantiate new {@link org.apache.commons.logging.Log} instances. 867 * @throws LogConfigurationException if a suitable constructor cannot be returned 868 * @deprecated Never invoked by this class; subclasses should not assume it will be. 869 */ 870 @Deprecated 871 protected Constructor<?> getLogConstructor() 872 throws LogConfigurationException { 873 874 // Return the previously identified Constructor (if any) 875 if (logConstructor == null) { 876 discoverLogImplementation(getClass().getName()); 877 } 878 879 return logConstructor; 880 } 881 882 // ------------------------------------------------------ Private Methods 883 884 /** 885 * Given two related class loaders, return the one which is a child of 886 * the other. 887 * 888 * @param c1 is a class loader (including the null class loader) 889 * @param c2 is a class loader (including the null class loader) 890 * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor, 891 * and null if neither is an ancestor of the other. 892 */ 893 private ClassLoader getLowestClassLoader(final ClassLoader c1, final ClassLoader c2) { 894 // TODO: use AccessController when dealing with class loaders here 895 896 if (c1 == null) { 897 return c2; 898 } 899 900 if (c2 == null) { 901 return c1; 902 } 903 904 ClassLoader current; 905 906 // scan c1's ancestors to find c2 907 current = c1; 908 while (current != null) { 909 if (current == c2) { 910 return c1; 911 } 912 // current = current.getParent(); 913 current = getParentClassLoader(current); 914 } 915 916 // scan c2's ancestors to find c1 917 current = c2; 918 while (current != null) { 919 if (current == c1) { 920 return c2; 921 } 922 // current = current.getParent(); 923 current = getParentClassLoader(current); 924 } 925 926 return null; 927 } 928 929 /** 930 * Fetch the parent class loader of a specified class loader. 931 * <p> 932 * If a SecurityException occurs, null is returned. 933 * <p> 934 * Note that this method is non-static merely so logDiagnostic is available. 935 */ 936 private ClassLoader getParentClassLoader(final ClassLoader cl) { 937 try { 938 return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> cl.getParent()); 939 } catch (final SecurityException ex) { 940 logDiagnostic("[SECURITY] Unable to obtain parent class loader"); 941 return null; 942 } 943 944 } 945 946 /** 947 * Generates an internal diagnostic logging of the discovery failure and 948 * then throws a {@code LogConfigurationException} that wraps 949 * the passed {@code Throwable}. 950 * 951 * @param logAdapterClassName is the class name of the Log implementation 952 * that could not be instantiated. Cannot be {@code null}. 953 * @param discoveryFlaw is the Throwable created by the class loader 954 * @throws LogConfigurationException ALWAYS 955 */ 956 private void handleFlawedDiscovery(final String logAdapterClassName, 957 final Throwable discoveryFlaw) { 958 959 if (isDiagnosticsEnabled()) { 960 logDiagnostic("Could not instantiate Log '" + 961 logAdapterClassName + "' -- " + 962 discoveryFlaw.getClass().getName() + ": " + 963 discoveryFlaw.getLocalizedMessage()); 964 965 if (discoveryFlaw instanceof InvocationTargetException ) { 966 // Ok, the lib is there but while trying to create a real underlying 967 // logger something failed in the underlying lib; display info about 968 // that if possible. 969 final InvocationTargetException ite = (InvocationTargetException) discoveryFlaw; 970 final Throwable cause = ite.getTargetException(); 971 if (cause != null) { 972 logDiagnostic("... InvocationTargetException: " + 973 cause.getClass().getName() + ": " + 974 cause.getLocalizedMessage()); 975 976 if (cause instanceof ExceptionInInitializerError) { 977 final ExceptionInInitializerError eiie = (ExceptionInInitializerError) cause; 978 final Throwable cause2 = eiie.getCause(); 979 if (cause2 != null) { 980 final StringWriter sw = new StringWriter(); 981 cause2.printStackTrace(new PrintWriter(sw, true)); 982 logDiagnostic("... ExceptionInInitializerError: " + sw.toString()); 983 } 984 } 985 } 986 } 987 } 988 989 if (!allowFlawedDiscovery) { 990 throw new LogConfigurationException(discoveryFlaw); 991 } 992 } 993 994 /** 995 * Report a problem loading the log adapter, then either return 996 * (if the situation is considered recoverable) or throw a 997 * LogConfigurationException. 998 * <p> 999 * There are two possible reasons why we successfully loaded the 1000 * specified log adapter class then failed to cast it to a Log object: 1001 * <ol> 1002 * <li>the specific class just doesn't implement the Log interface 1003 * (user screwed up), or 1004 * <li> the specified class has bound to a Log class loaded by some other 1005 * class loader; Log@ClassLoaderX cannot be cast to Log@ClassLoaderY. 1006 * </ol> 1007 * <p> 1008 * Here we try to figure out which case has occurred so we can give the 1009 * user some reasonable feedback. 1010 * 1011 * @param badClassLoader is the class loader we loaded the problem class from, 1012 * ie it is equivalent to badClass.getClassLoader(). 1013 * 1014 * @param badClass is a Class object with the desired name, but which 1015 * does not implement Log correctly. 1016 * 1017 * @throws LogConfigurationException when the situation 1018 * should not be recovered from. 1019 */ 1020 private void handleFlawedHierarchy(final ClassLoader badClassLoader, final Class<?> badClass) 1021 throws LogConfigurationException { 1022 1023 boolean implementsLog = false; 1024 final String logInterfaceName = Log.class.getName(); 1025 final Class<?>[] interfaces = badClass.getInterfaces(); 1026 for (final Class<?> element : interfaces) { 1027 if (logInterfaceName.equals(element.getName())) { 1028 implementsLog = true; 1029 break; 1030 } 1031 } 1032 1033 if (implementsLog) { 1034 // the class does implement an interface called Log, but 1035 // it is in the wrong class loader 1036 if (isDiagnosticsEnabled()) { 1037 try { 1038 final ClassLoader logInterfaceClassLoader = getClassLoader(Log.class); 1039 logDiagnostic("Class '" + badClass.getName() + "' was found in class loader " + 1040 objectId(badClassLoader) + ". It is bound to a Log interface which is not" + 1041 " the one loaded from class loader " + objectId(logInterfaceClassLoader)); 1042 } catch (final Throwable t) { 1043 handleThrowable(t); // may re-throw t 1044 logDiagnostic("Error while trying to output diagnostics about" + " bad class '" + badClass + "'"); 1045 } 1046 } 1047 1048 if (!allowFlawedHierarchy) { 1049 final StringBuilder msg = new StringBuilder(); 1050 msg.append("Terminating logging for this context "); 1051 msg.append("due to bad log hierarchy. "); 1052 msg.append("You have more than one version of '"); 1053 msg.append(Log.class.getName()); 1054 msg.append("' visible."); 1055 if (isDiagnosticsEnabled()) { 1056 logDiagnostic(msg.toString()); 1057 } 1058 throw new LogConfigurationException(msg.toString()); 1059 } 1060 1061 if (isDiagnosticsEnabled()) { 1062 final StringBuilder msg = new StringBuilder(); 1063 msg.append("Warning: bad log hierarchy. "); 1064 msg.append("You have more than one version of '"); 1065 msg.append(Log.class.getName()); 1066 msg.append("' visible."); 1067 logDiagnostic(msg.toString()); 1068 } 1069 } else { 1070 // this is just a bad adapter class 1071 if (!allowFlawedDiscovery) { 1072 final StringBuilder msg = new StringBuilder(); 1073 msg.append("Terminating logging for this context. "); 1074 msg.append("Log class '"); 1075 msg.append(badClass.getName()); 1076 msg.append("' does not implement the Log interface."); 1077 if (isDiagnosticsEnabled()) { 1078 logDiagnostic(msg.toString()); 1079 } 1080 1081 throw new LogConfigurationException(msg.toString()); 1082 } 1083 1084 if (isDiagnosticsEnabled()) { 1085 final StringBuilder msg = new StringBuilder(); 1086 msg.append("[WARNING] Log class '"); 1087 msg.append(badClass.getName()); 1088 msg.append("' does not implement the Log interface."); 1089 logDiagnostic(msg.toString()); 1090 } 1091 } 1092 } 1093 1094 /** 1095 * Appends message if the given name is similar to the candidate. 1096 * @param messageBuffer {@code StringBuffer} the message should be appended to, 1097 * not null 1098 * @param name the (trimmed) name to be test against the candidate, not null 1099 * @param candidate the candidate name (not null) 1100 */ 1101 private void informUponSimilarName(final StringBuilder messageBuffer, final String name, 1102 final String candidate) { 1103 if (name.equals(candidate)) { 1104 // Don't suggest a name that is exactly the same as the one the 1105 // user tried... 1106 return; 1107 } 1108 1109 // If the user provides a name that is in the right package, and gets 1110 // the first 5 characters of the adapter class right (ignoring case), 1111 // then suggest the candidate adapter class name. 1112 if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) { 1113 messageBuffer.append(" Did you mean '"); 1114 messageBuffer.append(candidate); 1115 messageBuffer.append("'?"); 1116 } 1117 } 1118 1119 /** 1120 * Initialize a number of variables that control the behavior of this 1121 * class and that can be tweaked by the user. This is done when the first 1122 * logger is created, not in the constructor of this class, because we 1123 * need to give the user a chance to call method setAttribute in order to 1124 * configure this object. 1125 */ 1126 private void initConfiguration() { 1127 allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true); 1128 allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true); 1129 allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true); 1130 } 1131 1132 /** 1133 * Calculate and cache a string that uniquely identifies this instance, 1134 * including which class loader the object was loaded from. 1135 * <p> 1136 * This string will later be prefixed to each "internal logging" message 1137 * emitted, so that users can clearly see any unexpected behavior. 1138 * <p> 1139 * Note that this method does not detect whether internal logging is 1140 * enabled or not, nor where to output stuff if it is; that is all 1141 * handled by the parent LogFactory class. This method just computes 1142 * its own unique prefix for log messages. 1143 */ 1144 private void initDiagnostics() { 1145 // It would be nice to include an identifier of the context class loader 1146 // that this LogFactoryImpl object is responsible for. However that 1147 // isn't possible as that information isn't available. It is possible 1148 // to figure this out by looking at the logging from LogFactory to 1149 // see the context & impl ids from when this object was instantiated, 1150 // in order to link the impl id output as this object's prefix back to 1151 // the context it is intended to manage. 1152 // Note that this prefix should be kept consistent with that 1153 // in LogFactory. 1154 @SuppressWarnings("unchecked") 1155 final Class<LogFactoryImpl> clazz = (Class<LogFactoryImpl>) this.getClass(); 1156 final ClassLoader classLoader = getClassLoader(clazz); 1157 String classLoaderName; 1158 try { 1159 if (classLoader == null) { 1160 classLoaderName = "BOOTLOADER"; 1161 } else { 1162 classLoaderName = objectId(classLoader); 1163 } 1164 } catch (final SecurityException e) { 1165 classLoaderName = "UNKNOWN"; 1166 } 1167 diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] "; 1168 } 1169 1170 /** 1171 * Tests whether <em>JDK 1.3 with Lumberjack</em> logging available. 1172 * 1173 * @return whether <em>JDK 1.3 with Lumberjack</em> logging available. 1174 * @deprecated Never invoked by this class; subclasses should not assume it will be. 1175 */ 1176 @Deprecated 1177 protected boolean isJdk13LumberjackAvailable() { 1178 return isLogLibraryAvailable( 1179 "Jdk13Lumberjack", 1180 "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); 1181 } 1182 1183 /** 1184 * Tests {@code true} whether <em>JDK 1.4 or later</em> logging is available. Also checks that the {@code Throwable} class supports {@code getStackTrace()}, 1185 * which is required by Jdk14Logger. 1186 * 1187 * @return Whether <em>JDK 1.4 or later</em> logging is available. 1188 * @deprecated Never invoked by this class; subclasses should not assume it will be. 1189 */ 1190 @Deprecated 1191 protected boolean isJdk14Available() { 1192 return isLogLibraryAvailable("Jdk14", "org.apache.commons.logging.impl.Jdk14Logger"); 1193 } 1194 1195 /** 1196 * Tests whether a <em>Log4J</em> implementation available. 1197 * 1198 * @return whether a <em>Log4J</em> implementation available. 1199 * @deprecated Never invoked by this class; subclasses should not assume it will be. 1200 */ 1201 @Deprecated 1202 protected boolean isLog4JAvailable() { 1203 return isLogLibraryAvailable("Log4J", LOGGING_IMPL_LOG4J_LOGGER); 1204 } 1205 1206 /** 1207 * Utility method to check whether a particular logging library is 1208 * present and available for use. Note that this does <em>not</em> 1209 * affect the future behavior of this class. 1210 */ 1211 private boolean isLogLibraryAvailable(final String name, final String className) { 1212 if (isDiagnosticsEnabled()) { 1213 logDiagnostic("Checking for '" + name + "'."); 1214 } 1215 try { 1216 final Log log = createLogFromClass( 1217 className, 1218 this.getClass().getName(), // dummy category 1219 false); 1220 1221 if (log == null) { 1222 if (isDiagnosticsEnabled()) { 1223 logDiagnostic("Did not find '" + name + "'."); 1224 } 1225 return false; 1226 } 1227 if (isDiagnosticsEnabled()) { 1228 logDiagnostic("Found '" + name + "'."); 1229 } 1230 return true; 1231 } catch (final LogConfigurationException e) { 1232 if (isDiagnosticsEnabled()) { 1233 logDiagnostic("Logging system '" + name + "' is available but not useable."); 1234 } 1235 return false; 1236 } 1237 } 1238 1239 /** 1240 * Output a diagnostic message to a user-specified destination (if the 1241 * user has enabled diagnostic logging). 1242 * 1243 * @param msg diagnostic message 1244 * @since 1.1 1245 */ 1246 protected void logDiagnostic(final String msg) { 1247 if (isDiagnosticsEnabled()) { 1248 logRawDiagnostic(diagnosticPrefix + msg); 1249 } 1250 } 1251 1252 /** 1253 * Create and return a new {@link org.apache.commons.logging.Log} instance for the specified name. 1254 * 1255 * @param name Name of the new logger 1256 * @return a new {@link org.apache.commons.logging.Log} 1257 * @throws LogConfigurationException if a new instance cannot be created 1258 */ 1259 protected Log newInstance(final String name) throws LogConfigurationException { 1260 Log instance; 1261 try { 1262 if (logConstructor == null) { 1263 instance = discoverLogImplementation(name); 1264 } 1265 else { 1266 final Object[] params = { name }; 1267 instance = (Log) logConstructor.newInstance(params); 1268 } 1269 1270 if (logMethod != null) { 1271 final Object[] params = { this }; 1272 logMethod.invoke(instance, params); 1273 } 1274 1275 return instance; 1276 1277 } catch (final LogConfigurationException lce) { 1278 1279 // this type of exception means there was a problem in discovery 1280 // and we've already output diagnostics about the issue, etc.; 1281 // just pass it on 1282 throw lce; 1283 1284 } catch (final InvocationTargetException e) { 1285 // A problem occurred invoking the Constructor or Method 1286 // previously discovered 1287 final Throwable c = e.getTargetException(); 1288 throw new LogConfigurationException(c == null ? e : c); 1289 } catch (final Throwable t) { 1290 handleThrowable(t); // may re-throw t 1291 // A problem occurred invoking the Constructor or Method 1292 // previously discovered 1293 throw new LogConfigurationException(t); 1294 } 1295 } 1296 1297 /** 1298 * Release any internal references to previously created 1299 * {@link org.apache.commons.logging.Log} 1300 * instances returned by this factory. This is useful in environments 1301 * like servlet containers, which implement application reloading by 1302 * throwing away a ClassLoader. Dangling references to objects in that 1303 * class loader would prevent garbage collection. 1304 */ 1305 @Override 1306 public void release() { 1307 1308 logDiagnostic("Releasing all known loggers"); 1309 instances.clear(); 1310 } 1311 1312 /** 1313 * Remove any configuration attribute associated with the specified name. 1314 * If there is no such attribute, no action is taken. 1315 * 1316 * @param name Name of the attribute to remove 1317 */ 1318 @Override 1319 public void removeAttribute(final String name) { 1320 attributes.remove(name); 1321 } 1322 1323 /** 1324 * Sets the configuration attribute with the specified name. Calling 1325 * this with a {@code null} value is equivalent to calling 1326 * {@code removeAttribute(name)}. 1327 * <p> 1328 * This method can be used to set logging configuration programmatically 1329 * rather than via system properties. It can also be used in code running 1330 * within a container (such as a webapp) to configure behavior on a 1331 * per-component level instead of globally as system properties would do. 1332 * To use this method instead of a system property, call 1333 * <pre> 1334 * LogFactory.getFactory().setAttribute(...) 1335 * </pre> 1336 * This must be done before the first Log object is created; configuration 1337 * changes after that point will be ignored. 1338 * <p> 1339 * This method is also called automatically if LogFactory detects a 1340 * commons-logging.properties file; every entry in that file is set 1341 * automatically as an attribute here. 1342 * 1343 * @param name Name of the attribute to set 1344 * @param value Value of the attribute to set, or {@code null} 1345 * to remove any setting for this attribute 1346 */ 1347 @Override 1348 public void setAttribute(final String name, final Object value) { 1349 if (logConstructor != null) { 1350 logDiagnostic("setAttribute: call too late; configuration already performed."); 1351 } 1352 1353 if (value == null) { 1354 attributes.remove(name); 1355 } else { 1356 attributes.put(name, value); 1357 } 1358 1359 if (name.equals(TCCL_KEY)) { 1360 useTCCL = value != null && Boolean.parseBoolean(value.toString()); 1361 } 1362 } 1363}