[slf4j-dev] SL4FJ light: ad-hoc bindings for friendlier OSGi/IoC integration

Simon Chemouil schemouil+slf4j at gmail.com
Mon Oct 15 13:53:23 CEST 2012


Hi,

I am in the process of choosing a logging framework for an OSGi-based
project and obviously SLF4J is a strong contender. It is nearly
ubiquitous now and has an OSGi LogService bridge, as well as a very
interesting implementation (Logback).

However, the design of SLF4J, as it is, is somewhat OSGi unfriendly:
we have the API bundle depending on its implementation -- and the
opposite. This cyclic dependency is the consequence of trying to make
it extremely easy in regular Java environments to plug a SLF4J
implementation to the LoggerFactory, simply by "dropping a jar" in the
runtime classpath. I don't know if the following points have been
already discussed, and I could not find anything in the archives.

I understand this solution is inherited from Log4J, and that it was
itself a solution to the classloading problems in JCL. Dynamic binding
and classloader hacks are clearly problematic in some setups, and
there is no "one size fits all", because Java allows many styles and
SLF4J, as a universal logging facade, should definitely work best
everywhere possible.

I believe SLF4J could become more universal/ubiquitous if the static
binding approach was ad-hoc and not part of the "API side":
OSGi/Guice/Spring folks like me could then use
ILoggerFactory/IMarkerFactory as service specifications and use the
more natural "dynamic binding" provided in their framework. For
instance, component frameworks for OSGi providing DI could easily
inject a logger by component instance (in the spirit of [1]). In OSGi
particularly, the best practices are that an "active" service is
provided only when the bundle is active but  stateful singletons like
LoggerFactory don't follow these rules.

The FAQ entry "Is it possible to retrieve loggers without going
through the static methods in LoggerFactory?"[2] partly addresses
this: yes it is already possible to ignore LoggerFactory and static
binding. Unfortunately this is still not fully OSGi-friendly (and I
think it applies  to any module system):
* the current SLF4J bundle imports the package org.slf4j.impl with a
mandatory resolution. It means there *must* be an implementation
providing that package, and that the package *must* be exported. Best
practices recommend making implementation packages private and thus
non-usable by external bundles at all, even for reflection.
* if we were to specify the resolution of org.slf4j.impl as optional,
then any piece of code using LoggerFactory directly will default to a
no-op logger with a message on stderr. This doesn't look like a big
problem, but ideally in an OSGi context I would not want to provide
that class at all and someone trying to use LoggerFactory.getLogger()
would simply not get their component to build. Only people using
static binding should have the class in their build classpath.

I think there is not much to do to address these issues: it should be
enough to separate binding logic (e.g LoggerFactory / MarkerFactory
and the .spi package) into another JAR. For legacy reasons, so that
OSGi projects already using SLF4J's static binding are not broken, and
because split packages are generally unsupported in OSGi,
LoggerFactory and MarkerFactory should move to another package like
org.slf4j.staticfactories (or whatever). If they move to another
package, there should still be LoggerFactory and MarkerFactory classes
that redirect to the "new" ones for compatibility reasons (though
deprecated and scheduled for removal at the next backwards
compatibility break).

The new modules available would be:
* slf4j-api: purely API, no wiring, no state, no singletons. Default
implementations could still be packaged here.
* slf4j-staticbindings: providing the LoggerFactory / MarkerFactory
classes in a new package. Only this JAR should reference
org.slf4j.impl.
* slf4j-compat: providing bridge between
org.slf4j.{Logger,Marker}Factory and
org.slf4j.staticfactories.{Logger,Marker}Factory.
* all the existing slf4j implementations could provide both bridging
to OSGi and through static factories.

To sum up: for non-OSGi users we could keep backwards compatible for
code, but there would be two new JARs on the classpath. It is also
possible to merge them in a all-in-one JAR as of now. OSGi users could
benefit from having multiple concurrent SLF4J  implementations, or
even multiple SLF4J API versions.

I'm not really sure how this proposal is going to be received (because
this is obviously a central design choice in SLF4J). I truly think
this is best for SLF4J to let users pick how they want to do their
binding: it is a facade after all, it should be as abstract as
possible. It will also make SLF4J more durable, because these
questions will also happen when Jigsaw comes with Java 9: it is imo
better to prepare early to avoid forks/fragmentation. My proposal will
not break source compatibility but will make all the calls to
LoggerFactory.getLogger() deprecated. The migrator tool could replace
those easily, and it would still be possible to provide a compat jar.
The cost of the indirection at class loading, especially compared to
the classpath scanning of static binding, is negligible. The real
problematic consequence would be for people not adding the new JARs to
their classpath... This could be justification enough to bump the
version to 2.0.0, to so that people know the scope of the JARs have
been redefined (e.g, the API is backwards-compatible but the JARs are
not).

Please let me know if you think the proposal makes sense or not :), I
would be willing to provide the necessary patches / updates to the
FAQ.

Cheers,

Simon


[1] http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring/
[2] http://www.slf4j.org/faq.html#noLoggerFactory


More information about the slf4j-dev mailing list