SL4FJ light: ad-hoc bindings for friendlier OSGi/IoC integration

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-... [2] http://www.slf4j.org/faq.html#noLoggerFactory

Simon, you have spent considerable time on this proposal. I agree that it would be worthwhile to investigate further. My experience with inject vs. static binding to loggers is mixed, meaning some people won't, or cannot, accept the injected approach. We need to have both methods of acquiring logger instances. I have a Guice injection module that I can contribute. I think you have something similar for Spring. Interested in putting something together as an injection starting point? -- Matthew Bishop Senior Product Architect Elastic Path Inc. On 2012-10-15, at 4:53 AM, "Simon Chemouil" <schemouil+slf4j@gmail.com> wrote:
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-... [2] http://www.slf4j.org/faq.html#noLoggerFactory
Matthew Bishop, Senior Architect Phone: 604.408.8078 ext. 101 Email: Matthew.Bishop@elasticpath.com Elastic Path Software, Inc. Web: www.elasticpath.com Blog: www.getelastic.com Community: grep.elasticpath.com Careers: www.elasticpath.com/jobs Confidentiality Notice: This message is intended only for the use of the designated addressee(s), and may contain information that is privileged, confidential and exempt from disclosure. Any unauthorized viewing, disclosure, copying, distribution or use of information contained in this e-mail is prohibited and may be unlawful. If you received this e-mail in error, please reply to the sender immediately to inform us you are not the intended recipient and delete the email from your computer system. Thank you. _______________________________________________
slf4j-dev mailing list slf4j-dev@qos.ch http://mailman.qos.ch/mailman/listinfo/slf4j-dev

Matthew Bishop <Matthew.Bishop <at> elasticpath.com> writes: [snip]
My experience with inject vs. static binding to loggers is mixed, meaning some people won't, or cannot, accept the injected approach. We need to have both methods of acquiring logger instances.
I have a Guice injection module that I can contribute. I think you have something similar for Spring. Interested in putting something together as an injection starting point?
Matthew, I agree the first concern should be to keep a clear backwards-compatibility route and that applies to clients using LoggerFactory.getLogger() static calls, as well as to implementation providers expecting a hook on StaticLoggerBinding. Clearly, it means we must keep the whole system. However, it seems reasonably easy and with little side effects to just "cut" the system into its own JAR. It is then up to users to choose whether they want to keep using static binding and drop that support JAR in their classpath, or whether they want to do the wiring using their favorite injection framework (using ILoggerFactory.class as service/injection key). I think that once we have made that cut, we could provide handy bindings for integration with DI frameworks such as the Guice bindings you made. I found the Spring example through a Google search, but I am personally writing such an extension for iPojo, an OSGi component framework. -- Simon

