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:
@@ -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();
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user