Okay, firstly, I hold up my hands and say, ‘I know almost nothing about this topic but what you read here. I’m learning a lot as I go.’
Having decided to do some development while I’m waiting for someone to provide me with gainful employment (my CV) I started with a lot of reading and I’ve now moved on to coding. Being in the foothills of the learning curve this coding is rather trial-and-error stuff, many mistakes are being made, but I’m learning a lot by doing. Here are a few things I’ve learned today about OSGi and logging.
Kudos to Hall et al. for their excellent book OSGi in Action: Creating Modular Applications in Java for getting me off to a sound start, and to no gunner’s blog for confirming a few things and providing the raw material for this exercise.
A basic feature of pretty much all software is the production of logs. Omneity will be no different, so I need to implement logging. The OSGi specification defines a logging service and Felix (the OSGi framework I’m using as my reference for Omneity) provides basic a logging service. However, this basic logging service does nothing without LogListeners to receive each LogEntry and do something with it (like print the message out to the console).
The OSGi logging system is accessed by clients through the LogService.log() method. So far, so simple. Call the log() method, this sends the log message to the logging service, which dispatches the message to any attached LogListeners. These LogListeners then handle the output.
One small wrinkle though is that you also have to discover the service before calling log(). And it get’s worse. The logging service might suddenly disappear and if it does the discovered logging service you were using log() to send your log messages to will be gone and you log() call will throw an exception. Not cool.
This is the nature of OSGi development with services. (I gather that this issue can be mitigated somewhat by using frameworks like iPOJO. It’s on my to do list to look into iPOJO, which on face value looks like the most suitable for Omneity.)
To protect yourself against the dynamic nature of logging services you need to program defensively. This means that each time you want to log a message you need to check that the logging service is good (or discover the logging services available) and then call the actual logging service’s log() method. Of course, you need to make this a synchronous operation because the service could still vanish in the short time between discovery and use.
Another factor to consider is the widespread use of the SLF4J api. Almost every Java developer is familiar with this façade, so it makes sense to use it unless there is a compelling reason to do otherwise. And then there are all those third-party packages that use the SLF4J api and can be used by your system unchanged with this approach.
Considering these three points together, it should be clear that since one needs to write some log handling code to manage the dynamism inherent in the logging service, providing that handling behind the SLF4J façade makes perfect sense. This approach kills two birds with one stone and is suitable for a wide variety of projects with basic logging requirements.
If you’re interested in the final product, you will find it written up here, along with links to sources etc. This post is more about how I got there.
The starting code I obtained from no gunner’s blog worked ‘out of the box’, but I wanted to update the SLF4J api and Logback to their latest versions.
Starting with the slf4j-osgi JAR I simply replaced the relevant version identities in the pom.xml and rebuilt (actually I also took the opportunity to replace the OSGi references. I did this more so I have a consistent build throughout Omneity than any technical reason—besides, at this point in my learning the fewer variables I have to think about the better). I then discovered that the code needed a tweak because the behaviour of MessageFormatter.format() had changed. Fairly trivial, and everything seemed happy.
I then looked at the only other component I was concerned with, the LogListener. This is contained in the LogbackConnector bundle (previously LogbackBundle). At first this seemed like more of a challenge, but it also turned out to be easy.
Initially I looked at carrying on with the solution originally implemented, embedding the updated logback JAR files in the bundle. This proved to be ‘challenging’. A naïve update to the pom.xml produced a build but when the bundle was loaded into Felix a long string of dependencies started to pop up.
This was in part due to the way the bundle had been produced using a default maven-bundle-plugin configuration. However, as I started to add in dependencies it quickly became apparent that without a much more thorough understanding of how logback worked I was not going to get anywhere.
Knowing that many standard JARs are now being provided with OSGi headers I took a peek inside the logback JARs and was delighted to see they had bundle metadata. I removed the embedded JARs from the LogbackConnector bundle, loaded the logback JARs into Felix and tried again.
Now SLF4J problems surfaced. (Long story short) The simple solution was to load the slf4j-api as a bundle (yup, it was bundled too).
One small wrinkle worth mentioning is that initially, when I had dropped in a test bundle to call the logging system, I forgot to remove an embedded version of the slf4j-api. This caused a class loading error because both the embedded slf4j-api and the slf4j-api bundle where supplying different classes for the ILoggerFactory. It was easily solved by removing the mistaken embedding of slf4j-api from the test bundle, but it did cause a few minutes head scratching.
All of this investigating, trying, diagnosing, and eventually solving, took about five hours but it was a valuable five hours as I learned a lot about OSGi, reading OSGi error messages, the relationships between class loaders and between bundles. I learned about using Maven to package bundles and how to correct issues with said bundles. And finally, I learned a little about tinkering with Java code. Oh, and I learned a bit about git too.
If you have a few moments, and especially if you are an expert in OSGi, Java, or Maven, I would appreciate any feedback.