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
018 package org.apache.commons.logging.impl;
019
020 import java.io.PrintWriter;
021 import java.io.StringWriter;
022 import java.lang.reflect.Constructor;
023 import java.lang.reflect.InvocationTargetException;
024 import java.lang.reflect.Method;
025 import java.net.URL;
026 import java.security.AccessController;
027 import java.security.PrivilegedAction;
028 import java.util.Hashtable;
029
030 import org.apache.commons.logging.Log;
031 import org.apache.commons.logging.LogConfigurationException;
032 import 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 862526 2013-05-20 17:07:42Z tn $
063 */
064 public 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 }