You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by sk...@apache.org on 2005/05/22 12:43:08 UTC

svn commit: r171300 - /jakarta/commons/proper/logging/trunk/src/java/org/apache/commons/logging/LogFactory.java

Author: skitching
Date: Sun May 22 03:43:06 2005
New Revision: 171300

URL: http://svn.apache.org/viewcvs?rev=171300&view=rev
Log:
Added internal diagnostics

Modified:
    jakarta/commons/proper/logging/trunk/src/java/org/apache/commons/logging/LogFactory.java

Modified: jakarta/commons/proper/logging/trunk/src/java/org/apache/commons/logging/LogFactory.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/logging/trunk/src/java/org/apache/commons/logging/LogFactory.java?rev=171300&r1=171299&r2=171300&view=diff
==============================================================================
--- jakarta/commons/proper/logging/trunk/src/java/org/apache/commons/logging/LogFactory.java (original)
+++ jakarta/commons/proper/logging/trunk/src/java/org/apache/commons/logging/LogFactory.java Sun May 22 03:43:06 2005
@@ -21,6 +21,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.io.FileOutputStream;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.security.AccessController;
@@ -80,6 +82,34 @@
         "META-INF/services/org.apache.commons.logging.LogFactory";
 
     /**
+     * The name of the property used to enable internal commons-logging
+     * diagnostic output, in order to get information on what logging
+     * implementations are being discovered, what classloaders they 
+     * are loaded through, etc.
+     * <p>
+     * If a system property of this name is set then the value is
+     * assumed to be the name of a file. The special strings
+     * STDOUT or STDERR (case-sensitive) indicate output to
+     * System.out and System.err respectively.
+     */
+    public static final String DIAGNOSTICS_DEST_PROPERTY =
+        "org.apache.commons.logging.diagnostics.dest";
+
+    /**
+     * When null (the usual case), no diagnostic output will be
+     * generated by LogFactory or LogFactoryImpl. When non-null,
+     * interesting events will be written to the specified object.
+     */
+    private static PrintStream diagnosticsStream = null;
+    
+    /**
+     * A string that gets prefixed to every message output by the
+     * logDiagnostic method, so that users can clearly see which
+     * LogFactory class is generating the output.
+     */
+    private static String diagnosticPrefix;
+    
+    /**
      * <p>Setting this system property value allows the <code>Hashtable</code> used to store
      * classloaders to be substituted by an alternative implementation.
      * </p>
@@ -133,10 +163,9 @@
     /**
      * Protected constructor that is not available for public use.
      */
