Archives for: December 2008

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:

12/01/08

Permalink 02:42:05 pm, by nogunner Email , 1701 words   English (US)
Categories: Web

Password Recovery: the good, the bad, and the procedure

The password recovery procedure is a major element of security for any web site that contains a login form or any form of authentication. All too often, however, it is left as a minor point of the development stage, and rarely gets a specification of its own, which leads to several security issues, even on major sites.

A good password recovery procedure has to be at the optimal level of compromise between security and usability. When the procedure is too easy to bypass, the security is compromised. When it's too complicated, it unnecessarily annoys the users, who may possibly give up at some point.

This simple rule of best trade-off between security and usability applies to most web sites, but not to some of them where the security needs to be psychologically enhanced. For instance, some categories of web sites (mainly online banks) define an overzealous security strategy, because they need to not only provide security, but also a strong feeling of security to the end-users. Most of the measures dedicated to the creation or recovery of accounts, such as not using email but only postal mail to send any information, sending the login and password in two separate letters, using one-time password generator devices, or just clicking on a random grid to write the password code, are more trouble and additional cost than real security, but it's nevertheless important to create a psychological evidence that the web site is secure. As for a lot of other things, it's not enough to have a secure web site: if the users do not feel the security is at the level they expect it to be, then the procedure may be technically secure, but wrong from a marketing point of view.

I'll detail some techniques commonly used, and finally provide a sample password recovery workflow, suitable for most web sites.

  • "Click here to receive a new password": this is very very wrong. Resetting a password to something random is not inherently incorrect, but doing it because _someone_ from the internet clicked on a button is not a good pattern. This may result in a denial of service for your users, simply because someone thinks it's funny to request a new password for them (and I'm not even mentionning funny things to do with a script requesting a new password every minute or every day).

    The screenshots below are taken from a real site.

  • "Click here to receive an email that contains a link to reset your password": The user receives, in a mail, not the password but a secure link (https) to a web page where the password can be reset. This is a good trade-off between ease of use and security. The only drawback is that the user receives a weird email every time someone clicks on the button, so you may define some additional restrictions on the action. For instance, only 3 mails per hour, or answer a personal question. Otherwise, it's a strategy I suggest to use in most cases.

  • "Answer a few question about you": this one is pretty common.
    For instance, the Yahoo procedure is the following: ask the login id, a captcha, some birthday information, the country you live in, a postal code, and the middlename or birth date. Wow, that's a lot of questions, so that's secure, right? WRONG. Any of your family members can answer it, and probably most of your friends, former friends, employers, and former employers. That's a crowd! And moreover, thinking about it a little further, imagine that you want to hack the account of someone, how long do you think it would take to collect this information from a co-worker, from a friend, from someone you meet at a bar ? Not more than 5 minutes, on average. Even less after a few drinks.




    Note that any person asking me my favourite author will have an immediate answer, that's not something I'm willing to hide, specially if I've forgotten that this is the secret answer I set up a few months ago for my email account... OMG! Now it's published on a blog! I'm so screwed!

  • "Answer a secret question": a variant from the one above. For instance, Google proposes some "security question": "what is your library card number?", "what was your first phone number?", "what was your first teacher's name", etc, which are all much better than yahoo's (the information required are harder to social-engineer) EXCEPT from people that either can get a physical access to your stuff, or people who actually GOT this information, for instance anyone that was in your circle of friends when you had your first phone number (am I the only one that finds it weird that a security question is based on an information that you actually spent a lot of time spreading around?). So the "security question" should actually be rephrased "security question to prevent people from accessing your account, except when they know you, even remotely". To be honest, I don't really think the library card question is that bad, because it still requires a physical access to a personal object. However, any of my co-workers can easely access my wallet, I don't take it with me when I go to lunch or when a biological urge takes over. Of course, I trust my coworkers, but a good security procedure shall NOT be based on my trust and beliefs, it shall also take into account (at least partially) the fact that I may be social-engineered.

    But the worse is still to come, it's the "write your own question". Have you ever had access to a database storing all the "own questions and answers" in plain text ? I did. When you let people choose their questions, not all of them, but a significant number of them, choose something trivial, based on a pun, on a date as complicated as the birth year of their child, or the name of their dog. This is the worst, because you cannot guarantee that the question has any level of security. And yet, you are responsible for the security, because you designed it the way it is.

Anyway, the Google's procedure is not that bad overall, because they actually use the secret question only after 5 days of inactivity on the account, which limits the possibility of hacking to the time periods where people can't access their mail (which means, for most people, during their holidays, when they are at the hospital, on a long trip, or anything else you can easely imagine). Just never leave your google account more than 5 days, and you're secure. The rule itself is a little weird, because it assumes that people are unlikely to be 5 days straight without checking their email, which may be totally correct in the silicon valley, but hardly applies to non-geek people.

The right level of security

So, if the simple rule to apply is "always find the right level of security", how should the typical password recovery workflow be designed ? It depends, of course.

The very first element you have to figure out is the secure perimeter that you are going to use in your user's environement. This secure perimeter defines how you are going to send recovery information, because you trust it against usurpation and disclosure. You don't need to trust it absolutly, just to trust it enough.

For most banks (in Europe at least), the only acceptable perimeter is the physical home of their customers, that's why they generally send the customer information there. Of course, this is not totally secure either, because people can still have their letters stolen, but absolute security does not exist in the real world, and this is the compromise that was defined in their security strategy. For phone operators, it can also be a mobile phone number to which an SMS (Text Message) is sent, it really depends on the data available. For an intranet in a corporate or academic environement, the secure perimeter may be the web administrator office, from whom you personally get a new password (in this case, the procedure often includes 10 minutes of ranting).

For most online sites, the secure perimeter is the email account. Some may argue, but the fact is, it's an acceptable compromise as long as you can admit that if someone can illegally access the user's email account, accessing your web site looks like a minor issue. However, using email as a mean to communicate with the user does not imply sending the recovery information in plain text (the email account may be compromised anytime), that's why it's much better to send a link to a secure password recovery page, rather than actually sending the password itself.

For the most standard cases (of internet web sites), I suggest the following:

  • Define the User Email as the secure perimeter. In my humble opinion, it's the best solution for standard web sites. My personal position is that the users are responsible for their email account. If it's broken or hacked, it's sad, but it's far away from my responsibility as a web site editor. Mentionning explicitly in the ToS that the password recovery procedure will use the user email is a good practice.
  • Don't use crappy personal question as a basis for security. It's not secure enough per se, but you can use it as an additional layer, for instance asking a personal question BEFORE sending a password retrieval email _is_ a good pattern, because it is only used to prevent the user from receiving annoying emails when he's not the one asking for it, and definitely not as an element of security.
  • Do not send password in emails. Instead, send a link to your site, with an HTTPS connection, where the new password can be changed (never change the password automatically, if the request wasn't done by the user, it would result in a (partial) denial of service).
  • Make sure the link is disabled server-side at some point (for instance, using a hashed parameted in the URL, and disable it accordingly in the database after the password was changed, and after a few days in any case).

So, here is the workflow, graphically (click to enlarge):

(the schema was done with balsamiq, a great tool).

nogunner's blog

Pointless technical stuffs are the bomb diggity of life.

December 2008
Sun Mon Tue Wed Thu Fri Sat
 << < Current> >>
  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 31      

Search

XML Feeds

Web Monitoring

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