Thursday, July 26, 2018

OSGi R7 Highlights: Log Service

The OSGi Compendium Release 7 specification contains version 1.4 of the Log Service specification which includes a number of exciting new enhancements and features.

Version 1.4 is a major update to the OSGi Log Service specification. A new logging API is added which supports logging levels and dynamic logging administration. A new Push Stream-based means of receiving log entries is also added. There is also support in the Declarative Services1.4 specification to make it easy to use the new logging API in components.

Logger


Most developers are probably familiar with SLF4J which is a popular logging API for Java. The new Logger interface is inspired by the ideas in SLF4J. The Logger interface allows the bundle developer to:
  • Specify a message, message parameters, and an exception to be logged.
  • Specify the Service associated with the message being logged.
  • Query if a log level is effective.
The logging methods of Logger support curly brace "{}" placeholders to format the message parameters into the message.

logger.error("Cannot access file {}", myFile);

Sometimes message parameters can be expensive to compute, so avoiding computation is important if the log level is not effective. This can be done using either an if block

if (logger.isInfoEnabled()) {
    logger.info("Max {}", Collections.max(processing));
}

or a LoggerConsumer which is convenient with a lambda expression.

logger.info(l -> l.info("Max {}", Collections.max(processing)));

Both the prior examples avoid computation if the log level is not effective. The latter example only calls the lambda expression if the log level is effective.

LoggerFactory


Logger objects can be obtained from the LoggerFactory service using one of the getLogger methods. Loggers are named. Logger names should be in the form of a fully qualified Java class name with segments separated by full stop. For example:

com.foo.Bar

Logger names form a hierarchy. A logger name is said to be an ancestor of another logger name if the logger name followed by a full stop is a prefix of the descendant logger name. The root logger name is the top ancestor of the logger name hierarchy. For example:

com.foo.Bar
com.foo
com
ROOT

Normally the name of the class which is doing the logging is used as the logger name. The LoggerFactory service can be used to obtain two types of Logger objects: Logger and FormatterLogger. The Logger object uses SLF4J-style ("{}") placeholders for message formatting. The FormatterLogger object use printf-style placeholders from java.util.Formatter for message formatting. The following FormatterLogger example uses printf-style placeholders:

FormatterLogger logger = loggerFactory.getLogger(Bar.class, FormatterLogger.class);
logger.error("Cannot access file %s", myFile);

The old LogService service extends the new LoggerFactory service and the original methods on LogService are now deprecated in favor of the new LoggerFactory methods.

Logger Configuration


A LoggerAdmin service is defined which allows for the configuration of Loggers. The LoggerAdmin service can be used to obtain the LoggerContext for a bundle. Each bundle may have its own named LoggerContext based upon its bundle symbolic name, bundle version, and bundle location. There is also a root LoggerContext from which all named LoggerContexts inherit so default logging configuration can be set in the root LoggerContext.

If Configuration Admin is present, then logger configuration information can be stored in Configuration Admin. This allows external logger configuration such as via the Configurator Specification.


Receiving Log Entries

The Log Service specification has never dealt with persisting or displaying log entries. It provides API for logging and another API for receiving what has been logged. This latter API can then be used to store logged information in any place appropriate for the application. Since Release 1, the LogReaderService has provided access to logged information. This service predates the formulation of the Whiteboard pattern and is thus out-of-date with OSGi best practices. For the version 1.4 update of the Log Service specification, we added a newer, more modern way to receive logged information: the LogStreamProvider service. The Log Stream Provider uses the new Push Stream API added for R7. To receive a stream of LogEntry objects pushed as they are created, use the createStream method to receive a PushStream<LogEntry> object.

logStreamProvider.createStream()
  .forEach(this::writeToLogFile)
  .onResolve(this::closeLogFile);

The stream can be created with any past history if desired.

logStreamProvider.createStream(LogStreamProvider.Options.HISTORY)
  .forEach(this::writeToLogFile)
  .onResolve(this::closeLogFile);

The LogEntry interface has also been updated to return additional information about the log entry. It now contains the name of the Logger used to create the entry, location and thread information about the source of the log entry creation, and a sequence number so that log entry creation order can be inspected.

Conclusion

Version 1.4 of the Log Service is a pretty significant update to the specification. It includes a much more modern API for both logging information and receiving logged information as well as the ability to configure the log levels of loggers and bundles. Make sure to try it out in your next project. The Eclipse Equinox framework 3.13.0 implements version 1.4 of the Log Service specification and the companion bundle registers the LogStreamProvider service if you want to use it.



Want to find out more about OSGi R7?

This is one post in a series of 12 focused on OSGi R7 Highlights.  Previous posts are:
  1. Proposed Final Draft Now Available
  2. Java 9 Support
  3. Declarative Services
  4. The JAX-RS Whiteboard
  5. The Converter
  6. Cluster Information
  7. Transaction Control
  8. The Http Whiteboard Service
  9. Push Streams and Promises 1.1
  10. Configuration Admin and Configurator
Be sure to follow us on Twitter or LinkedIn or subscribe to our Blog feed to find out when it's available.

1 comment:

  1. Sounds really nice. Thank you. But is there any recommended best practice how to integrate the LogService with SLF4J? Should I decide for one of both logging solutions exclusively or should I use both together? Actually using the OSGi LogService as main Logger seems to be no option since you would still want to use logback configurations for managing log file handling. But if you go the SLF4J route, using the OSGi LogService is kind of obsolete, isn't it? You could bridge all OSGi LogService messages to SLF4J. But then again you also could use SLF4J directly. Don't really know what to make of it. In our projects we use SLF4J and leave the OSGi log service reserved for technical messages logged by the OSGi container.

    ReplyDelete