« Someone out there is a total moronBuilding a full-featured OSGI logging back-end »

OSGi logging: putting it all together

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

5 comments

Comment from: Dave Joyce [Visitor] · http://www.jroller.com/daveslife/
*****
So every bundle that wants to produce log messages needs to include the slf4j-osgi or commons-logging-osgi jar in its Bundle-Classpath and, therefore, maintain a 'local' copy of the jar(s)? Do I understand that correctly? As I understand that, it would necessitate multiple copies of the same jars to be loaded repetitively, one set per bundle classloader.

In your view, is there some way to 'point' to a single set of osgi-bridge jars in the Bundle-Classpath of several bundles and reduce jar duplication and the maintenance nightmare that comes out-of-the-box with library duplication?

Awesome articles, BTW. Thank you SO MUCH for finally addressing this tricky topic!
01/22/09 @ 07:52
Comment from: nogunner [Member] Email
Hi Dave,

Unfortunately, there's hardly any other way:
- Either your software uses the OSGi LogService directly, and you don't need any additional jar, but your code is bound to OSGi.
- Either you use a standard (or pseudo-standard, whatever) logging API, such as log4j or commons-logging, and you definitively need to have an adapter that makes the bridge to the LogService.

The way the LogService is designed, it has to be specifically retrieved by the Bundle that uses it, because there is no way to specify the calling bundle in the log calls. Consequently, there's hardly any other option than duplicating the same code in every bundle.

However, to be totally honest, although jar duplication is really an issue I take seriously, in this particular case, given the fact that the commons-logging API is pretty stable (the OSGi adapter should work without any change with the initial 2002 release of commons-logging), well, I'm inclined to consider this as a minor issue. Of course, your mileage may vary.
01/22/09 @ 09:50
Comment from: Niranjan [Visitor] · http://nksharma0624.blogspot.com/
*****
Very Nice and Good Job for handling the highly "Confused Topic by mob..."..

Just couple of thoughts:
1. How about having a aspect using AspectJ to use the OSGI Services and attach the OSGI services to SLF4J or JCL facade...
This way you are not tightly coupled...

Thoughts?
06/26/09 @ 17:47
Comment from: Leonid Ilyevsky [Visitor]
*****
This article was very helpful to me.
I actually found a solution without the need of embedding those jars in every bundle. It may not work for everybody, but for me it was actually what I was looking for.
I wanted to have the logging on the server to be configured in one place. My thinking was: even though there will be multiple bundles running, together they constitute an application, and it will make sense to define everything related to the logging in one place.
So here is how I did it. I created one bundle that embeds the slf4j-api,slf4j-log4j12, and log4j jar files, as described in the article. It exports the org.slf4j package. All other bundles import org.slf4j, and in the code they use pure slf4j stuff. Everything works fine, and I can control all log4j configuration in one file. I used rolling file appenders, and I configured different files for different packages, so each bundle has its own log file.
06/29/09 @ 19:36
Comment from: nksharma [Visitor] · http://nksharma0624.blogspot.com/
So far..Mixing the loggers works for me and Now I am experimenting Leonid's idea.
My question is how does slf4j binds a logger..I am not sure..Can you publish your code Leonid.
06/29/09 @ 22:54

This post has 1 feedback awaiting moderation...

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
PoorExcellent
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)
nogunner's blog

Pointless technical stuffs are the bomb diggity of life.

March 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 31      

Search

XML Feeds

Web Monitoring

Be sure to check my LinkLogics web monitoring application if you happen to need external monitoring.
powered by b2evolution