-    protected LogFactory() { 
+    protected LogFactory() {
     }
-
-
+    
     // --------------------------------------------------------- Public Methods
 
 
@@ -247,6 +276,21 @@
      */
     protected static LogFactory nullClassLoaderFactory = null;
 
+    /**
+     * Create the hashtable which will be used to store a map of
+     * (context-classloader -> logfactory-object). Version 1.2+ of Java
+     * supports "weak references", allowing a custom Hashtable class
+     * to be used which uses only weak references to its keys. Using weak
+     * references can fix memory leaks on webapp unload in some cases (though
+     * not all). Version 1.1 of Java does not support weak references, so we
+     * must dynamically determine which we are using. And just for fun, this
+     * code also supports the ability for a system property to specify an
+     * arbitrary Hashtable implementation name.
+     * <p>
+     * Note that the correct way to ensure no memory leaks occur is to ensure
+     * that LogFactory.release(contextClassLoader) is called whenever a 
+     * webapp is undeployed.
+     */
     private static final Hashtable createFactoryStore() {
         Hashtable result = null;
         String storeImplementationClass 
@@ -260,9 +304,16 @@
             
         } catch (Throwable t) {
             // ignore
-        	if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) {
-        		// if the user's trying to set up a custom implementation, give a clue
-        		System.err.println("[ERROR] LogFactory: Load of custom hashtable failed");
+            if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) {
+        	    // if the user's trying to set up a custom implementation, give a clue
+        	    if (isDiagnosticsEnabled()) {
+        	        // use internal logging to issue the warning
+        	        logDiagnostic("[ERROR] LogFactory: Load of custom hashtable failed");
+        		} else {
+        		    // we *really* want this output, even if diagnostics weren't
+                    // explicitly enabled by the user.
+        		    System.err.println("[ERROR] LogFactory: Load of custom hashtable failed");
+        		}
         	}
         }
         if (result == null) {
@@ -305,13 +356,32 @@
         // Identify the class loader we will be using
         ClassLoader contextClassLoader = getContextClassLoader();
 
+        if (contextClassLoader == null) {
+            // This is an odd enough situation to report about. This
+            // output will be a nuisance on JDK1.1, as the system
+            // classloader is null in that environment.
+            logDiagnostic("Context classloader is null.");
+        }
+
         // Return any previously registered factory for this class loader
         LogFactory factory = getCachedFactory(contextClassLoader);
-        if (factory != null)
+        if (factory != null) {
             return factory;
+        }
+
+        logDiagnostic(
+            "LogFactory requested for the first time for context classloader "
+            + objectId(contextClassLoader));
 
         // Load properties file.
-        // Will be used one way or another in the end.
+        //
+        // If the properties file exists, then its contents are used as
+        // "attributes" on the LogFactory implementation class. One particular
+        // property may also control which LogFactory concrete subclass is
+        // used, but only if other discovery mechanisms fail..
+        //
+        // As the properties file (if it exists) will be used one way or 
+        // another in the end we may as well look for it first.
 
         Properties props=null;
         try {
@@ -324,28 +394,47 @@
                 stream.close();
             }
         } catch (IOException e) {
+            ; // ignore
         } catch (SecurityException e) {
+            ; // ignore
         }
 
 
-        // First, try the system property
+        // First, try a global system property
+        logDiagnostic(
+            "Looking for system property [" + FACTORY_PROPERTY 
+            + "] to define the LogFactory subclass to use..");
+
         try {
             String factoryClass = System.getProperty(FACTORY_PROPERTY);
             if (factoryClass != null) {
-                factory = newFactory(factoryClass, contextClassLoader);
+                logDiagnostic(
+                    "Creating an instance of LogFactory class " + factoryClass
+                    + " as specified by system property " + FACTORY_PROPERTY);
+
+                factory = newFactory(factoryClass, contextClassLoader, contextClassLoader);
             }
         } catch (SecurityException e) {
+            logDiagnostic(
+                "A security exception occurred while trying to create an"
+                + " instance of the custom factory class"
+                + ": [" + e.getMessage().trim()
+                + "]. Trying alternative implementations..");
             ;  // ignore
         }
 
 
-        // Second, try to find a service by using the JDK1.3 jar
-        // discovery mechanism. This will allow users to plug a logger
-        // by just placing it in the lib/ directory of the webapp ( or in
-        // CLASSPATH or equivalent ). This is similar to the second
-        // step, except that it uses the (standard?) jdk1.3 location in the jar.
+        // Second, try to find a service by using the JDK1.3 class
+        // discovery mechanism, which involves putting a file with the name
+        // of an interface class in the META-INF/services directory, where the
+        // contents of the file is a single line specifying a concrete class 
+        // that implements the desired interface.
 
         if (factory == null) {
+            logDiagnostic(
+                "Looking for a resource file of name [" + SERVICE_ID
+                + "] to define the LogFactory subclass to use..");
+
             try {
                 InputStream is = getResourceAsStream(contextClassLoader,
                                                      SERVICE_ID);
@@ -366,11 +455,23 @@
                     if (factoryClassName != null &&
                         ! "".equals(factoryClassName)) {
 
-                        factory= newFactory( factoryClassName, contextClassLoader );
+                        logDiagnostic(
+                            "Creating an instance of LogFactory class " + factoryClassName
+                            + " as specified by file " + SERVICE_ID 
+                            + " which was present in the path of the context"
+                            + " classloader.");
+
+                        factory = newFactory( factoryClassName, contextClassLoader, contextClassLoader );
                     }
                 }
             } catch( Exception ex ) {
-                ;
+                logDiagnostic(
+                        "A security exception occurred while trying to create an"
+                        + " instance of the custom factory class"
+                        + ": [" + ex.getMessage().trim()
+                        + "]. Trying alternative implementations..");
+
+                ; // ignore
             }
         }
 
