Using Pax Logging API without backend

This page contains information about OSGi internals and how pax-logging-api bundle alone allows bundles to use any supported logging API/facade.

What happens during OSGi runtime startup

As mentioned in Download page, Pax Logging is usually installed as part of OSGi runtime (for example, Apache Karaf server). Usually pax-logging-api and specific pax-logging-* backend of choice are installed as early bundles with low start level.

To summarize how it works, let’s describe the most fundamental scenario, where only pax-logging-api bundle is available.

pax-logging-api bundle starts early and doesn’t require anything beyond what OSGi core provides. The narrowed down list of imports and exports is this (with a little formatting):

Import-Package: javax.xml.parsers, org.osgi.framework; version = [1.0.0,2.0.0), org.osgi.framework.wiring; version = [1.0.0,2.0.0), org.osgi.service.event; version = [1.0.0,2.0.0); resolution := optional, org.osgi.service.log; version = [1.3.0,2.0.0), org.osgi.util.tracker; version = [1.0.0,2.0.0), org.w3c.dom Export-Package: org.apache.avalon.framework.logger; version = 4.3 org.apache.commons.logging; version = 1.2.0 org.apache.commons.logging; version = 1.1.3 org.apache.commons.logging; version = 1.0.4 org.apache.commons.logging.impl; version = 1.2.0 org.apache.commons.logging.impl; version = 1.1.3 org.apache.commons.logging.impl; version = 1.0.4 org.apache.juli.logging; version = 9.0.19 org.apache.juli.logging; version = 8.5.40 org.apache.juli.logging; version = 7.0.94 org.apache.juli.logging; version = 6.0.18 org.apache.juli.logging; version = 5.5.28 org.apache.log4j; version = 1.2.17 org.apache.log4j.config; version = 1.2.17 org.apache.log4j.helpers; version = 1.2.17 org.apache.log4j.or; version = 1.2.17 org.apache.log4j.pattern; version = 1.2.17 org.apache.log4j.spi; version = 1.2.17 org.apache.log4j.xml; version = 1.2.17 org.apache.logging.log4j; version = 2.12.0 org.apache.logging.log4j; version = 2.9.1 org.apache.logging.log4j.message; version = 2.12.0 org.apache.logging.log4j.message; version = 2.9.1 org.apache.logging.log4j.simple; version = 2.12.0 org.apache.logging.log4j.simple; version = 2.9.1 org.apache.logging.log4j.spi; version = 2.12.0 org.apache.logging.log4j.spi; version = 2.9.1 org.apache.logging.log4j.status; version = 2.12.0 org.apache.logging.log4j.status; version = 2.9.1 org.apache.logging.log4j.util; version = 2.12.0 org.apache.logging.log4j.util; version = 2.9.1 org.jboss.logging; version = 3.4.0.Final org.jboss.logging; version = 3.3.0.Final org.knopflerfish.service.log; version = 1.2.0 org.knopflerfish.service.log; version = 1.1.0 org.ops4j.pax.logging; version = 1.11.0 org.ops4j.pax.logging.avalon; version = 1.11.0 org.ops4j.pax.logging.slf4j; version = 1.11.0 org.ops4j.pax.logging.spi; version = 1.11.0 org.osgi.service.log; version = 1.3 org.slf4j; version = 1.7.26 org.slf4j; version = 1.6.6 org.slf4j; version = 1.5.11 org.slf4j; version = 1.4.3 org.slf4j.event; version = 1.7.26 org.slf4j.helpers; version = 1.7.26 org.slf4j.helpers; version = 1.6.6 org.slf4j.helpers; version = 1.5.11 org.slf4j.helpers; version = 1.4.3 org.slf4j.spi; version = 1.7.26 org.slf4j.spi; version = 1.6.6 org.slf4j.spi; version = 1.5.11 org.slf4j.spi; version = 1.4.3

As visible in Export-Package all public packages from all supported logging APIs/facades are exported - sometimes with different versions. For some libraries it was easy to specify list of packages constituting public API, in some cases it wasn’t trivial (Log4J1…).

