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