javaosgislf4jservice-providerbnd

Usage of "slf4j.api" in an OSGi bundle requires extender "osgi.serviceloader.processor" after update to 2.x


I have created a small reproducer project on GitHub: slf4j-experiment

Basically all I need is an OSGi bundle where some code is using slf4j-api.

Example:

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = SomeService.class)
public class SomeServiceImpl implements SomeService {
    private static final Logger LOG = LoggerFactory.getLogger(SomeServiceImpl.class);

    @Override
    public String doIt(String first, String second) {
        LOG.info("SomeServiceImpl: doIt(..)");
        return operation(first, second);
    }

    static String operation(String first, String second) {
        return first + second;
    }

    @Activate
    public void activate() {
        LOG.info("SomeServiceImpl: activate");
    }

    @Deactivate
    public void deactivate() {
        LOG.info("SomeServiceImpl: deactivate");
    }
}

And in my build.gradle I have:

dependencies {
    compileOnly 'org.osgi:osgi.annotation:7.0.0'
    compileOnly 'org.osgi:org.osgi.service.component.annotations:1.3.0'

    implementation 'org.slf4j:slf4j-api:2.0.11'
    runtimeOnly 'org.slf4j:slf4j-simple:2.0.11'
    
    // ...
}

This is working as expected with slf4j 1.7.36

Now with the version 2.0.11 of slf4j, because of usage of service provider, there are new constraints modeled in the OSGi metadata and now I am stuck at:

Resolution failed. Capabilities satisfying the following requirements could not be found:
    [<<INITIAL>>]
      ⇒ osgi.identity: (osgi.identity=slf4j.simple)
          ⇒ [slf4j.simple version=2.0.11]
              ⇒ osgi.wiring.package: (&(osgi.wiring.package=org.slf4j)(version>=2.0.0)(!(version>=3.0.0)))
                  ⇒ [slf4j.api version=2.0.11]
                      ⇒ osgi.extender: (&(osgi.extender=osgi.serviceloader.processor)(version>=1.0.0)(!(version>=2.0.0)))

It seems that I need an additional bundle, maybe provided by SPI Fly, but I could not understand how…


Solution

  • Your example declares:

    slf4j.api;version='[1.7.36,1.7.37)',\
    slf4j.simple;version='[1.7.36,1.7.37)'
    

    which is (I'm not a fan of gradle + OSGi + bnd) resolved to slf4j-api-1.7.36.jar from Maven central.

    This is proper OSGi bundle with:

    Import-Package: org.slf4j.impl;version=1.6.0
    Export-Package: org.slf4j;version=1.7.36, org.slf4j.spi;version=1.7.36
     , org.slf4j.helpers;version=1.7.36, org.slf4j.event;version=1.7.36
    

    However slf4j-api-2.0.11.jar has:

    Require-Capability: osgi.extender;filter:="(&(osgi.extender=osgi.service
     loader.processor)(version>=1.0.0)(!(version>=2.0.0)))"
    

    And indeed - it's satisfied by Aries SPI-Fly with:

    Provide-Capability: osgi.extender;osgi.extender="osgi.serviceloader.re
     gistrar";version:Version="1.0",osgi.extender;osgi.extender="osgi.serv
     iceloader.processor";version:Version="1.0";uses:="org.apache.aries.sp
     ifly"
    

    SLF4J got these headers (requirements) in this commit.

    SPI-Fly is OSGi's response to Java Service Loader, transforming your bundle's code on the fly so proper thread context class loader is set.

    This is actually a proper requirement from SLF4J API 2, because they switched from static binding of logger implementation to service discovery of /META-INF/services/org.slf4j.spi.SLF4JServiceProvider service.

    However, OSGi is a tough beast. For logging I recommend Pax Logging project, which not only exports proper org.slf4j packages, but it also allows you to dynamically configure the logging backend and have it configured with OSGi Configuration admin.