View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.logging.impl;
19  
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.net.URL;
26  import java.security.AccessController;
27  import java.security.PrivilegedAction;
28  import java.util.Hashtable;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogConfigurationException;
32  import org.apache.commons.logging.LogFactory;
33  
34  /**
35   * Concrete subclass of {@link LogFactory} that implements the
36   * following algorithm to dynamically select a logging implementation
37   * class to instantiate a wrapper for:
38   * <ul>
39   * <li>Use a factory configuration attribute named
40   *     {@code org.apache.commons.logging.Log} to identify the
41   *     requested implementation class.</li>
42   * <li>Use the {@code org.apache.commons.logging.Log} system property
43   *     to identify the requested implementation class.</li>
44   * <li>If <em>Log4J</em> is available, return an instance of
45   *     {@code org.apache.commons.logging.impl.Log4JLogger}.</li>
46   * <li>If <em>JDK 1.4 or later</em> is available, return an instance of
47   *     {@code org.apache.commons.logging.impl.Jdk14Logger}.</li>
48   * <li>Otherwise, return an instance of
49   *     {@code org.apache.commons.logging.impl.SimpleLog}.</li>
50   * </ul>
51   * <p>
52   * If the selected {@link Log} implementation class has a
53   * <code>setLogFactory()</code> method that accepts a {@link LogFactory}
54   * parameter, this method will be called on each newly created instance
55   * to identify the associated factory.  This makes factory configuration
56   * attributes available to the Log instance, if it so desires.
57   * <p>
58   * This factory will remember previously created {@code Log} instances
59   * for the same name, and will return them on repeated requests to the
60   * {@code getInstance()} method.
61   */
62  public class LogFactoryImpl extends LogFactory {
63  
64      /** Log4JLogger class name */
65      private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
66      /** Jdk14Logger class name */
67      private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger";
68      /** Jdk13LumberjackLogger class name */
69      private static final String LOGGING_IMPL_LUMBERJACK_LOGGER =
70              "org.apache.commons.logging.impl.Jdk13LumberjackLogger";
71  
72      /** SimpleLog class name */
73      private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog";
74  
75      private static final String PKG_IMPL="org.apache.commons.logging.impl.";
76      private static final int PKG_LEN = PKG_IMPL.length();
77  
78      /**
79       * An empty immutable {@code String} array.
80       */
81      private static final String[] EMPTY_STRING_ARRAY = {};
82  
83      /**
84       * The name ({@code org.apache.commons.logging.Log}) of the system
85       * property identifying our {@link Log} implementation class.
86       */
87      public static final String LOG_PROPERTY = "org.apache.commons.logging.Log";
88  
89      /**
90       * The deprecated system property used for backwards compatibility with
91       * old versions of JCL.
92       */
93      protected static final String LOG_PROPERTY_OLD = "org.apache.commons.logging.log";
94  
95      /**
96       * The name ({@code org.apache.commons.logging.Log.allowFlawedContext})
97       * of the system property which can be set true/false to
98       * determine system behavior when a bad context class loader is encountered.
99       * 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 <i>and</i> 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 <i>not</i>
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 }