OSGi Console Logger

Logging in OSGi can be very tedious. OSGi provided a very robust framework for logging but it misses one point:

The application developer does not want to implement his own logger. He wants to just use a logging implementation!

And this is where OSGi misses the point with its very sophisticated framework. There is a framework but there is no implementation of the last link in the chain: the output of the log.

Logging should be dead simple. But in OSGi it just isn't that simple. There is the LogService, the LogReader and the LogListener … and the whole time I just think “Hey, I just want to output some messages!”

Both popular OSGi frameworks (Eclipse Equinox and Apache Felix) have implementation for LogService and LogReader but the LogListener is not implemented for the end-user (the application developer).

So here is a trivial implementation of a LogListener , using System.out:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

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

public class ConsoleLogListener implements LogListener {

	private static final String LOG_LEVEL_INFO = "INFO ";
	private static final String LOG_LEVEL_WARN = "WARN ";
	private static final String LOG_LEVEL_ERROR = "ERROR";
	private static final String LOG_LEVEL_DEBUG = "DEBUG";

	private static final String[] LOG_LEVELS = { 
		LOG_LEVEL_ERROR, LOG_LEVEL_WARN, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG 
	};

	private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	private int logLevel = LogService.LOG_INFO;

	public void bindLogReader(LogReaderService logReader) {
		logReader.addLogListener(this);
	}

	public void unbindLogReader(LogReaderService logReader) {
		logReader.removeLogListener(this);
	}

	@Override
	public void logged(LogEntry entry) {
		if (!isLoggable(entry)) {
			return;
		}

		String message = formatLogTime(entry.getTime()) + " " + formatLogLevel(entry.getLevel())
				+ " - " + entry.getBundle().getSymbolicName() + " - " + entry.getMessage();

		if (entry.getServiceReference() != null) {
			message = message + " - [ " + entry.getServiceReference().getClass().getName() + " ("
					+ entry.getServiceReference().getProperty("service.id") + ") ]";
		}
		
		System.out.println(message);
	}

	private String formatLogTime(long time) {
		return dateFormat.format(new Date(time));
	}

	private String formatLogLevel(int logLevel) {
		return LOG_LEVELS[--logLevel];
	}

	private boolean isLoggable(LogEntry entry) {
		boolean loggable = false;

		if (logLevel <= entry.getLevel()) {
			loggable = true;
		}

		return loggable;
	}

}

The message building method is totally unoptimized and is solely for demonstration purposes.

But that isn't doing the trick yet as it is nowhere registered in the logging framework. The easiest way to register it is to make a declarative component of it and bind the LogReader implementations to it, file OSGI-INF/service-log.xml.

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="miworkplace.log.console">
   <implementation class="miworkplace.log.ConsoleLogListener"/>
   <reference bind="bindLogReader" cardinality="0..n" interface="org.osgi.service.log.LogReaderService" name="LogReaderService" policy="dynamic" unbind="unbindLogReader"/>
</scr:component>

Don't forget to add the component to the MANIFEST.MF:
Service-Component: OSGI-INF/service-log.xml