1
0

Added internal diagnostics

git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/logging/trunk@171300 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Simon Kitching
2005-05-22 10:43:06 +00:00
parent cbfc01a0ab
commit c485c20b1e

View File

@@ -21,6 +21,8 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.AccessController; import java.security.AccessController;
@@ -79,6 +81,34 @@ public abstract class LogFactory {
protected static final String SERVICE_ID = protected static final String SERVICE_ID =
"META-INF/services/org.apache.commons.logging.LogFactory"; "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 * <p>Setting this system property value allows the <code>Hashtable</code> used to store
* classloaders to be substituted by an alternative implementation. * classloaders to be substituted by an alternative implementation.
@@ -136,7 +166,6 @@ public abstract class LogFactory {
protected LogFactory() { protected LogFactory() {
} }
// --------------------------------------------------------- Public Methods // --------------------------------------------------------- Public Methods
@@ -247,6 +276,21 @@ public abstract class LogFactory {
*/ */
protected static LogFactory nullClassLoaderFactory = null; 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() { private static final Hashtable createFactoryStore() {
Hashtable result = null; Hashtable result = null;
String storeImplementationClass String storeImplementationClass
@@ -262,9 +306,16 @@ public abstract class LogFactory {
// ignore // ignore
if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) { if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) {
// if the user's trying to set up a custom implementation, give a clue // 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"); System.err.println("[ERROR] LogFactory: Load of custom hashtable failed");
} }
} }
}
if (result == null) { if (result == null) {
result = new Hashtable(); result = new Hashtable();
} }
@@ -305,13 +356,32 @@ public abstract class LogFactory {
// Identify the class loader we will be using // Identify the class loader we will be using
ClassLoader contextClassLoader = getContextClassLoader(); 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 // Return any previously registered factory for this class loader
LogFactory factory = getCachedFactory(contextClassLoader); LogFactory factory = getCachedFactory(contextClassLoader);
if (factory != null) if (factory != null) {
return factory; return factory;
}
logDiagnostic(
"LogFactory requested for the first time for context classloader "
+ objectId(contextClassLoader));
// Load properties file. // 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; Properties props=null;
try { try {
@@ -324,28 +394,47 @@ public abstract class LogFactory {
stream.close(); stream.close();
} }
} catch (IOException e) { } catch (IOException e) {
} catch (SecurityException e) { ; // ignore
}
// First, try the system property
try {
String factoryClass = System.getProperty(FACTORY_PROPERTY);
if (factoryClass != null) {
factory = newFactory(factoryClass, contextClassLoader);
}
} catch (SecurityException e) { } catch (SecurityException e) {
; // ignore ; // ignore
} }
// Second, try to find a service by using the JDK1.3 jar // First, try a global system property
// discovery mechanism. This will allow users to plug a logger logDiagnostic(
// by just placing it in the lib/ directory of the webapp ( or in "Looking for system property [" + FACTORY_PROPERTY
// CLASSPATH or equivalent ). This is similar to the second + "] to define the LogFactory subclass to use..");
// step, except that it uses the (standard?) jdk1.3 location in the jar.
try {
String factoryClass = System.getProperty(FACTORY_PROPERTY);
if (factoryClass != null) {
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 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) { if (factory == null) {
logDiagnostic(
"Looking for a resource file of name [" + SERVICE_ID
+ "] to define the LogFactory subclass to use..");
try { try {
InputStream is = getResourceAsStream(contextClassLoader, InputStream is = getResourceAsStream(contextClassLoader,
SERVICE_ID); SERVICE_ID);
@@ -366,11 +455,23 @@ public abstract class LogFactory {
if (factoryClassName != null && if (factoryClassName != null &&
! "".equals(factoryClassName)) { ! "".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 ) { } 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 @@ public abstract class LogFactory {
// the webapp, even if a default logger is set at JVM level by a // the webapp, even if a default logger is set at JVM level by a
// system property ) // system property )
if (factory == null && props != null) { 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); String factoryClass = props.getProperty(FACTORY_PROPERTY);
if (factoryClass != null) { if (factoryClass != null) {
factory = newFactory(factoryClass, contextClassLoader); factory = newFactory(factoryClass, contextClassLoader, contextClassLoader);
// what about handling an exception from newFactory??
}
} }
} }
@@ -394,8 +508,21 @@ public abstract class LogFactory {
// Fourth, try the fallback implementation class // Fourth, try the fallback implementation class
if (factory == null) { 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); ClassLoader logFactoryClassLoader = getClassLoader(LogFactory.class);
factory = newFactory(FACTORY_DEFAULT, logFactoryClassLoader); factory = newFactory(FACTORY_DEFAULT, logFactoryClassLoader, contextClassLoader);
} }
if (factory != null) { if (factory != null) {
@@ -464,6 +591,7 @@ public abstract class LogFactory {
*/ */
public static void release(ClassLoader classLoader) { public static void release(ClassLoader classLoader) {
logDiagnostic("Releasing factory for classloader " + objectId(classLoader));
synchronized (factories) { synchronized (factories) {
if (classLoader == null) { if (classLoader == null) {
if (nullClassLoaderFactory != null) { if (nullClassLoaderFactory != null) {
@@ -492,6 +620,7 @@ public abstract class LogFactory {
*/ */
public static void releaseAll() { public static void releaseAll() {
logDiagnostic("Releasing factory for all classloaders.");
synchronized (factories) { synchronized (factories) {
Enumeration elements = factories.elements(); Enumeration elements = factories.elements();
while (elements.hasMoreElements()) { while (elements.hasMoreElements()) {
@@ -511,19 +640,16 @@ public abstract class LogFactory {
// ------------------------------------------------------ Protected Methods // ------------------------------------------------------ Protected Methods
/** /**
* Safely get access to the classloader for the specified class. * Safely get access to the classloader for the specified class.
* <p> * <p>
* Theoretically, calling Class.getClassLoader can throw a security * Theoretically, calling getClassLoader can throw a security exception,
* exception, and so should be done under an AccessController in order * and so should be done under an AccessController in order to provide
* to provide maximum flexibility. * maximum flexibility. However in practice people don't appear to use
* <p> * security policies that forbid getClassLoader calls. So for the moment
* However in practice people don't appear to use security policies that * all code is written to call this method rather than Class.getClassLoader,
* forbid getClassLoader calls, so for the moment this method doesn't * so that we could put AccessController stuff in this method without any
* bother to actually do that. As all code is written to call this * disruption later if we need to.
* method rather than Class.getClassLoader, AccessController stuff could
* be put in this method without any disruption later if needed.
* <p> * <p>
* Even when using an AccessController, however, this method can still * Even when using an AccessController, however, this method can still
* throw SecurityException. Commons-logging basically relies on the * throw SecurityException. Commons-logging basically relies on the
@@ -539,6 +665,9 @@ public abstract class LogFactory {
try { try {
return clazz.getClassLoader(); return clazz.getClassLoader();
} catch(SecurityException ex) { } catch(SecurityException ex) {
logDiagnostic(
"Unable to get classloader for class " + clazz
+ " due to security restrictions.");
throw ex; throw ex;
} }
} }
@@ -637,6 +766,19 @@ public abstract class LogFactory {
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// Assume we are running on JDK 1.1 // Assume we are running on JDK 1.1
classLoader = getClassLoader(LogFactory.class); 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 // Return the selected class loader
@@ -646,6 +788,13 @@ public abstract class LogFactory {
/** /**
* Check cached factories (keyed by contextClassLoader) * 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 * @return the factory associated with the specified classloader if
* one has previously been created, or null if this is the first time * one has previously been created, or null if this is the first time
* we have seen this particular classloader. * we have seen this particular classloader.
@@ -655,6 +804,9 @@ public abstract class LogFactory {
LogFactory factory = null; LogFactory factory = null;
if (contextClassLoader == 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. // nb: nullClassLoaderFactory might be null. That's ok.
factory = nullClassLoaderFactory; factory = nullClassLoaderFactory;
} else { } else {
@@ -693,7 +845,36 @@ public abstract class LogFactory {
* implementation class, loaded by the specified class loader. * implementation class, loaded by the specified class loader.
* If that fails, try the class loader used to load this * If that fails, try the class loader used to load this
* (abstract) LogFactory. * (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> * @param factoryClass Fully qualified name of the <code>LogFactory</code>
* implementation class * implementation class
* @param classLoader ClassLoader from which to load this class * @param classLoader ClassLoader from which to load this class
@@ -702,7 +883,8 @@ public abstract class LogFactory {
* cannot be created * cannot be created
*/ */
protected static LogFactory newFactory(final String factoryClass, protected static LogFactory newFactory(final String factoryClass,
final ClassLoader classLoader) final ClassLoader classLoader,
final ClassLoader contextClassLoader)
throws LogConfigurationException throws LogConfigurationException
{ {
Object result = AccessController.doPrivileged( Object result = AccessController.doPrivileged(
@@ -712,8 +894,17 @@ public abstract class LogFactory {
} }
}); });
if (result instanceof LogConfigurationException) if (result instanceof LogConfigurationException) {
throw (LogConfigurationException)result; 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; return (LogFactory)result;
} }
@@ -739,47 +930,80 @@ public abstract class LogFactory {
// Warning: must typecast here & allow exception // Warning: must typecast here & allow exception
// to be generated/caught & recast properly. // to be generated/caught & recast properly.
logFactoryClass = classLoader.loadClass(factoryClass); 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(); return (LogFactory) logFactoryClass.newInstance();
} catch (ClassNotFoundException ex) { } catch (ClassNotFoundException ex) {
if (classLoader == thisClassLoader) { if (classLoader == thisClassLoader) {
// Nothing more to try, onwards. // Nothing more to try, onwards.
logDiagnostic(
"Unable to locate any class called " + factoryClass
+ " via classloader " + objectId(classLoader));
throw ex; throw ex;
} }
// ignore exception, continue // ignore exception, continue
} catch (NoClassDefFoundError e) { } catch (NoClassDefFoundError e) {
if (classLoader == thisClassLoader) { if (classLoader == thisClassLoader) {
// Nothing more to try, onwards. // 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; throw e;
} }
// ignore exception, continue
} catch(ClassCastException e) { } catch(ClassCastException e) {
if (classLoader == thisClassLoader) { if (classLoader == thisClassLoader) {
// Nothing more to try, onwards (bug in loader implementation). // 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; throw e;
} }
}
// Ignore exception, continue // Ignore exception, continue
} }
}
/* At this point, either classLoader == null, OR /* At this point, either classLoader == null, OR
* classLoader was unable to load factoryClass. * 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: * Notes:
* a) LogFactory.class.getClassLoader() may return 'null' * * LogFactory.class.getClassLoader() may return 'null'
* if LogFactory is loaded by the bootstrap classloader. * if LogFactory is loaded by the bootstrap classloader.
* b) The Java endorsed library mechanism is instead
* Class.forName(factoryClass);
*/ */
// Warning: must typecast here & allow exception // Warning: must typecast here & allow exception
// to be generated/caught & recast properly. // 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); logFactoryClass = Class.forName(factoryClass);
return (LogFactory) logFactoryClass.newInstance(); return (LogFactory) logFactoryClass.newInstance();
} catch (Exception e) { } catch (Exception e) {
// Check to see if we've got a bad configuration // Check to see if we've got a bad configuration
logDiagnostic("Unable to create logfactory instance.");
if (logFactoryClass != null if (logFactoryClass != null
&& !LogFactory.class.isAssignableFrom(logFactoryClass)) { && !LogFactory.class.isAssignableFrom(logFactoryClass)) {
return new LogConfigurationException( return new LogConfigurationException(
@@ -805,6 +1029,202 @@ public abstract class LogFactory {
} }
}); });
} }
/**
* 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. // Static initialiser block to perform initialisation at class load time.
// //
@@ -825,8 +1245,11 @@ public abstract class LogFactory {
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
static { static {
// note: it's safe to call methods before initInternalLogging..
thisClassLoader = getClassLoader(LogFactory.class); thisClassLoader = getClassLoader(LogFactory.class);
initDiagnostics();
logClassLoaderTree(LogFactory.class);
factories = createFactoryStore(); factories = createFactoryStore();
}
} }
}