@@ -383,10 +484,23 @@
         // the webapp, even if a default logger is set at JVM level by a
         // system property )
 
-        if (factory == null  &&  props != null) {
-            String factoryClass = props.getProperty(FACTORY_PROPERTY);
-            if (factoryClass != null) {
-                factory = newFactory(factoryClass, contextClassLoader);
+        if (factory == null) {
+            logDiagnostic(
+                "Looking for a properties file of name " + FACTORY_PROPERTIES
+                + " to define the LogFactory subclass to use..");
+
+            if (props != null) {
+                logDiagnostic(
+                        "Properties file found. Looking for property " 
+                        + FACTORY_PROPERTY
+                        + " to define the LogFactory subclass to use..");
+
+                String factoryClass = props.getProperty(FACTORY_PROPERTY);
+                if (factoryClass != null) {
+                    factory = newFactory(factoryClass, contextClassLoader, contextClassLoader);
+                    
+                    // what about handling an exception from newFactory??
+                }
             }
         }
 
@@ -394,8 +508,21 @@
         // Fourth, try the fallback implementation class
 
         if (factory == null) {
+            logDiagnostic(
+                "Loading the default LogFactory implementation " + FACTORY_DEFAULT
+                + " via the same classloader that loaded this LogFactory"
+                + " class (ie not looking in the context classloader).");
+            
+            // Note: we don't try to load the LogFactory implementation
+            // via the context classloader here because:
+            // * that can cause problems (see comments in newFactory method)
+            // * no-one should be customising the code of the default class
+            // Yes, we do give up the ability for the child to ship a newer
+            // version of the LogFactoryImpl class and have it used dynamically
+            // by an old LogFactory class in the parent, but that isn't 
+            // necessarily a good idea anyway.
             ClassLoader logFactoryClassLoader = getClassLoader(LogFactory.class);
-            factory = newFactory(FACTORY_DEFAULT, logFactoryClassLoader);
+            factory = newFactory(FACTORY_DEFAULT, logFactoryClassLoader, contextClassLoader);
         }
 
         if (factory != null) {
@@ -464,6 +591,7 @@
      */
     public static void release(ClassLoader classLoader) {
 
+        logDiagnostic("Releasing factory for classloader " + objectId(classLoader));
         synchronized (factories) {
             if (classLoader == null) {
                 if (nullClassLoaderFactory != null) {
@@ -492,6 +620,7 @@
      */
     public static void releaseAll() {
 
+        logDiagnostic("Releasing factory for all classloaders.");
         synchronized (factories) {
             Enumeration elements = factories.elements();
             while (elements.hasMoreElements()) {
@@ -511,19 +640,16 @@
 
     // ------------------------------------------------------ Protected Methods
 
-
     /**
      * Safely get access to the classloader for the specified class.
      * <p>
-     * Theoretically, calling Class.getClassLoader can throw a security 
-     * exception, and so should be done under an AccessController in order
-     * to provide maximum flexibility. 
-     * <p>
-     * However in practice people don't appear to use security policies that 
-     * forbid getClassLoader calls, so for the moment this method doesn't
-     * bother to actually do that. As all code is written to call this 
-     * method rather than Class.getClassLoader, AccessController stuff could
-     * be put in this method without any disruption later if needed.
+     * Theoretically, calling getClassLoader can throw a security exception,
+     * and so should be done under an AccessController in order to provide
+     * maximum flexibility. However in practice people don't appear to use
+     * security policies that forbid getClassLoader calls. So for the moment
+     * all code is written to call this method rather than Class.getClassLoader,
+     * so that we could put AccessController stuff in this method without any
+     * disruption later if we need to.
      * <p>
      * Even when using an AccessController, however, this method can still
      * throw SecurityException. Commons-logging basically relies on the
@@ -539,10 +665,13 @@
         try {
             return clazz.getClassLoader();
         } catch(SecurityException ex) {
+            logDiagnostic(
+                "Unable to get classloader for class " + clazz
+                + " due to security restrictions.");
             throw ex;
         }
     }
-    
+
     /**
      * Calls LogFactory.directGetContextClassLoader under the control of an
      * AccessController class. This means that java code running under a
@@ -637,6 +766,19 @@
         } catch (NoSuchMethodException e) {
             // Assume we are running on JDK 1.1
             classLoader = getClassLoader(LogFactory.class);
+
+            // We deliberately don't log a message here to outputStream;
+            // this message would be output for every call to LogFactory.getLog()
+            // when running on JDK1.1
+            //
+            // if (outputStream != null) {
+            //    outputStream.println(
+            //        "Method Thread.getContextClassLoader does not exist;"
+            //         + " assuming this is JDK 1.1, and that the context"
+            //         + " classloader is the same as the class that loaded"
+            //         + " the concrete LogFactory class.");
+            // }
+            
         }
 
         // Return the selected class loader
@@ -646,6 +788,13 @@
     /**
      * Check cached factories (keyed by contextClassLoader)
      *
+     * @param contextClassLoader is the context classloader associated
+     * with the current thread. This allows separate LogFactory objects
+     * per component within a container, provided each component has
+     * a distinct context classloader set. This parameter may be null
+     * in JDK1.1, and in embedded systems where jcl-using code is
+     * placed in the bootclasspath.
+     * 
      * @return the factory associated with the specified classloader if
      * one has previously been created, or null if this is the first time
      * we have seen this particular classloader.
@@ -655,6 +804,9 @@
         LogFactory factory = null;
 
         if (contextClassLoader == null) {
+            // We have to handle this specially, as factories is a Hashtable
+            // and those don't accept null as a key value.
+            //
             // nb: nullClassLoaderFactory might be null. That's ok.
             factory = nullClassLoaderFactory;
         } else {
@@ -693,7 +845,36 @@
      * implementation class, loaded by the specified class loader.
      * If that fails, try the class loader used to load this
      * (abstract) LogFactory.
-     *
+     * <p>
+     * <h2>ClassLoader conflicts</h2>
+     * Note that there can be problems if the specified ClassLoader is not the 
+     * same as the classloader that loaded this class, ie when loading a
+     * concrete LogFactory subclass via a context classloader.
+     * <p>
+     * The problem is the same one that can occur when loading a concrete Log
+     * subclass via a context classloader.
+     * <p>
+     * The problem occurs when code running in the context classloader calls
+     * class X which was loaded via a parent classloader, and class X then calls
+     * LogFactory.getFactory (either directly or via LogFactory.getLog). Because
+     * class X was loaded via the parent, it binds to LogFactory loaded via
+     * the parent. When the code in this method finds some LogFactoryYYYY
+     * class in the child (context) classloader, and there also happens to be a
+     * LogFactory class defined in the child classloader, then LogFactoryYYYY
+     * will be bound to LogFactory@childloader. It cannot be cast to
+     * LogFactory@parentloader, ie this method cannot return the object as
+     * the desired type. Note that it doesn't matter if the LogFactory class
+     * in the child classloader is identical to the LogFactory class in the
+     * parent classloader, they are not compatible.
+     * <p>
+     * The solution taken here is to simply print out an error message when
+     * this occurs then throw an exception. The deployer of the application
+     * must ensure they remove all occurrences of the LogFactory class from
+     * the child classloader in order to resolve the issue. Note that they
+     * do not have to move the custom LogFactory subclass; that is ok as
+     * long as the only LogFactory class it can find to bind to is in the
+     * parent classloader.
+     * <p>
      * @param factoryClass Fully qualified name of the <code>LogFactory</code>
      *  implementation class
      * @param classLoader ClassLoader from which to load this class
@@ -702,7 +883,8 @@
      *  cannot be created
      */
     protected static LogFactory newFactory(final String factoryClass,
-                                           final ClassLoader classLoader)
+                                           final ClassLoader classLoader,
+                                           final ClassLoader contextClassLoader)
         throws LogConfigurationException
     {
         Object result = AccessController.doPrivileged(
@@ -712,8 +894,17 @@
                 }
             });
 
-        if (result instanceof LogConfigurationException)
-            throw (LogConfigurationException)result;
+        if (result instanceof LogConfigurationException) {
+            LogConfigurationException ex = (LogConfigurationException) result;
+            logDiagnostic(
+                "An error occurred while loading the factory class:"
+                + ex.getMessage());
+            throw ex;
+        }
+
+        logDiagnostic(
+            "Created object " + objectId(result)
+            + " to manage classloader " + objectId(contextClassLoader));
 
         return (LogFactory)result;
     }
@@ -739,47 +930,80 @@
                     // Warning: must typecast here & allow exception
                     // to be generated/caught & recast properly.
                     logFactoryClass = classLoader.loadClass(factoryClass);
+                    if (LogFactory.class.isAssignableFrom(logFactoryClass)) {
+                        logDiagnostic(
+                            "loaded class " + logFactoryClass.getName()
+                            + " from classloader " + objectId(classLoader));
+                    } else {
+                        logDiagnostic(
+                            "Factory class " + logFactoryClass.getName()
+                            + " loaded from classloader " + objectId(classLoader)
+                            + " does not extend " + LogFactory.class.getName()
+                            + " as loaded by this classloader.");
+                    }
+                    
                     return (LogFactory) logFactoryClass.newInstance();
 
                 } catch (ClassNotFoundException ex) {
                     if (classLoader == thisClassLoader) {
                         // Nothing more to try, onwards.
+                        logDiagnostic(
+                            "Unable to locate any class called " + factoryClass
+                            + " via classloader " + objectId(classLoader));
                         throw ex;
                     }
                     // ignore exception, continue
                 } catch (NoClassDefFoundError e) {
                     if (classLoader == thisClassLoader) {
                         // Nothing more to try, onwards.
+                        logDiagnostic(
+                            "Class " + factoryClass + " cannot be loaded"
+                            + " via classloader " + objectId(classLoader)
+                            + ": it depends on some other class that cannot"
+                            + " be found.");
                         throw e;
                     }
-
-                } catch(ClassCastException e){
-
-                  if (classLoader == thisClassLoader) {
-                        // Nothing more to try, onwards (bug in loader implementation).
+                    // ignore exception, continue
+                } catch(ClassCastException e) {
+                    if (classLoader == thisClassLoader) {
+                        // This cast exception is not due to classloader issues;
+                        // the specified class *really* doesn't extend the 
+                        // required LogFactory base class.
+                        logDiagnostic(
+                            "Class " + factoryClass + " really does not extend "
+                            + LogFactory.class.getName());
                         throw e;
-                   }
+                    }
+                    // Ignore exception, continue
                 }
-                // Ignore exception, continue
             }
 
             /* At this point, either classLoader == null, OR
              * classLoader was unable to load factoryClass.
-             * Try the class loader that loaded this class:
-             * LogFactory.getClassLoader().
+             *
+             * In either case, we call Class.forName, which is equivalent
+             * to LogFactory.class.getClassLoader.load(name), ie we ignore
+             * the classloader parameter the caller passed, and fall back
+             * to trying the classloader associated with this class. See the
+             * javadoc for the newFactory method for more info on the 
+             * consequences of this.
              *
              * Notes:
-             * a) LogFactory.class.getClassLoader() may return 'null'
-             *    if LogFactory is loaded by the bootstrap classloader.
-             * b) The Java endorsed library mechanism is instead
-             *    Class.forName(factoryClass);
+             * * LogFactory.class.getClassLoader() may return 'null'
+             *   if LogFactory is loaded by the bootstrap classloader.
              */
             // Warning: must typecast here & allow exception
             // to be generated/caught & recast properly.
+            
+            logDiagnostic(
+                "Unable to load factory class via classloader " 
+                + objectId(classLoader)
+                + ": trying the classloader associated with this LogFactory.");
             logFactoryClass = Class.forName(factoryClass);
             return (LogFactory) logFactoryClass.newInstance();
         } catch (Exception e) {
             // Check to see if we've got a bad configuration
+            logDiagnostic("Unable to create logfactory instance.");
             if (logFactoryClass != null
                 && !LogFactory.class.isAssignableFrom(logFactoryClass)) {
                 return new LogConfigurationException(
@@ -805,6 +1029,202 @@
                 }
             });
     }
+
+    /**
+     * Determines whether the user wants internal diagnostic output. If so,
+     * returns an appropriate writer object. Users can enable diagnostic
+     * output by setting the system property named OUTPUT_PROPERTY to
+     * a filename, or the special values STDOUT or STDERR. 
+     */
+    private static void initDiagnostics() {
+        String dest;
+    	try {
+    	    dest = System.getProperty(DIAGNOSTICS_DEST_PROPERTY);
+    	    if (dest == null) {
+    	        return;
+    	    }
+    	} catch(SecurityException ex) {
+    	    // We must be running in some very secure environment.
+    	    // We just have to assume output is not wanted..
+    	    return;
+    	}
+    	
+    	if (dest.equals("STDOUT")) {
+    	    diagnosticsStream = System.out;
+    	} else if (dest.equals("STDERR")) {
+    	    diagnosticsStream = System.err;
+    	} else {
+    	    try {
+                // open the file in append mode
+    	        FileOutputStream fos = new FileOutputStream(dest, true);
+    	        diagnosticsStream = new PrintStream(fos);
+    	    } catch(IOException ex) {
+    	        // We should report this to the user - but how?
+    	        return;
+    	    }
+    	}
+
+        // In order to avoid confusion where multiple instances of JCL are
+        // being used via different classloaders within the same app, we
+        // ensure each logged message has a prefix of form
+        //   LogFactory@12345: 
+        Class clazz = LogFactory.class;
+        String classLoaderName;
+        try {
+            ClassLoader classLoader = thisClassLoader;
+            if (thisClassLoader == null) {
+                classLoaderName = "BOOTLOADER";
+            } else {
+                classLoaderName = String.valueOf(System.identityHashCode(classLoader));
+            }
+        } catch(SecurityException e) {
+            classLoaderName = "UNKNOWN";
+        }
+        diagnosticPrefix = 
+            clazz.getName() + "@" + classLoaderName + ": ";
+    }
+
+    /**
+     * Indicates true if the user has enabled internal logging.
+     * <p>
+     * By the way, sorry for the incorrect grammar, but calling this method
+     * areDiagnosticsEnabled just isn't java beans style.
+     * 
+     * @return true if calls to logDiagnostic will have any effect.
+     */
+    protected static boolean isDiagnosticsEnabled() {
+        return diagnosticsStream != null;
+    }
+
+    /**
+     * Write the specified message to the internal logging destination.
+     * <p>
+     * Note that this method is private; concrete subclasses of this class
+     * should not call it because the diagnosticPrefix string this
+     * method puts in front of all its messages is LogFactory@....,
+     * while subclasses should put SomeSubClass@...
+     * <p>
+     * Subclasses should instead compute their own prefix, then call
+     * logRawDiagnostic. Note that calling isDiagnosticsEnabled is
+     * fine for subclasses.
+     * <p>
+     * Note that it is safe to call this method before initDiagnostics
+     * is called; any output will just be ignored (as isDiagnosticsEnabled
+     * will return false).
+     * 
+     * @param msg is the diagnostic message to be output.
+     */
+    private static final void logDiagnostic(String msg) {
+        if (diagnosticsStream != null) {
+            diagnosticsStream.print(diagnosticPrefix);
+            diagnosticsStream.println(msg);
+            diagnosticsStream.flush();
+        }
+    }
+
+    /**
+     * Write the specified message to the internal logging destination.
+     * 
+     * @param msg is the diagnostic message to be output.
+     */
+    protected static final void logRawDiagnostic(String msg) {
+        if (diagnosticsStream != null) {
+            diagnosticsStream.println(msg);
+            diagnosticsStream.flush();
+        }
+    }
+
+    /**
+     * Generate useful diagnostics regarding the classloader tree for
+     * the specified class.
+     * <p>
+     * As an example, if the specified class was loaded via a webapp's
+     * classloader, then you may get the following output:
+     * <pre>
+     * Class com.acme.Foo was loaded via classloader 11111
+     * ClassLoader tree: 11111 -> 22222 (SYSTEM) -> 33333 -> BOOT 
+     * </pre>
+     * <p>
+     * This method returns immediately if isDiagnosticsEnabled()
+     * returns false.
+     * 
+     * @param clazz is the class whose classloader + tree are to be
+     * output.
+     */
+    private static void logClassLoaderTree(Class clazz) {
+        if (!isDiagnosticsEnabled()) {
+            return;
+        }
+        
+        String className = clazz.getName();
+        ClassLoader classLoader;
+        ClassLoader systemClassLoader;
+        
+        try {
+            classLoader = getClassLoader(clazz);
+        } catch(SecurityException ex) {
+            // not much useful diagnostics we can print here!
+            logDiagnostic(
+                "Security forbids determining the classloader for " + className);
+            return;
+        }
+
+        logDiagnostic(
+            "Class " + className + " was loaded via classloader "
+            + objectId(classLoader));
+            
+        try {
+            systemClassLoader = ClassLoader.getSystemClassLoader();
+        } catch(SecurityException ex) {
+            logDiagnostic(
+                "Security forbids determining the system classloader.");
+            return;
+        }
+
+        if (classLoader != null) {
+            StringBuffer buf = new StringBuffer("ClassLoader tree:");
+            for(;;) {
+                buf.append(objectId(classLoader));
+                if (classLoader == systemClassLoader) {
+                    buf.append(" (SYSTEM) ");
+                }
+
+                try {
+                    classLoader = classLoader.getParent();
+                } catch(SecurityException ex) {
+                    buf.append(" --> SECRET");
+                    break;
+                }
+
+                buf.append(" --> ");
+                if (classLoader == null) {
+                    buf.append("BOOT");
+                    break;
+                }
+            }
+            logDiagnostic(buf.toString());
+        }
+    }
+
+    /**
+     * Returns a string that uniquely identifies the specified object, including
+     * its class.
+     * <p>
+     * The returned string is of form "classname@hashcode", ie is the same as
+     * the return value of the Object.toString() method, but works even when
+     * the specified object's class has overidden the toString method.
+     * 
+     * @param o may be null.
+     * @return
+     */
+    public static String objectId(Object o) {
+        if (o == null) {
+            return "null";
+        } else {
+            return o.getClass().getName() + "@" + System.identityHashCode(o);
+        }
+    }
+
     // ----------------------------------------------------------------------
     // Static initialiser block to perform initialisation at class load time.
     //
@@ -825,8 +1245,11 @@
     // ----------------------------------------------------------------------
 
     static {
+        // note: it's safe to call methods before initInternalLogging..
         thisClassLoader = getClassLoader(LogFactory.class);
+        initDiagnostics();
+        logClassLoaderTree(LogFactory.class);
         factories = createFactoryStore();
-    }
 
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org