After reading about this, I felt like I understood it and now I am left baffled. Here is what I expect and what I did:
I expect to log into Karaf, reload my bundle, and run log:tail
and eventually see a log message like this:
13:28:47.265 INFO [Blueprint] You just created a class called: MyClass.
Technologies used: - OSGI Container implemented by Apache Karaf - Blueprint implemented by Aries
My OSGI bundle imports the pax logger from Karaf
org.slf4j.*; provider=paxlogging
to my understanding, this means that a reference to to the singleton logger of Karaf will be provided at runtime to my application, which only uses an API.
My classes use the SLF4J interface, so the dependency slf4j-api:slf4j-api:1.7.26
exists in my project.
A class exists
Class serves a model
public class MyClass {
private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);
public MyClass() {
LOGGER.info("You just created a class called: {}", this);
}
@Override
public String toString() { return "MyClass" };
}
I just followed the specifications for an OSGI LoggerFactory:
Consumers of this API must not implement this type https://osgi.org/specification/osgi.cmpn/7.0.0/service.log.html#org.osgi.service.log.LoggerFactory
Blueprint XML
<?xml version="1.0" encoding="UTF-8" ?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
<description>
A sample Blueprint XML to demonstrate
the initialization procedure in the hopes of reproducing a problem.
</description>
<bean id="myclass1" class=com.stack.MyClass/>
</blueprint>
Got this figured out:
The SLF4J API compiled in the bundle is part of the story. The rest is provided in Karaf / Felix by org.ops4j.pax.logging.pax-logging-api
. This thing does stuff in the background:
PaxLoggingManager
object (Slf4jLoggerFactory.setPaxLoggingManager(manager)
, where manager is new OSGIPaxLoggingManager(bundleContext)
). The method getLogger
within this singleton returns a new Slf4jLogger(name, paxLogger)
object (where name is usually a class name and the paxLogger is either from a FallbackLogFactory.createFallbackLog(FrameworkUtil.getBundle(Logger.class), name)
or m_paxLogging.getLogger(name, Slf4jLogger.SLF4J_FQCN)
. Slf4jLoggerSo, it is necessary to bind to this specific Slf4jLoggerFactory
(implements ILoggerFactory
) so that all of the classes in the bundle get the correct reference when they call getLogger(class)
. The problem with Aries Blueprint, it seems, is that it lazily binds the SLF4J API to the implementation provided by headers org.ops4j.pax.logging.pax-logging-api
. So, I followed Christian Schneider's advice and created a top-level reference in Blueprint that forced Blueprint to wait for the Pax Manager to be ready:
<reference id="logService" interface="org.osgi.service.log.LogService" availability="mandatory" activation="eager"/>
Then the other top-level managers can depend on this by using depends-on
:
<bean id="MyRegistry" class="com.foo.MyRegistry" scope="singleton" factory-method="getSingletonInstance" depends-on="logService">
Of course, I needed to add the following to my OSGI MANIFEST.MF
Import-Package:
org.slf4j;version="[1.7.0,2.0.0)",
org.osgi.service.log,