Chapter 17. Logging

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

17.1 The Log Service Specification

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.

17.1.1 Logging Levels

As with most logging infrastructure, the OSGi LogService interface defines levels at which messages are logged.

LogService.LOG_DEBUGUsed 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_INFOUsed to log information messages that are considered normal and expected.

LogService.LOG_WARNINGIndicates that the application is still functioning but may experience problems in the future because of the warning condition.

LogService.LOG_ERRORIndicates 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.

17.1.2 Writing to the Log

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:

image

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:

image

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.

17.1.3 Reading the Log

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:

getBundleReturns the Bundle that logged the entry, or null

getExceptionReturns the Throwable associated with the message, or null

getLevelReturns the logging level at which the message was logged; see Section 17.1.1, “Logging Levels

getMessageReturns the logged message, or null

getServiceReferenceReturns the ServiceReference associated with the logged message, or null

getTimeReturns the value of System.currentTimeMillis at the time the LogEntry was created

17.1.4 Listening to the Log

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:

image

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.

17.2 Using the LogService in Toast

As 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:

image

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:

image

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.

Separation of Concerns

Consider carefully before coupling your application to the OSGi Log Service interfaces. Creating a dependency on any OSGi-defined interface typically means that you need to include the OSGi interfaces, and possibly implementations, when packaging your application, even when it is executing outside the OSGi framework.

You should consider defining an application-specific logging API that can be implemented in terms of the OSGi service interface, as we do with the Toast LogUtility, discussed in Section 17.4, “Toast’s LogUtility Class.”

17.3 Using the 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:

image

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:

image

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.

image

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.

17.4 Toast’s LogUtility Class

Having 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:

image

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.

17.5 Equinox’s LogService Implementations

The 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:

image

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.

image

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.

image

Finally, the ExtendedLogEntry interface extends the LogEntry interface, adding API for querying the name of the logger, the context, thread details, and sequence number:

image

The ToastLogReader class from Section 17.3 might use Equinox’s ExtendedLogService, and specifically its ExtendedLogEntry class, as shown here:

image

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.

17.6 Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.135.107