Category: OSGi

12/18/08

Permalink 10:46:37 pm, by nogunner Email , 1537 words   English (US)
Categories: OSGi

OSGi logging: putting it all together

In a componentized world such as OSGi, logging is not anymore a matter of instanciating a logger and using it, it's become a strict procedure which starts with requesting a reference to the logging service, checking that the service is available, and finally calling its methods. I know, all this sounds like a cheesy overhead just for some stupid logging, while all we want is living the simple life of POJO. But it's not just about logging here, it's about having a dynamic environment, being able to dynamically start, stop, update, and change any service without other components having to worry about it. And fortunately, OSGi manages to keep it simple.

I've started my series about logging in OSGi out of the sad notice that most blog entries and comments just missed the point and had an erroneous idea of logging on OSGi, mainly because they are trying to use log4j or the java.logging API as they are used to, without taking into account the drastic change of architecture imposed by the OSGI component model.

For instance, implementing a LogService hard-coded to bind on logback or log4j (or any specific engine) is definitely a bad idea: OSGi is all about dynamic component, and the way to go is to build a LogListener backed by logback or log4j, and let it connect to the LogReaderService. That way, not only can we easily *swap* the component in charge of storing/processing the logs, but we can also *combine* several of them if needed.

This is what should be avoided:

WRONG Architecture

Unfortunately, most logging libraries are done like that, including Pax Logging or the bundles provided for slf4j or logback.

On the other hand, a logging architecture that fits the dynamic component model proposed by OSGi should rather look like this:

GOOD Architecture

Note how this architecture respects what we expect of components: dynamically updateable, swappable and combinable.

Building the bundles

If your bundle only contains your own code and has no dependency on third-party libraries, you may just use the LogService straight. However, even in this case, it's still more convenient to use one of the two facades provided below (commons-logging or sl4j): not only will your code be able to run in a non-OSGi compliant environment as well, but the libraries provided here will take care of managing the log services available on the server.

However, chances are that your code links to some jar that use either commons-logging or slf4j. In this case, including one of the bridge-to-osgi libraries provided below should be enough to OSGi-fy them. Those OSGi bridge libraries must be privately loaded from the bundle, because they use static variable that are not meant to be shared across the boundaries of bundles. To ensure this constraint is respected, verify they are not loaded from the global classpath of the server (there's no reason to find them there anyway), and use the Bundle-Classpath property in the manifest to have the OSGI server load them with the classloader of the bundle.

Each of these OSGi bridge MUST be initialized from the Activator class of your bundle. There's one good reason for that: they need a valid BundleContext object, which is provided by the framework to the Activator's start() method. The beginning of the start() method is a good place for this initialization.

For SLF4J

Initialize with the following line in your Activator.start() method:

 
org.slf4j.impl.OSGILogFactory.initOSGI(context); 

osgi-slf4j-bundle

For Commons-Logging

Initialize with the following line in your Activator.start() method:

 
net.kornr.osgi.jcl.LogOSGIFactory.initOSGI(context); 

osgi-commons-logging-bundle

Note the extra step for the commons-logging: a system property must be set up to give an indication to the library of the factory class to use to create the loggers:

 java -Dorg.apache.commons.logging.LogFactory=net.kornr.osgi.jcl.LogOSGIFactory ...

(the -D is a command line flag used to set a system-wide property).

If your bundle needs to use both libraries, just call both initializing methods, and include both jars.

Configuring the Logback bundle

In the previous part of the series, I've shown how to create an OSGi bundle for Logback (the Log4J's successor). We'll just use it and set up a configuration file (please refer to the article if you need further technical details). You can skip the config file part if the default log-all-to-stdout is fine for you.

For instance, given the following configuration file at location /tmp/config.xml:

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss} %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>

  <logger name="net.kornr.log.commons_logging_test_bundle" level="warn">
  </logger>

  <logger name="net.kornr.log.slf4j_test_bundle" level="error">
  </logger>

  <!-- Strictly speaking, the level attribute is not necessary since -->
  <!-- the level of the root level is set to DEBUG by default.       -->
  <root level="info">
    <appender-ref ref="STDOUT" />
  </root>  
  
