« Building a full-featured OSGI logging back-endUnderstanding the OSGI logging service »

OSGI-fying the apache commons-logging API

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:

3 comments

Comment from: viano [Visitor]
vianoHi: I have a problem , if I want to ctreat a log file and log information using commonning-log.jar

how to ?

thank you !
07/04/09 @ 15:38
Comment from: Toni Menzel [Visitor] Email
*----
Toni MenzelHi,

just found your blogs and complaints about Pax Logging.
Looking at your solution, i don't know if this is an april fool's joke or ment to be serious? Are you really saying that embedding the commons logging libs into each bundle is a solution?
What you showed here is counter-productive as it creates fat "self contained" bundles, sweeping out the entire service layer and replacing it with monotholic uber bundles.
I am not sure, maybe this article is too old (2008?) and does not reflect your current mindset anymore. (i hope so).
No offence, just blown away from spreading those code smells.
cheers,
Toni
04/08/10 @ 12:03
Comment from: nogunner [Member] Email
nogunnerHi Toni,

I hesitated somewhat to publish your comment, because it severely lacks arguments, and I think you're missing the big picture here. Anyway, it's not worth than some of the viagra-spam that sometimes pass the spam barrier, so I just let it.

First, the point you mention was already discussed there: http://blog.kornr.net/index.php/2008/12/18/osgi-logging-putting-it-all-together (read the comments please)

I'm not sure why you're aggressively defending Pax Logging, which is not a bad solution except it fragments _again_ the logging frameworks used by the java community, which by itself is enough to keep it at bay, but what really bothered me was all the FUD the PAX initiative seemed to use in order to justify their solution. From what I can see, bad habits are not lost.

Regarding your argument "it creates fat "self contained" bundles, sweeping out the entire service layer and replacing it with monotholic (I suppose you mean "monolithic", right?) uber bundles", it only shows you have not correctly understood what commons-logging is: it's not just a logging framework, it's also designed to be a _facade_ for a logging system that you can define at runtime. Let me put some emphasis on this: FACADE. I'm pretty sure you know how the facade pattern works, and why it makes sense to keep this facade because, well, it's widely used and can be considered a de facto standard.

It's straightforward to keep the commons-logging facade (that's 2 classes, Log and LogFactory), and just change the code behind to call the the OSGi Log Service instead of the normal commons-logging engine that is available by default. That's what the sample code I provide (2 classes, 6Kb in total, is that what you call fat?) do. Nothing more. The benefit of that is that only one logging API is used (read facade), and a common code base can be shared between OSGi and non-OSGi projects.

Now, please read the article I mentionned above, and if you have any suggestion for a better solution (and I'm sure there are), please do not hesitate to give a pointer. Pax Logging is not what _I_ call a better solution, but I'm not offended if it is for others. Just, please, stop the FUD, I'm sure you can make up better arguments than that to convince people to use your PAX stuff.
04/08/10 @ 14:43
nogunner's blog
Pointless technical stuffs are the bomb diggity of life.
November 2014
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.
powered by b2evolution free blog software