When bundle is in RESOLVED state, it can already provide the exported packages. So even before pax-logging-api bundle transitions to ACTIVE state, other bundles may already get resolved and wire to the exported API packages. That’s enough for all the bundles to be able to invoke successfully code fragments like this:

org.slf4j.LoggerFactory.getLogger(name).info("INFO using SLF4J");

What actually happens?

Simple call to org.slf4j.Logger#info() is not trivial underneath. Especially if org.slf4j package is exported by Pax Logging API and if pax-logging-apiis not even started (ACTIVE). What’s going on underneath?

  1. Internally, all logging APIs/facades are supported by singleton instance of internal instance of org.ops4j.pax.logging.OSGIPaxLoggingManager class.

  2. This class is used by all facades to obtain an instance of org.ops4j.pax.logging.PaxLogger internal logger. All facades provide specific loggers (e.g., org.ops4j.pax.logging.slf4j.Slf4jLogger implementing org.slf4j.spi.LocationAwareLogger) that delegate to this internal PaxLogger.

  3. This internal logger is backed by org.ops4j.pax.logging.internal.TrackingLogger which is dynamically configured with currently available OSGi service of org.ops4j.pax.logging.PaxLoggingService interface.

  4. org.ops4j.pax.logging.PaxLoggingService interface is provided by an OSGi service registered by one of pax-logging-service, pax-logging-logback or pax-logging-log4j2 backends and simply delegate to framework specific loggers.

  5. However, if no Pax Logging backend is available pax-logging-api itself provides org.ops4j.pax.logging.spi.support.FallbackLogFactory class that’s used to obtain instances of org.ops4j.pax.logging.PaxLogger. This time, logger returned by FallbackLogFactory is not tracking and dynamically awaiting actual logging implementation, this fallback logger actually does what stems from its name - provides fallback logging mechanism.

As you may have guessed, fallback logger is used in interregnum times, where no actual logging backend is available. There are two valid behaviors configurable for this fallback logger:

  • write logging events to standard output

  • write logging events to configured file

Configuration of fallback logger

Because fallback logger is used so early, we can’t even use Configuration Admin to configure its behavior. The only available configuration is through:

  • system properties (in Karaf, configured via etc/system.properties) - java.lang.System#getProperty()

  • bundle context properties (in Karaf, configured via etc/config.properties) - org.osgi.framework.BundleContext#getProperty()

System properties have higher priority than context properties.

These are the system/context properties that affect the behavior of fallback logger:

Property name

Description

Default value

Property name

Description

Default value

org.ops4j.pax.logging.DefaultServiceLog.level

Specifies threshold for fallback logger used behind all facades handled by pax-logging-api.

DEBUG (that’s why Karaf contains org.ops4j.pax.logging.DefaultServiceLog.level = ERROR in etc/system.properties)

org.ops4j.pax.logging.useFileLogFallback

Selects file-based fallback logger. The value should be writable filename. Turning on this logger will enable synchronization and register singleton stream used by all instances of org.ops4j.pax.logging.spi.support.FileServiceLog.

 

Integration tests available in Pax Logging show different usages of fallback logger. The important thing is - before pax-logging-api bundle becomes ACTIVE, it’ll only provide non-file based default logger. The reason is synchronization. The important aspect of fallback logging is that all actual logging frameworks (like Log4J2) have notion of status logger - some logger that used by the logging framework itself to provide details about internal behavior and possible problems! So even if real Log4J2 is used by user’s code, Log4J2 itself still needs a mechanism to log internal details about its operation.

So, file-based fallback logger needs synchronization and this happens via some OSGi services registered by pax-logging-api bundle that’s already ACTIVE.

Fallback logger uses standard, hardcoded layout that’s writing information like this:

bundle-symbolic-name [category name] level : message (optional stack trace)

For example (from org.ops4j.pax.logging.it.karaf.CleanIntegrationTest):