Modifies Felix' internal logger to track and use available log services
(FELIX-77). Also implements a new log level configuration property to
control how much information is logged by the framework.
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@484660 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 07f8c2c..6625b31 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -36,7 +36,7 @@
public class Felix
{
// Logging related member variables.
- private Logger m_logger = new Logger();
+ private Logger m_logger = null;
// Config properties.
private PropertyResolver m_config = new ConfigImpl();
// Configuration properties passed into constructor.
@@ -131,6 +131,13 @@
* The following configuration properties can be specified:
* </p>
* <ul>
+ * <li><tt>felix.log.level</tt> - An integer value indicating the degree
+ * of logging reported by the framework; the higher the value the more
+ * logging is reported. If zero ('0') is specified, then logging is
+ * turned off completely. The log levels match those specified in the
+ * OSGi Log Service (i.e., 1 = error, 2 = warning, 3 = information,
+ * and 4 = debug). The default value is 1.
+ * </li>
* <li><tt>felix.auto.install.<n></tt> - Space-delimited list of
* bundles to automatically install into start level <tt>n</tt> when
* the framework is started. Append a specific start level to this
@@ -223,6 +230,15 @@
m_factory = null;
m_configMutable = (configMutable == null)
? new MutablePropertyResolverImpl(new StringMap(false)) : configMutable;
+
+ // Create logger with appropriate log level. Even though the
+ // logger needs the system bundle's context for tracking log
+ // services, it is created now because it is needed before
+ // the system bundle is created. The system bundle's context
+ // will be set below after the system bundle is created.
+ m_logger = new Logger(m_configMutable.get(FelixConstants.LOG_LEVEL_PROP));
+
+ // Initialize other member variables.
m_activeStartLevel = FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
m_installRequestMap = new HashMap();
m_installedBundleMap = new HashMap();
@@ -386,7 +402,11 @@
throw new RuntimeException("Unable to start system bundle.");
}
- // Reload and cached bundles.
+ // Now that the system bundle is successfully created we can give
+ // its bundle context to the logger so that it can track log services.
+ m_logger.setSystemBundleContext(systembundle.getInfo().getContext());
+
+ // Now reload the cached bundles.
BundleArchive[] archives = null;
// First get cached bundle identifiers.
diff --git a/framework/src/main/java/org/apache/felix/framework/Logger.java b/framework/src/main/java/org/apache/felix/framework/Logger.java
index a0a8795..55baf22 100644
--- a/framework/src/main/java/org/apache/felix/framework/Logger.java
+++ b/framework/src/main/java/org/apache/felix/framework/Logger.java
@@ -18,128 +18,280 @@
*/
package org.apache.felix.framework;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.ServiceReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.osgi.framework.*;
/**
* <p>
* This class mimics the standard OSGi <tt>LogService</tt> interface. An
- * instance of this class will be used by the framework for all logging. Currently,
- * the implementation of this class just sends log messages to standard output,
- * but in the future it will be modified to use a log service if one is
- * installed in the framework. To do so, it will need to use reflection to
- * call the log service methods, since it will not have access to the
- * <tt>LogService</tt> class.
+ * instance of this class is used by the framework for all logging. By default
+ * this class logs messages to standard out. The log level can be set to
+ * control the amount of logging performed, where a higher number results in
+ * more logging. A log level of zero turns off logging completely.
+ * </p>
+ * <p>
+ * The log levels match those specified in the OSGi Log Service (i.e., 1 = error,
+ * 2 = warning, 3 = information, and 4 = debug). The default value is 1.
+ * </p>
+ * <p>
+ * This class also uses the System Bundle's context to track log services
+ * and will use the highest ranking log service, if present, as a back end
+ * instead of printing to standard out. The class uses reflection to invoking
+ * the log service's method to avoid a dependency on the log interface.
* </p>
**/
-// TODO: Modify LogWrapper to get LogService service object and invoke with reflection.
-public class Logger
+public class Logger implements ServiceListener
{
public static final int LOG_ERROR = 1;
public static final int LOG_WARNING = 2;
public static final int LOG_INFO = 3;
public static final int LOG_DEBUG = 4;
- private Object m_logObj = null;
+ private int m_logLevel = 1;
+ private BundleContext m_context = null;
- public Logger()
+ private final static int LOGGER_OBJECT_IDX = 0;
+ private final static int LOGGER_METHOD_IDX = 1;
+ private ServiceReference m_logRef = null;
+ private Object[] m_logger = null;
+
+ public Logger(int logLevel)
{
+ m_logLevel = logLevel;
+ }
+
+ public Logger(String s)
+ {
+ s = (s == null) ? "1" : s;
+ try
+ {
+ m_logLevel = Integer.parseInt(s);
+ }
+ catch (NumberFormatException ex)
+ {
+ // Default to 1.
+ }
+ }
+
+ protected void setSystemBundleContext(BundleContext context)
+ {
+ m_context = context;
+ startListeningForLogService();
}
public void log(int level, String msg)
{
- synchronized (this)
- {
- if (m_logObj != null)
- {
-// Will use reflection.
-// m_logObj.log(level, msg);
- }
- else
- {
- _log(null, level, msg, null);
- }
- }
+ _log(null, level, msg, null);
}
public void log(int level, String msg, Throwable ex)
{
- synchronized (this)
- {
- if (m_logObj != null)
- {
-// Will use reflection.
-// m_logObj.log(level, msg);
- }
- else
- {
- _log(null, level, msg, ex);
- }
- }
+ _log(null, level, msg, ex);
}
public void log(ServiceReference sr, int level, String msg)
{
- synchronized (this)
- {
- if (m_logObj != null)
- {
-// Will use reflection.
-// m_logObj.log(level, msg);
- }
- else
- {
- _log(sr, level, msg, null);
- }
- }
+ _log(sr, level, msg, null);
}
public void log(ServiceReference sr, int level, String msg, Throwable ex)
{
- synchronized (this)
+ _log(sr, level, msg, ex);
+ }
+
+ private void _log(ServiceReference sr, int level, String msg, Throwable throwable)
+ {
+ // Save our own copy just in case it changes. We could try to do
+ // more conservative locking here, but let's be optimistic.
+ Object[] logger = m_logger;
+
+ if (logger != null)
{
- if (m_logObj != null)
+ _logReflectively(logger, sr, level, msg, throwable);
+ }
+ else
+ {
+ String s = (sr == null) ? null : "SvcRef " + sr;
+ s = (s == null) ? msg : s + " " + msg;
+ s = (throwable == null) ? s : s + " (" + throwable + ")";
+ switch (level)
{
-// Will use reflection.
-// m_logObj.log(level, msg);
- }
- else
- {
- _log(sr, level, msg, ex);
+ case LOG_DEBUG:
+ if (m_logLevel >= LOG_DEBUG)
+ {
+ System.out.println("DEBUG: " + s);
+ }
+ break;
+ case LOG_ERROR:
+ if (m_logLevel >= LOG_ERROR)
+ {
+ System.out.println("ERROR: " + s);
+ if (throwable != null)
+ {
+ if ((throwable instanceof BundleException) &&
+ (((BundleException) throwable).getNestedException() != null))
+ {
+ throwable = ((BundleException) throwable).getNestedException();
+ }
+ throwable.printStackTrace();
+ }
+ }
+ break;
+ case LOG_INFO:
+ if (m_logLevel >= LOG_INFO)
+ {
+ System.out.println("INFO: " + s);
+ }
+ break;
+ case LOG_WARNING:
+ if (m_logLevel >= LOG_WARNING)
+ {
+ System.out.println("WARNING: " + s);
+ }
+ break;
+ default:
+ System.out.println("UNKNOWN[" + level + "]: " + s);
}
}
}
-
- private void _log(ServiceReference sr, int level, String msg, Throwable ex)
+
+ private void _logReflectively(
+ Object[] logger, ServiceReference sr, int level, String msg, Throwable throwable)
{
- String s = (sr == null) ? null : "SvcRef " + sr;
- s = (s == null) ? msg : s + " " + msg;
- s = (ex == null) ? s : s + " (" + ex + ")";
- switch (level)
+ if (logger != null)
{
- case LOG_DEBUG:
- System.out.println("DEBUG: " + s);
- break;
- case LOG_ERROR:
- System.out.println("ERROR: " + s);
- if (ex != null)
- {
- if ((ex instanceof BundleException) &&
- (((BundleException) ex).getNestedException() != null))
- {
- ex = ((BundleException) ex).getNestedException();
- }
- ex.printStackTrace();
- }
- break;
- case LOG_INFO:
- System.out.println("INFO: " + s);
- break;
- case LOG_WARNING:
- System.out.println("WARNING: " + s);
- break;
- default:
- System.out.println("UNKNOWN[" + level + "]: " + s);
+ Object[] params = {
+ sr, new Integer(level), msg, throwable
+ };
+ try
+ {
+ ((Method) logger[LOGGER_METHOD_IDX]).invoke(logger[LOGGER_OBJECT_IDX], params);
+ }
+ catch (InvocationTargetException ex)
+ {
+ System.err.println("Logger: " + ex);
+ }
+ catch (IllegalAccessException ex)
+ {
+ System.err.println("Logger: " + ex);
+ }
+ }
+ }
+
+ /**
+ * This method is called when the system bundle context is set;
+ * it simply adds a service listener so that the system bundle can track
+ * log services to be used as the back end of the logging mechanism. It also
+ * attempts to get an existing log service, if present, but in general
+ * there will never be a log service present since the system bundle is
+ * started before every other bundle.
+ **/
+ private synchronized void startListeningForLogService()
+ {
+ // Add a service listener for log services.
+ try
+ {
+ m_context.addServiceListener(
+ this, "(objectClass=org.osgi.service.log.LogService)");
+ }
+ catch (InvalidSyntaxException ex) {
+ // This will never happen since the filter is hard coded.
+ }
+ // Try to get an existing log service.
+ m_logRef = m_context.getServiceReference("org.osgi.service.log.LogService");
+ // Get the service object if available and set it in the logger.
+ if (m_logRef != null)
+ {
+ setLogger(m_context.getService(m_logRef));
+ }
+ }
+
+ /**
+ * This method implements the callback for the ServiceListener interface.
+ * It is public as a byproduct of implementing the interface and should
+ * not be called directly. This method tracks run-time changes to log
+ * service availability. If the log service being used by the framework's
+ * logging mechanism goes away, then this will try to find an alternative.
+ * If a higher ranking log service is registered, then this will switch
+ * to the higher ranking log service.
+ **/
+ public synchronized void serviceChanged(ServiceEvent event)
+ {
+ // If no logger is in use, then grab this one.
+ if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef == null))
+ {
+ m_logRef = event.getServiceReference();
+ // Get the service object and set it in the logger.
+ setLogger(m_context.getService(m_logRef));
+ }
+ // If a logger is in use, but this one has a higher ranking, then swap
+ // it for the existing logger.
+ else if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef != null))
+ {
+ ServiceReference ref =
+ m_context.getServiceReference("org.osgi.service.log.LogService");
+ if (!ref.equals(m_logRef))
+ {
+ m_context.ungetService(m_logRef);
+ m_logRef = ref;
+ setLogger(m_context.getService(m_logRef));
+ }
+
+ }
+ // If the current logger is going away, release it and try to
+ // find another one.
+ else if ((event.getType() == ServiceEvent.UNREGISTERING) &&
+ m_logRef.equals(event.getServiceReference()))
+ {
+ // Unget the service object.
+ m_context.ungetService(m_logRef);
+ // Try to get an existing log service.
+ m_logRef = m_context.getServiceReference(
+ "org.osgi.service.log.LogService");
+ // Get the service object if available and set it in the logger.
+ if (m_logRef != null)
+ {
+ setLogger(m_context.getService(m_logRef));
+ }
+ else
+ {
+ setLogger(null);
+ }
+ }
+ }
+
+ /**
+ * This method sets the new log service object. It also caches the method to
+ * invoke. The service object and method are stored in array to optimistically
+ * eliminate the need to locking when logging.
+ **/
+ private void setLogger(Object logObj)
+ {
+ if (logObj == null)
+ {
+ m_logger = null;
+ }
+ else
+ {
+ Class[] formalParams = {
+ ServiceReference.class,
+ Integer.TYPE,
+ String.class,
+ Throwable.class
+ };
+
+ try
+ {
+ Method logMethod = logObj.getClass().getMethod("log", formalParams);
+ logMethod.setAccessible(true);
+ m_logger = new Object[] { logObj, logMethod };
+ }
+ catch (NoSuchMethodException ex)
+ {
+ System.err.println("Logger: " + ex);
+ m_logger = null;
+ }
}
}
}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java b/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
index a63d1ae..f0a0993 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
@@ -41,6 +41,7 @@
public static final String BUNDLE_URL_PROTOCOL = "bundle";
// Miscellaneous framework configuration property names.
+ public static final String LOG_LEVEL_PROP = "felix.log.level";
public static final String AUTO_INSTALL_PROP = "felix.auto.install";
public static final String AUTO_START_PROP = "felix.auto.start";
public static final String EMBEDDED_EXECUTION_PROP = "felix.embedded.execution";
diff --git a/main/src/main/resources/config.properties b/main/src/main/resources/config.properties
index c9bae94..784967d 100644
--- a/main/src/main/resources/config.properties
+++ b/main/src/main/resources/config.properties
@@ -30,6 +30,7 @@
file:bundle/org.apache.felix.shell-${pom.version}.jar \
file:bundle/org.apache.felix.shell.tui-${pom.version}.jar \
file:bundle/org.apache.felix.bundlerepository-${pom.version}.jar
+felix.log.level=4
felix.startlevel.framework=1
felix.startlevel.bundle=1
#framework.service.urlhandlers=false