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