Hi, We had a discussion earlier today with Martin Ellis and Ceki Gülcü on IRC. Martin challenged my proposal to make sure that we guarantee backwards compatibility, make it clear what we are exactly trying to solve and find the possible alternatives. The full log is available for interested parties [1]. The motivation: * make static bindings optional for developers using frameworks that already do binding (OSGi, Guice, Spring, etc) or want to roll their own solution. * make it easier to use OSGi features like having several implementations available concurrently * in general, make slf4j more modular. The main problem: We would like to separate the static binding logic from the general API: however the org.slf4j package contains both the central interfaces ILoggerFactory, Logger, IMarkerFactory and Marker and the static binding logic. To keep backwards compatibility for both "vanilla" Java and OSGi environments, we cannot easily split that package into two different JARs. The alternatives discussed: (1) Do nothing. :-) (2) We move static-binding related classes in the org.slf4j package (LoggerFactory, MarkerFactory) to another package (e.g: org.slf4j.factories). In order to provide backwards-compatibility, we keep the factory classes in the org.slf4j that delegate to their moved counterpart, but we deprecate them. (3) We create a new package org.slf4j.api in which we put the central interfaces ILoggerFactory, IMarkerFactory, Logger and Marker. In org.slf4j the original interfaces are deprecated, emptied (except for constants that delegate to the new interfaces) and extend the moved interfaces. (4) We make LoggerFactory/MarkerFactory return proxies to the actual implementations, so that we can support implementation swapping. This requires providing new SPI to define proxy factories. I won't elaborate on this alternative because it adds complexity (it could also be done as a complement of alternatives (2) or (3)). (2) vs. (3): Alternatives (2) and (3) differ on what gets deprecated, and on how soon people can get the benefit of the lighter API without static binding. In the call: Logger logger = LoggerFactory.get(..): * with (2), LoggerFactory only gets deprecated. It can be solved by simply changing the import from org.slf4j.LoggerFactory to org.slf4j.factories.LoggerFactory * with (3), Logger only gets deprecated. It can be solved by changing the import from org.slf4j.Logger to org.slf4j.api.Logger. In both case, we could provide support with the migrator tool to make the proper import change, and in any case there will be no removal of the classes without a lot of time, due notice, a major version change and a FAQ entry ;). Even then, for non-OSGi users, a very simple compatibility JAR can be dropped to support this ad vitam eternam. Alternative (3) puts a bit more effort on implementation providers, since they have to update the interfaces they implement. Still they will have plenty of time to do so since the interfaces they already implement will extend the new ones. Packaging: To keep full backwards compatibility while allowing interested parties to take only what they need, the plan would be to keep the current slf4j-api with the same contents, but provide two new lighter JARS: slf4j-light-api and slf4j-static-bindings. (slf4j-api = slf4j-light-api + slf4j-static-bindings) Solution (2) (I hope you used fixed-width fonts for mails ;)). --------------------- ----------------------- | slf4j-light-api | | slf4j-static-bindings | | | | | | [org.slf4j] | | [org.slf4j.factories] | | [org.slf4j.helpers] | | [org.slf4j.spi] | |_____________________| |_______________________| Solution (3) --------------------- ----------------------- | slf4j-light-api | | slf4j-static-bindings | | | | | | [org.slf4j.api] | | [org.slf4j] | | [org.slf4j.helpers] | | [org.slf4j.spi] | |_____________________| |_______________________| About implementation bindings: Currently, implementations are registered by providing a class org.slf4j.impl.Static{Logger,Marker}Binder. This is problematic in an OSGi environment, when several implementation bundles are installed, because multiple bundles export the same package/version with different contents. The wiring of the package used by clients likely depends on installation order of implementation bundles. While this is a non mandatory extra-step for now, ideally the implementation bundles should let users choose the binding system used. It is much simpler to solve on this side, for instance by keeping the org.slf4j.impl package but not exporting it in the OSGi manifest, and providing the package in another bundle that only exports it as a commodity for OSGi users wishing to use static binding. Since this is entirely transparent, there could be a Bundle-Activator entry in the implementation manifest to register the implementation factories as OSGi services, and the same implementation bundle could be dropped without change in both OSGi and non-OSGi environments. We could also provide other bindings (e.g for Guice, Spring, etc). Summary: We can remain backwards compatible until we feel it's time and do a major version change (i.e, in years). For the sake of good practices, we would mark interfaces @Deprecated but it's just a matter of changing the import (can be done by the provided migrator tool). We keep publishing JARs entirely compatible, but OSGi/Guice/Springs users who want to can gain flexibility by dropping static binding altogether. Those who want to use static bindings can as long as they want. So, this was supposed to be a short mail :). I hope it's clear, at least ;). Cheers, -- Simon [1] https://gist.github.com/0811d39d8264dcf881dc

Hi Simon, Thanks for initiating the discussion. After reading through the proposal I was wondering if adding a setter to LoggerFactory (note: over-simplified!) has been considered an option? In combination with an activator for the SLF4J API this might be a flexible and simple way to open the gate for a broad range of other options without causing too much disruption in the user base. Moving Logger and LoggerFactory out of "org.slf4j" just for the sake of OSGi (or any other runtime environment) does not sound like the best option to me. It causes migration grief for many, many users to satisfy a (IMHO) much smaller group of SLF4J implementors/integrators. FWIW, I captured my ideas in a simple commit: https://github.com/guw/slf4j/commit/3cb841c5f3619992c7c4016fb36ffc5d2e9b7fbd It's not meant to be functional complete an bullet prove but it should outline the idea. I think Ceki can provide more details of the effects this might cause. For example, the method (in it's current implementation) allows changing the factory at runtime multiple times. For this to work reliable, the Loggers really need to become proxies to the actual ("current") logger. They are usually held in static variables. A really dynamic OSGi implementation might actually implement it that way. However, SLF4J can simply delegate that complexity to the runtime. -Gunnar -- Gunnar Wagenknecht gunnar@wagenknecht.org http://wagenknecht.org/
participants (3)
-
Gunnar Wagenknecht
-
Matthew Bishop
-
Simon Chemouil