</configuration>

To let logback be aware of this configuration file, define the logback.configurationFile property as follows:

 
java -Dlogback.configurationFile=/tmp/config.xml ...

The loggers name referenced in file are the symbolic bundle name, and you can adjust the logging properties of each of them; please refer to the logback documentation for the details.

Testing everything

You can quickly test the configuration from a blank felix installation.
To install the bundles directly from the OSGi shell, use the following commands:

* install http://oscar-osgi.sf.net/repo/log/log.jar to install a LogService (if there is not already one installed)

* install http://osgi-logging.googlecode.com/files/LogbackBundle-1.0.jar to install the Logback-based LogListener back-end.

* install http://osgi-logging.googlecode.com/files/commons-logging-osgi-test-bundle-1.0.jar to install a log tester using the commons-logging-osgi bridge. It generates logs every few seconds (Warning: it really generates LOADS of logs, I should probably fix that)

* http://osgi-logging.googlecode.com/files/slf4j-osgi-test-bundle-1.0.jar to install a log tester using the slf4j API (comments above apply on this bundle as well)

Please see below for a session using the Felix OSGi server:

 
c:\temp\felix-1.4.0>java -Dorg.apache.commons.logging.LogFactory=net.kornr.osgi.jcl.LogOSGIFactory -jar bin\felix.jar

Welcome to Felix.
=================

-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.4.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.2)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.2.1)
-> install http://oscar-osgi.sf.net/repo/log/log.jar
Bundle ID: 4
-> install http://osgi-logging.googlecode.com/files/LogbackBundle-1.0.jar
Bundle ID: 5
-> install http://osgi-logging.googlecode.com/files/commons-logging-osgi-test-bundle-1.0.jar
Bundle ID: 6
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.4.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.2)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.2.1)
[   4] [Installed  ] [    1] Log Service (1.0.0)
[   5] [Installed  ] [    1] LogBackend (1.0.0)
[   6] [Installed  ] [    1] Commons_logging_test_bundle Plug-in (1.0.0)
-> start 4
-> start 5
-> 21:59:13.861 [Thread-1] INFO  LogBackend - BundleEvent.STARTED
start 6
21:59:20.901 [Thread-1] INFO  n.k.log.commons_logging_test_bundle - null
-> 21:59:20.942 [Thread-1] INFO  n.k.log.commons_logging_test_bundle - BundleEve
nt.STARTED
21:59:20.948 [Thread-1] DEBUG n.k.log.commons_logging_test_bundle - JCL-debug-log=Thu Dec 18 21:59:20 CET 2008
21:59:20.949 [Thread-1] INFO  n.k.log.commons_logging_test_bundle - JCL-info-log=Thu Dec 18 21:59:20 CET 2008
21:59:20.949 [Thread-1] WARN  n.k.log.commons_logging_test_bundle - JCL-warning-log=Thu Dec 18 21:59:20 CET 2008
21:59:20.950 [Thread-1] ERROR n.k.log.commons_logging_test_bundle - JCL-error-log=Thu Dec 18 21:59:20 CET 2008
21:59:20.956 [Thread-1] DEBUG n.k.log.commons_logging_test_bundle - JCL-debug-log=Thu Dec 18 21:59:20 CET 2008
java.lang.Exception: An EXCEPTION!
        at commons_logging_test_bundle.Activator$1.run(Unknown Source) [na:na]
        at java.lang.Thread.run(Unknown Source) [na:1.6.0_07]
21:59:20.969 [Thread-1] INFO  n.k.log.commons_logging_test_bundle - JCL-info-log=Thu Dec 18 21:59:20 CET 2008
java.lang.Exception: An EXCEPTION!
        at commons_logging_test_bundle.Activator$1.run(Unknown Source) [na:na]
        at java.lang.Thread.run(Unknown Source) [na:1.6.0_07]
