OSGi Logging

My new job is calling for OSGi-fu, which is actually a design space that I am not very familiar with. For one thing, I have never really used Java seriously before. For another, the OSGi system suspiciously resembles the microkernel systems that I spent a fair part of my early career with; discovering that they really didn't work all that well.

Fortunately, OSGi seems to work significantly better than the microkernels ever did. The only real remaining problem is the lingering suspicion that OSGi is an attempt to recreate Erlang's Online Telecom Platform without all the nasty fault tolerance and distribution.

In any case, the system in question is intended to be a web application framework. One service provided by the framework is logging to a three database tables:

  • normal logging records,
  • application defined event records, and
  • operational alert records.

In order to support a standard logging interface, we decided to use the Log4J library as the underlying logging system, and I found the JDBCPlus Appender to record the logging messages in the database tables. (The JDBC appender supplied with Log4J is, well, marginal.) To write event and alert records, I supplied a small library with two specialized loggers and a utility class. That left two questions: How to design something appropriate for an OSGi system, and how to interface logging with the other services we intend to provide.

The figure below describes what I came up with:

LoggingDesign

This design shows six major components:

  1. The Log4J bundle itself. Log4J is available from the SpringSource Application Platform's bundle repository; keeping this (and to a lesser extent, the other off-the-shelf components) as separate bundles allows those components to be used as unchanged as possible, as well as simplifying maintenance and upgrades of them.
  2. The "logging" bundle, consisting of the extra classes to support event and alert records. This bundle imports packages from Log4J to implement the specialized loggers.
  3. The Log4J configuration bundle; this bundle imports the JDBCPlus appender classes from that bundle, below, and injects the appropriate configuration into the stateful Log4J bundle. By keeping this separate from the "logging" bundle above, I intend to enable reuse of the logging bundle---it does not depend on any special environment, and can be used with Log4J and the JDBCPlus appender anywhere.
  4. The JDBCPlus appender bundle is another off-the-shelf component, although I needed to add the OSGi metadata to it myself.
  5. The "datasource" bundle, which will ultimately be replaced by our framework's configuration system, implemented by one or more bundles. In the prototype, this bundle provided a sql.DataSource OSGi service with a property indicating it was to be used for logging. The configuration bundle discovered that service and used it to configure Log4J.
  6. A JDBC driver bundle (MySQL's, in this case), another off-the-shelf component that required bundleization.

I used a service interface between the datasource bundle and the configuration bundle to provide a "firewall"---to allow each side (particularly the database side) to be replaced without affecting the other side. The basic flow of the initial configuration is shown in the next figure:

LoggingConfiguration

Suppose that the application bundles, Log4J, logging, and configuration bundles have already been installed and started in the OSGi container. In this situation, the application functions normally but does not produce any logging output, because no appenders have been configured. When the datasource bundle is brought up, it resolves against the JDBC provider bundle and registers the DataSource service (step 1). The configuration bundle (through the use of OSGi Dynamic Services) accepts the new service, sets up the JDBCPlus appender and configures Log4J to use the appenders and to log to the database tables (step 2).

If the datasource service is set up in the OSGi container first, a similar process occurs; when the configuration bundle starts, the DataSource service is injected into it, and it then configures Log4J.

If the datasource bundle needs to be taken down, the reverse procedure takes place:

LoggingDeconfiguration

In this case, removal (or stopping) the bundle causes the service to be unregistered, which causes it to be withdrawn from the configuration bundle (step 3). When that happens, the configuration bundle deconfigures Log4J, removing the appenders it added (step 4). The applications, however, continue unimpeded.

My implementation makes use of OSGi's Dynamic Services system to manage the service between the datasource and the configuration. Dynamic Services is not able to use all of the features available to OSGi services, but works well (in this case) for dependency injection. The service as used by the configuration bundle is defined for 0..1 cardinality, allowing the configuration bundle to accept or remove the service at any point. I used bnd to create the bundles, and the following bnd directive for the configuration bundle:

Service-Component: my.package.logging.config.JDBCPlusAppenderConfig; \
  dataSource="javax.sql.DataSource(component.purpose=my.package.logging)"; \
  dynamic:=dataSource; \
  optional:=true

This causes Dynamic Services to call methods in my JDBCPlusAppenderConfig class when the appropriate DataSource service is available. The methods are:

public void setDataSource(DataSource s) { ... }
public void unsetDataSource(DataSource s) { ... }

On the datasource bundle side, I used the following bnd directive:

Service-Component: my.package.logging.datasource.DataSource; \
  provide:=javax.sql.DataSource; \
  properties:="component.purpose=my.package.logging"

gloria i ad inferni
faciamus opus

Return to Top | About this site...
Last edited Sat Aug 8 03:29:10 2009.
Copyright © 2005-2012 Tommy M. McGuire