The OSGi Log Service specification describes a way for a bundle to log messages. Logging is important for users, application administrators, support teams, and developers, regardless of the application domain. In this chapter we cover the following topics:
• The logging services defined by the OSGi specification
• Writing to, reading from, and being a listener of a log
• How logging is done in Toast
• Richer logging using Equinox’s extended log service
The OSGi Log Service specification describes two services. The first is the LogService
used to write log messages, and the second is the LogReaderService
used to read log messages; these services are a pair, potentially backed by a single implementation. An application may contain zero, one, or many pairs of logging services.
These services were designed when OSGi was conceived, so it was important to define an approach to logging that worked well in embedded applications running on constrained devices such as set-top boxes. The specification does not define how these services are implemented or the destination of logged messages. In fact, applications use these services to ensure that their logging strategy is pluggable and decoupled from logging implementations.
As with most logging infrastructure, the OSGi LogService
interface defines levels at which messages are logged.
LogService.LOG_DEBUG
—Used for problem determination and may be irrelevant to anyone but the bundle developer. The Log Service specification says that implementations may choose to ignore these messages.
LogService.LOG_INFO
—Used to log information messages that are considered normal and expected.
LogService.LOG_WARNING
—Indicates that the application is still functioning but may experience problems in the future because of the warning condition.
LogService.LOG_ERROR
—Indicates that the application may not be functional and that action should be taken to remedy the situation.
The specification defines loose semantics for the log levels, and it is the application that decides the level at which a message is logged, based on the perceived importance and audience of the message. Readers of the log can use the log level to decide how to handle each message.
The LogService
provides API for writing to a log. Each method takes a level parameter as described in the preceding section. All other parameters may be null
. The most commonly used methods are shown here:
These methods create an entry in the log that captures the message, the current time in milliseconds, the log level, and an optional exception. If the message being logged is related to an OSGi service, the following logging API can be used to provide additional context for the logged message:
Since these methods take an instance of the OSGi-defined ServiceReference
class, they are typically inappropriate for use by POJO classes. Bundles traditionally obtained a ServiceReference
object as a normal part of working with the OSGi service registry, but with the introduction of Declarative Services, ServiceReference
objects are increasingly rare. For this reason, these methods are seldom used by application bundles.
While the ability to log only string messages might seem surprising, it was done for good reason. Allowing arbitrarily large objects to be logged, and potentially persisted, could be fatal for an application that is running on a constrained embedded device, such as a set-top box. As we will see in Section 17.5, “Equinox’s LogService
Implementations,” the Equinox extended log service addresses this issue.
A LogService
is intended to be a sink for logged messages and is not intended to do anything other than store logged messages for eventual distribution to others. The LogReaderService
, on the other hand, is intended for reading messages that have been written to the log.
The LogReaderService
method getLog
is used to poll the contents of the log. The method does not change the contents of the log, but rather it returns an Enumeration
of immutable LogEntry
objects, each of which describes a single entry in the log. It is not possible to change or remove a log entry, or to empty the log entirely. The LogEntry
objects are ordered with the most recent first. The following LogEntry
methods are available:
getBundle
—Returns the Bundle
that logged the entry, or null
getException
—Returns the Throwable
associated with the message, or null
getLevel
—Returns the logging level at which the message was logged; see Section 17.1.1, “Logging Levels”
getMessage
—Returns the logged message, or null
getServiceReference
—Returns the ServiceReference
associated with the logged message, or null
getTime
—Returns the value of System.currentTimeMillis
at the time the LogEntry
was created
As an alternative to polling the log, the LogReaderService
allows an application to listen for messages by adding a LogListener
. By listening to the log, the application is notified when a message is added to the log. The API to add and remove a LogListener
is as follows:
The LogListener
interface consists of the logged(LogEntry)
method that is called when a message has been logged. Listeners are free to do whatever processing they like, but, as with all callbacks, care should be taken to return quickly.
LogService
in ToastAs an example of how Toast can use the LogService
, let’s look at Chapter 7, “Client/Server Interaction,” and change the FakeAirbag
so that it logs messages when it is started up, deployed, and shut down. Use the Samples Manager to load the code for Chapter 7.
Start by adding a reference to LogService
in the DS component for org.equinoxosgi.toast.dev.airbag
:
• The LogService
interface is defined by the bundle org.eclipse.osgi.services
, so use the bundle manifest editor to add it to the Automated Management of Dependencies list of the org.equinoxosgi.toast.dev.airbag
project.
• Open the org.equinoxosgi.toast.dev.airbag
component’s component.xml
and add the LogService
as a referenced service.
• Set the reference’s cardinality to 0..1
and its policy to dynamic
. Treating the LogService
as optional makes sense since the FakeAirbag
can function just fine without it.
• Since we’re using the dynamic
reference policy, set the component’s bind
callback to setLog
and its unbind
callback to clearLog
.
The component should look like this:
Having updated the DS component, we now need to update its implementation class to use the LogService
. Let’s add some logging to the FakeAirbag
class; the changes are shown here:
The first thing to notice is that we’ve added a logInfo
method that handles the class’s logging needs. This method is called by startup
, shutdown
, and deployed
. If available, logInfo
logs to the LogService
; otherwise it falls back to the System.out
. The method also adds an identifier to the front of every logged message to make it easy to spot which class logged the message.
Let’s now turn our attention to the component’s bind
and unbind
callbacks. It is important for us to understand how DS behaves when using dynamic 0..1
referenced services. When a unary referenced service is dynamically bound and rebound, the order of events is as follows:
1. Bind a service—setLog(log1)
.
2. Bind a replacement service—setLog(log2)
.
3. Unbind the original service—clearLog(log1).
Once a LogService
has been bound and the component’s configuration satisfied, DS will always try to rebind a replacement service before it unbinds the current service. The setLog
method takes care to call the clearLog
method before allowing the field to be set when the log
field is not null
. While in this simple case calling clearLog
is not necessary, this coding pattern is useful when unbinding from a dynamically referenced service requires cleanup such as removing a listener.
Correspondingly, the clearLog
method sets the log
field to null
only if the service passed to it is identical to the service already cached in the log
field. This ensures that step 3 does not undo the binding done in step 2.
To try out our changes, run the Toast client. You should see messages logged to the console when the airbag is started up, deployed, and shut down. However, without the org.eclipse.equinox.log
bundle installed, Toast was not using logging at all.
Try again after adding the org.eclipse.equinox.log
bundle to the launch configuration. This time you will see nothing in the console! Remember, the LogService
is a message sink, so to see its contents, you need to use the console’s log
command or implement a component that uses LogReaderService
, as discussed in the next section.
LogReaderService
As we’ve just seen, writing to the LogService
is fruitless unless the LogReaderService
is available to retrieve logged messages and write them to a file, present them in a user interface, or send them to a server for processing. In this section we build a simple DS component that echoes logged messages to System.out
.
Start by creating a project called org.equinoxosgi.toast.core.log.reader
and add the DS component as shown here:
As soon as the referenced LogReader
service is available, DS will create an instance of its implementation class, ToastLogReader
, and activate it by calling the startup
method. The class ToastLogReader
is shown here:
When activated, the startup
method adds the ToastLogReader
as a listener of the LogReaderService
. As messages are logged, the LogReaderService
notifies its listeners by calling their logged
method, passing a LogEntry
that describes the logged message.
ToastLogReader
implements LogListener
, and its logged
method simply uses the information from the supplied entry—for example, the message and the bundle from which the message was logged—to build an appropriately formatted message that it writes to System.out
.
When deactivated, the shutdown
method removes the ToastLogReader
as a listener of the LogReaderService
.
Running the Toast Client with Equinox’s org.eclipse.equinox.log
bundle and our org.equinoxosgi.toast.core.log.reader
bundle included in the launch configuration gives output similar to what follows. Notice that the LogService
is being used much more than we ever realized. The property manager is logging accesses and the framework is logging service, bundle, and framework events.
Even this is only a partial list. It turns out that org.eclipse.equinox.log
, org.equinoxosgi.toast.core.log.reader
, and its prerequisite, the DS bundle org.eclipse.equinox.ds
, are not starting early enough. As a result, some logged messages are being missed or potentially falling off the back of Equinox LogService
’s in-memory buffer. To remedy this, launch Toast again, but this time set the start level for these bundles to 1 so that they start before the other bundles. Now the console will show considerably more logged messages, too many, in fact, to show here.
While we generally do not recommend that you use OSGi’s start levels, this is perhaps one of the few legitimate reasons for doing so—setting up system utilities that are widely used and need to be in place early.
LogUtility
ClassHaving learned all about the OSGi log services, you might be wondering why Toast does not appear to use the LogService
. In fact, it does, but it does so through the singleton class org.equinoxosgi.toast.core.LogUtility
. Here are some of the reasons for this structure:
Historical—Toast predates the availability of DS. Toast was originally built using the Service Activator Toolkit (SAT), which provided its own LogUtility
singleton. When we moved Toast to use DS, we decided not to change its approach to logging and instead ported SAT’s LogUtility
to Toast.
Simplicity—Requiring every DS component to reference the LogService
is not the simplest thing that could possibly work. We like to keep our code simple, and logging is something that we believe does not fit well as a service.
Logging is pervasive—Logging is needed throughout an application. If Toast were to use the LogService
, it would be necessary to pass it from each component’s implementation class down into the various domain abstractions, which is more work and more complicated than we need.
Using the LogUtility
is simple. Since the class is a singleton, residing in an exported package, every Toast bundle can reference it by importing the package. The LogUtility
provides a variety of static methods that make logging easy.
If you look at the bundle org.equinoxosgi.toast.core
, you’ll see that it contains an immediate DS component that specifies the LogService
as an optional and unary referenced service:
This is the same pattern we saw before, but implemented in one place and shared among the Toast bundles. Similarly, the component’s implementation class injects the referenced LogService
into the LogUtility
singleton. The LogUtility
’s logging methods use the LogService
when it is available and falls back to logging to System.out
when it is not.
LogService
ImplementationsThe Equinox bundle org.eclipse.equinox.log
provides an implementation of the LogService
and LogReaderService
interfaces. Equinox’s LogService
is a circular in-memory buffer with a default size of 100 entries. The buffer’s size can be configured dynamically at runtime using the ConfigurationAdmin
service, which is discussed in Chapter 12, “Dynamic Configuration.”
In Equinox 3.5 the ExtendedLogService
and ExtendedLogReaderService
interfaces were introduced. These interfaces are not part of the OSGi specification but do add some useful logging capabilities:
• Logging of an arbitrary Object
, known as the context, rather than just simple message strings; for example, it is now possible to log org.eclipse.core.runtime.IStatus
objects
• Support for multiple named loggers
• The use of an ExtendedLogEntry
to capture the log context, details of the thread on which the log entry was made, and a sequence number for the log entry
• Enabling integration with other logging frameworks, such as Apache’s Commons Logging and log4j and the Equinox logging infrastructure
The Logger
interface declares the new logging APIs and allows us to have named logs:
The ExtensionLogService
interface extends the Logger
interface to add API for locating a particular Logger
, as shown in the following snippet. Note that to use the default, anonymous logger, you can pass null
to getLogger
or use Logger
’s inherited log
methods.
The ExtendedLogReaderService
interface extends the LogReaderService
interface with the ability to add a LogListener
that is notified only of logged messages that match a specified LogFilter
.
Finally, the ExtendedLogEntry
interface extends the LogEntry
interface, adding API for querying the name of the logger, the context, thread details, and sequence number:
The ToastLogReader
class from Section 17.3 might use Equinox’s ExtendedLogService
, and specifically its ExtendedLogEntry
class, as shown here:
By querying the actual type of the LogEntry
object, it is able to handle the case where it’s really an instance of ExtendedLogEntry
and provide enhanced log output.
Logging is an important part of every application since it provides a way to communicate important information to developers, support teams, administrators, and users. Logging should not be an afterthought, but rather it should be designed into an application, with the ability to redirect or handle logged messages differently depending on the need.
We showed how the OSGi Log Service specification describes a small-scale, loosely coupled, and pluggable approach to logging. We also showed how to correctly read from and listen to the OSGi log services to ensure that logged messages are observed and handled correctly. This was demonstrated in a DS component that uses the LogReaderService
to listen to the log and writes each logged message to System.out
.
Finally, we discussed Equinox’s ExtendedLogService
and saw how it can be used to provide richer log content and to integrate with other logging frameworks.