21:59:20.974 [Thread-1] WARN  n.k.log.commons_logging_test_bundle - JCL-warning-log=Thu Dec 18 21:59:20 CET 2008
java.lang.Exception: An EXCEPTION!
        at commons_logging_test_bundle.Activator$1.run(Unknown Source) [na:na]
        at java.lang.Thread.run(Unknown Source) [na:1.6.0_07]
21:59:20.996 [Thread-1] ERROR n.k.log.commons_logging_test_bundle - JCL-error-log=Thu Dec 18 21:59:20 CET 2008
java.lang.Exception: An EXCEPTION!
        at commons_logging_test_bundle.Activator$1.run(Unknown Source) [na:na]
        at java.lang.Thread.run(Unknown Source) [na:1.6.0_07]
stop 6
-> 21:59:23.223 [Thread-1] INFO  n.k.log.commons_logging_test_bundle - BundleEvent.STOPPED
shutdown
->
c:\temp\felix-1.4.0>
Download the libraries

I've put all the articles from this series, as well as the code and the binaries, on a project page of its own, so that I can more easily fix the code in case of bugs or errors: Project Page

You can download the bundles and the libraries directly from the google code project page:
* http://code.google.com/p/osgi-logging/downloads/list

The "normal" commons-logging and slf4j jar should be downloaded from their respective web sites:
* Apache Commons Logging
* SLF4J

You can grab or browse the source code on the appropriate google-code page:
* http://code.google.com/p/osgi-logging/source/checkout

12/16/08

Permalink 11:37:39 pm, by nogunner Email , 736 words   English (US)
Categories: OSGi

Building a full-featured OSGI logging back-end

This article is the third of a four-part series about logging in an OSGi environment.

In the previous articles of the series, we've seen how to link standard logs from the apache-commons logging library to the OSGi LogService, and how to create a simple service that reads the LogReaderService to display the log entries. Now it's time to build a full-featured back-end for our OSGi logs.

I've used the Logback library, but it can easily be transposed to any similar logging system, be it log4j or the jdk logging system (and in fact, any system supported by slf4j).

The big picture

Most of the work is done by one single class, the LogbackAdaptor, which is basically a LogListener that manages a bunch of Logger objects. Every time a LogEvent is received, it retrieves a Logger associated to the event's bundle, or create a new one if there's none. It then just calls the appropriate Logger method: debug() info(), warn(), or error(). If there's an exception carried by the event, the Logger's methods with an exception in the signature are used.

The source code

The source code is available at the bottom of this article, but the LogbackAdaptor source code is so simple that it's worth showing it. The only other class in the package is the Activator that keeps track of the LogReaderService instances, and registers the LogbackAdaptor to them.
(if you use an RSS reader, you probably won't see any syntax highlighting in the source below, sorry)

package net.kornr.log;

import java.util.HashMap;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The LogbackAdaptor converts the LogEntry objects it receives into calls to the slf4j 
 * loggers (the native interface of logback).
 * 
 * @author Rodrigo Reyes
 *
 */
public class LogbackAdaptor implements LogListener
{
	HashMap<Long, org.slf4j.Logger> m_loggers = new HashMap<Long, org.slf4j.Logger>(); 

	/**
	 * This methods is called by the LogReaderService, and dispatch them to 
	 * a set of Loggers, created with 
	 */
	public void logged(LogEntry log) 
	{
		if ((log.getBundle() == null) || (log.getBundle().getSymbolicName() == null))
		{
			// if there is no name, it's probably the framework emitting a log
			// This should not happen and we don't want to log something anonymous
			return; 
		}
		
		// Retrieve a Logger object, or create it if none exists.
		Logger logger = m_loggers.get(log.getBundle().getBundleId());
		if (logger == null)
		{
			logger = LoggerFactory.getLogger(log.getBundle().getSymbolicName());
			m_loggers.put(log.getBundle().getBundleId(), logger);
		}

		// If there is an exception available, use it, otherwise just log 
		// the message
		if (log.getException() != null)
		{
			switch(log.getLevel())
			{
			case LogService.LOG_DEBUG:
				logger.debug(log.getMessage(), log.getException());
				break;
			case LogService.LOG_INFO:
				logger.info(log.getMessage(), log.getException());
				break;
			case LogService.LOG_WARNING:
				logger.warn(log.getMessage(), log.getException());
				break;
			case LogService.LOG_ERROR:
				logger.error(log.getMessage(), log.getException());
				break;
			}
		}
		else
		{
			switch(log.getLevel())
			{
			case LogService.LOG_DEBUG:
				logger.debug(log.getMessage());
				break;
			case LogService.LOG_INFO:
				logger.info(log.getMessage());
				break;
			case LogService.LOG_WARNING:
				logger.warn(log.getMessage());
				break;
			case LogService.LOG_ERROR:
				logger.error(log.getMessage());
				break;
			}			
		}
	}
}
Configuring logback

The easiest way to configure Logback is to create an xml configuration and place it in a relevant location. Then, define the logback.configurationFile property with the location of the configuration file. For instance on the command line:

java -Dlogback.configurationFile=/some/location/logback.xml ...

Examples and documentation on the configuration are available in the logback manual.

Configuring the bundle

The bundle is just a matter of specifying the Bundle-Classpath with the logback-related jars.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: LogBackend
Bundle-SymbolicName: LogBackend
Bundle-Version: 1.0.0
Bundle-Activator: net.kornr.log.LogBackendActivator
Import-Package: org.osgi.framework,
 org.osgi.service.log,
 org.osgi.util.tracker
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-ClassPath: libs/logback-classic-0.9.13.jar,
 libs/logback-core-0.9.13.jar,
 .,
 libs/slf4j-api-1.5.6.jar
Quickstart guide for the impatient
  1. Install and start the bundle, it works out of the box: Logback uses its default configuration, sending the logs to standard output, with a default formatter.
  2. (optional) configure the logback configuration
Download

The software is provided as free software, please refer to the readme (in the tar.gz) for the details.

12/14/08

Permalink 08:31:52 pm, by nogunner Email , 937 words   English (US)
Categories: OSGi

OSGI-fying the apache commons-logging API

This article is the second of a four-part series about logging in an OSGi environment.

There's a funny FUD that has been going on for some time, stating that the apache commons-logging library is definitely not compliant with OSGI, because of some surprising classloading issue. This even reached the apache commons wiki, which states the following:

apache commons logging fud

Of course there's no point in making an OSGI bundle out of the commons-logging jar, because that's not how it is supposed to be used in this context. The commons-logging library provides a facade that should be included in the internal classpath of an OSGI bundle, there's no point in turning it into a service: there's already a standard OSGI Logging service, which API is fully described in the OSGI specifications.

I've found some traces of similar statements around the internet, and of course no factual proof that apache-commons is not OSGI-compliant, except some clueless people whining on some newsgroups (see http://wiki.apache.org/commons/Commons_Logging_FUD for a discussion of the FUD). This seemed rather odd to me, specially when considering the clean design of the commons-logging library. It's just sad to see people trashing some good piece of software just for the purpose of pushing their own solution.

Anyway, for the people interested, I developped an OSGI-compliant LogFactory that forwards the log to the OSGI LogService. There was no difficulty in that, and was done and tested in a couple of hours.

Is it a hack? Absolutely not. Did it require any change in the commons-logging source code? Not at all. The LogFactory used by commons-logging to create the Log object can be redefined using a system property, namely org.apache.commons.logging.LogFactory. Just set this property to the name of a class implementing LogFactory, and you're done.

Well, almost done. You also need to provide the BundleContext to the LogFactory, so that it can find the currently active LogService. The implementation I provide does the following:

  • It uses the currently active LogService as the backend for the Log implementation.
  • It registers as a ServiceListener, to be able to behave correctly if the LogService is stopped, or if another one is registered. If at some point, the LogService is not active anymore, it tries to find another one, and if none is available, it just ignores any logging until a LogService object is registered back.

Here is an example of how the OSGI logging must be initialized in the BundleActivator:

public class Activator implements BundleActivator 
{
	private Log log = LogFactory.getLog(Activator.class);

	public void start(BundleContext context) throws Exception 
	{
		// Here is how the LogService is retrieved and set up
		net.kornr.osgi.jcl.LogOSGIFactory.initOSGI(context);
		// or, to specify a service reference: 
		// net.kornr.osgi.jcl.LogOSGIFactory.initOSGI(context, someServiceReference);

		// If there is no LogService available, the line below does nothing
		log.info("Hello World!!");
	}

	public void stop(BundleContext context) throws Exception {
		// The logging is available 'til the very end
		log.info("Goodbye World!!");
	}

}

That's the only specific code required, and IMHO it makes sense to have the BundleActivator do this kind of initialization.

Other classes use the logging system as usual:

public class FooBar
{
   private Log log = LogFactory.getLog(FooBar.class);
   
   public void test()
   {
         log.debug("A dummy log...");
   }
}

Unlike the commons-logging API, the LogService object does not provide a slot to specify the class emitting the log. Instead, OSGI provides to the LogService a reference to the emitting Bundle, but also additionally offers a ServiceReference argument. If your Bundle registers one single Service, you can provide it to the LogService by initializing the LogOSGIFactory with a ServiceReference object:

LogOSGIFactory.initOSGI(BundleContext context, ServiceReference ref)

Once you have set up your bundle to use apache commons logging, you'll probably need a LogService back-end. Please check my previous article for some pointers.

Structure of a project

The apache-commons-logging jar as well as the commons-logging-osgi jar must be in the bundles' classpath, not in the common classpath of the osgi server.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: TestJCL1 Plug-in
Bundle-SymbolicName: TestJCL1
Bundle-Version: 1.0.0
Bundle-Activator: testjcl1.Activator
Import-Package: org.osgi.framework;version="1.3.0",
 org.osgi.service.log
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ClassPath: ., commons-logging-1.1.1.jar, commons-logging-osgi-20081214.jar

To use the library, in 4 simple steps (excerpt from readme file):

1. Add the commons-logging-osgi-XXXX.jar to your runtime classpath.
2. Add to the System properties of the JVM the following setting:
org.apache.commons.logging.LogFactory=net.kornr.osgi.jcl.LogOSGIFactory
For instance, add the following to your java command line:
-Dorg.apache.commons.logging.LogFactory=net.kornr.osgi.jcl.LogOSGIFactory
This informs the apache commons logging API to use the
OSGI-compliant LogFactory provided instead of using its default
settings.
3. Add in the Activator class of your bundle the following line:
public void start(BundleContext context) throws Exception {
...
net.kornr.osgi.jcl.LogOSGIFactory.initOSGI(context);
...
}
This bootstraps the LogFactory with the BundleContext, so that it can
get a reference to a LogService object.

4. Add the commons-logging-osgi-XXXX.jar to the Bundle-ClassPath:
entry of your MANIFEST.MF
That's it.

Download

The software is provided under the Apache License, Version 2.0

Update on 2008-12-15: It appears some OSGI containers do not raise an exception when using a stopped service, preventing the logger to update its reference accordingly. I modified the source code so that it always uses the up-to-date log service reference. There is no impact on performance with this change.

Previous version:

12/09/08

Permalink 10:13:19 pm, by nogunner Email , 1450 words   English (US)
Categories: OSGi

Understanding the OSGI logging service

This article is the first of a four-part series about logging in an OSGi environment.

The OSGI specifications provide a description of the Log Service as a very simplistic set of classes, none of them providing any method related to how the logs are stored or displayed. As a consequence, the logging service is often seen as a unusable toy-like API, far from the level of maturity of log4j (to name the most common logging API).

This is not only very unfair, but also leads developpers to use the Log4j API with a naive OSGI bundling, often implemented as an Activator managing the log4j configuration, and even worse, to still link against it using static logging methods instead of using the standard OSGI logging service.

The misunderstanding mainly comes from the fact that developers expect the logservice to be a fully-featured logging service, while in fact it only defines a front-end API to be used by bundles. The logging back-end, or how the log entries are displayed, stored, or processed are currently not defined by the OSGI specifications. Is it an issue? Yes and no, it certainly is consistent from an architecture point of view to exclude the back-end logging services from the specifications, however on a more pragmatic level it didn't exactly help the LogService usage spread as widely as it should have.

Anyway, let's have a glance at a typical OSGI server and its bundles. Some bundles are pure services (such as the logging or the configuration service), while others are applicative bundles that use the osgi server as a standard application server.

Figure 1

Figure 1 shows how bundles use the LogService, without any direct relationship with the back-end logging services.
The figure shows some example of possible LogListener elements:

  • a ConsoleLog logging on the console
  • a RotatingFileLog, logging in a file, rotating the file name.
  • an EmailAlertLog, sending an alert email to the administrator when an error log is propagated.

The approach taken by OSGI enforces the separation of concerns: applications should only know how to send their logs to the log service; the OSGI administrator (the human being, not some service) takes care of managing the logging back-ends, and sets the logs up according to a server-specific logging strategy. To make a parallel with log4J, while in log4j you set up a centralized configuration file, and have a thread watch it for changes ("configureAndWatch()"), with the OSGI architecture you would rather hot-plug and unplug the logging back-end services, and reconfigure them dynamically when needed (typically, changing a configuration file, then stop/starting the service).

Note that, as we are in the OSGI world, the back-end logging strategy can be defined not only in terms of packages and classes (as standard java APIs do), but also in terms of services and bundles. This higher-level view is a major benefit provided by the model, and is much more convenient than packages for many use cases. However, as a drawback, there is currently no filter at the logservice level to prevent the log entries to be propagated to all the log listeners.

As for logging back-ends, even if the lack of service available in bundle repositories may be disappointing, let's take it as an exercice left to the developers, and let's build a simple Console back-end. Our objective here is to use a logging architecture as showed in figure 1, with a standard LogService and a sample implementation of ConsoleLog.

First, we need to have a fully-compliant LogService implementation. If your framework doesn't provide one, you can easily install the one available from the oscar repository, by typing the following command:

install http://oscar-osgi.sf.net/repo/log/log.jar

Now, let's write a LogTester. For the sake of simplicity, we're going to use an Activator making a single log call only if it can find a LogService available at startup :

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.service.log.*;

public class Activator implements BundleActivator
{
    public void start(BundleContext context) throws Exception {
        ServiceTracker logServiceTracker = new ServiceTracker(context, org.osgi.service.log.LogService.class.getName(), null);
        logServiceTracker.open();
        LogService logservice = (LogService) logServiceTracker.getService();
       
        if (logservice != null)
            logservice.log(LogService.LOG_INFO, "hey, I logged that!");
    }
   
    public void stop(BundleContext context) throws Exception {
    }
}

To test the log, just stop/start it. Alternatively, you can also create a thread that logs something every few seconds.

If you start both LogService and LogTester, nothing should be displayed yet, because we still need a LogReaderService to do something useful with the LogEntry object implicitly created by the log() call.

In another bundle, which I'll call "ConsoleLog", let's create a class implementing LogListener, and make it echo the log message on System.out:

import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;

public class ConsoleLogImpl implements LogListener
{
    public void logged(LogEntry log)
    {
        if (log.getMessage() != null)
            System.out.println("[" + log.getBundle().getSymbolicName() + "] " + log.getMessage());

    }
}

Then, we need the Activator responsible for registering our LogListener with all the logging service available on the server. The Activator below does mainly 3 things:
- In the start() method, it registers the ConsoleLogImpl object to all the LogReaderService available at the time of the method call.
- In the start() method, it also registers a ServiceListener (namely m_servlistener) that adds or remove the ConsoleLogImpl object to all the LogReaderService elements that are started or stopped (respectively).
- In the stop() method, it unregisters the ConsoleLogImpl from LogReaderService objects that are still active on the server.

import java.util.Iterator;
import java.util.LinkedList;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.service.log.LogReaderService;
import org.osgi.util.tracker.ServiceTracker;

public class StdOutActivator implements BundleActivator
{
    private ConsoleLogImpl m_console = new ConsoleLogImpl();
    private LinkedList<LogReaderService> m_readers = new LinkedList<LogReaderService>();

    //  We use a ServiceListener to dynamically keep track of all the LogReaderService service being
    //  registered or unregistered
    private ServiceListener m_servlistener = new ServiceListener() {
        public void serviceChanged(ServiceEvent event)
        {
            BundleContext bc = event.getServiceReference().getBundle().getBundleContext();
            LogReaderService lrs = (LogReaderService)bc.getService(event.getServiceReference());
            if (lrs != null)
            {
                if (event.getType() == ServiceEvent.REGISTERED)
                {
                    m_readers.add(lrs);
                    lrs.addLogListener(m_console);
                } else if (event.getType() == ServiceEvent.UNREGISTERING)
                {
                    lrs.removeLogListener(m_console);
                    m_readers.remove(lrs);
                }
            }
        }
    };

    public void start(BundleContext context) throws Exception
    {
        // Get a list of all the registered LogReaderService, and add the console listener
        ServiceTracker logReaderTracker = new ServiceTracker(context, org.osgi.service.log.LogReaderService.class.getName(), null);
        logReaderTracker.open();
        Object[] readers = logReaderTracker.getServices();
        if (readers != null)
        {
            for (int i=0; i<readers.length; i++)
            {
                LogReaderService lrs = (LogReaderService)readers[i];
                m_readers.add(lrs);
                lrs.addLogListener(m_console);
            }
        }

        logReaderTracker.close();
       
        // Add the ServiceListener, but with a filter so that we only receive events related to LogReaderService
        String filter = "(objectclass=" + LogReaderService.class.getName() + ")";
        try {
            context.addServiceListener(m_servlistener, filter);
        } catch (InvalidSyntaxException e) {
            e.printStackTrace();
        }
    }

    public void stop(BundleContext context) throws Exception
    {
        for (Iterator<LogReaderService> i = m_readers.iterator(); i.hasNext(); )
        {
            LogReaderService lrs = i.next();
            lrs.removeLogListener(m_console);
            i.remove();
        }
    }

}

In order to test, install and start the LogService and the ConsoleLog services (in any order). Then install and start/stop the LogTester bundle. Every time it starts, it echoes a message. Now, stop the ConsoleLog, and stop/start the LogTester, nothing is displayed.

A sample session with the Felix server:

M:\felix-1.4.0>java -jar bin\felix.jar

Welcome to Felix.
=================

-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.4.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.2)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.2.1)
-> install file:///plugins/ConsoleLog_1.0.0.jar
Bundle ID: 16
-> install file:///plugins/LogTester_1.0.0.jar
Bundle ID: 17
-> install file:///plugins/log.jar
Bundle ID: 18
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.4.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.2)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.2.1)
[  16] [Installed  ] [    1] KornrLoggingBackend Plug-in (1.0.0)
[  17] [Installed  ] [    1] LogTester Plug-in (1.0.0)
[  18] [Installed  ] [    1] Log Service (1.0.0)
-> start 16
-> start 17
-> start 18
[null] BundleEvent.STARTED
-> stop 17
[LogTester] BundleEvent.STOPPED
-> start 17
[LogTester] hey, I logged that!
[LogTester] BundleEvent.STARTED
-> stop 17
[LogTester] BundleEvent.STOPPED
->

The highlighted lines were output by the ConsoleLog plugin. Of course, the LogTester and ConsoleLog bundles aforementionned work perfectly on Eclipse, on which they were developped (see figure 2 below).

Figure 2

You can download the project files:

nogunner's blog

Pointless technical stuffs are the bomb diggity of life.

September 2010
Sun Mon Tue Wed Thu Fri Sat
 << <   > >>
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Search

XML Feeds

Web Monitoring

Be sure to check my LinkLogics web monitoring application if you happen to need external monitoring.
blog tool