Switching bindings for SLF4J as a runtime scoped dependency in Maven - maven

Think to Simple Logging Facade (SLF4J) for Java. As a background, it provides an API by means of a simple facade pattern in a way that the underlying logging backend is determined at runtime by adding the desired binding to the classpath. It may be the standard java.util.logging, log4j, logback or tinylog.
The neat separation of the client application from the logging backend reduces the coupling between the specific application and any particular logging framework. This can make it easier to integrate a newly implemented client with existing code of other projects that have already made a choice of logging backend.
So considering the logging API SLF4J, for compilation you need only the slf4j-api and you shall avoid including any specific binding like slf4j-log4j12 as a compile dependency.
As result, slf4j-log4j12 is a good candidate for being a runtime scoped dependency instead of a compile dependency, because this will allow you to switch among slf4j bindings at runtime without having to recompile the application.
Question: where to set the switching among logging frameworks bindings at runtime in a Maven project? Any example?

Disclaimer: I am the author of SLF4J
Even though the documentation talks about changing the logging back-end at runtime, SLF4J allows to switch logging back-ends at build time but not at runtime.

Related

How can I add log4j + slf4j to pom.xml?

I am trying to using slf4j and log4j together. After some googling, I found some solutions:
How does simply adding slf4j to the pom.xml wrap log4j?
https://dzone.com/articles/adding-slf4j-your-maven
How to get SLF4J "Hello World" working with log4j?
Various names and versions of jars related to slf4j and log4j just confused me so much. slf4j-log4j12, log4j, log4j-core, log4j-over-slf4j, log4j-slf4j-impl, log4j-api, slf4j-impl, log4j12-api, log4j-to-slf4j ...... I can't even know the function of these different jars.
So, which combination and version should I choose?
The standard way to use SLF4J is that it's the main logging framework that you use. (You call methods defined within the slf4j-api.) It, in turn, uses a "binding" such as slf4j-log4j12 which tells it how to talk to the "real" logging framework. And then you also need to have the real logging framework on your classpath, such as "log4j" version 1.2.
Some newer logging libraries, such as Logback, are both the "binding" and the "real" framework, so if you want to use that as your logging framework, you only need logback-classic along with slf4j-api, so it's two libraries rather than three.
The confusing "over" and "to" libraries exist as a way to deal with the reality that you probably depend on libraries that want to log in a different way than the way you've selected for your application, but it's nice to have everything directed into one framework. So, if you're using SLF4J and Logback, but you're depending on a library which logs using Log4j 1.2, you want to include the log4j-over-slf4j library, which will "intercept" any Log4j calls within any libraries in your application and translate them to be logged by SLF4J instead. Conversely, if you're logging with Log4j 1.2 directly (without SLF4J) and need to call a library that's using SLF4J, you're going to want to include the slf4j-log4j12 library to intercept those calls and translate them to Log4j for you. There are a variety of these kinds of libraries, each to intercept and translate from one particular logging framework to another.
But your question was "So, which combination and version should I choose?", which is rather broad, as we're not sure what it is that you're trying to do. Selecting a logging framework is like any other technology framework decision, based on a lot of things like developer familiarity, what the systems you need to integrate with are using, and if there are any existing code or standards which one wants to stay consistent with. So, I'm going to try to avoid getting into that selection process too much, and answer your question about how to set up Maven to use SLF4J as your logging framework, backed by Log4j version 2:
Add a dependency for the current version of slf4j-api
Add a dependency for Log4j 2 and its SLF4J binding (From https://logging.apache.org/log4j/2.x/maven-artifacts.html):
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.6.1</version>
</dependency>
</dependencies>
Use <dependencyManagement> sections in your POM to ensure that all your dependencies use the same version of your logging framework. (For instance, many libraries will include slf4j-api as a dependency, but they may each use a different version.) Generally logging frameworks keep good compatibility between versions, so you generally want to override all the supplied dependency versions with the (usually newer) one that you're using.
If you have any libraries that are using other logging framework, use the appropriate interceptor bridge to redirect its logging, either one from Log4j 2 that will redirect it straight to Log4Jj 2, or one from SLF4J which will redirect to SLF4J, which will then be further directed to Log4j. (While it may seem to do redirect twice, it could make things easier if you were to keep SLF4J but change to another "real" logging framework at some point. Maybe.) For instance, if you have a library that uses commons-logging, you want to include jcl-over-slf4j instead.
Also, use the maven-enforcer-plugin's bannedDependencies rules to ensure that you're excluding any logging frameworks that you're not using that the libraries you're depending on are trying to bring into your project. That is, for that example I gave of a library you depend on that uses commons-logging, you need to <exclude> commons-logging from that library dependency, and add it to your bannedDependencies list to ensure that you don't accidentally get it again from some other library. Otherwise, you'll have both the "real" commons-logging as well as your fake bridge (that emulates the interface and translates to your real logging framework) on the classpath, and will run into trouble.
I hope that overview helps. Note I haven't actually tried running Log4j 2 in anything yet, and just got those dependencies from their documentation. Definitely test that everything's working the way you expect.

Logging in Spring framework -flow and configuration

When we run a sample main program which reads a applicationContext.xml with a single bean..
how does Spring do the logging..and how can one overwrite the default logging.
I didnt see any log4j.xml in the spring dependencies as well..
Regards
This is described in the documentation:
Logging is a very important dependency for Spring because a) it is the only mandatory external dependency, b) everyone likes to see some output from the tools they are using, and c) Spring integrates with lots of other tools all of which have also made a choice of logging dependency. One of the goals of an application developer is often to have unified logging configured in a central place for the whole application, including all external components. This is more difficult than it might have been since there are so many choices of logging framework.
The mandatory logging dependency in Spring is the Jakarta Commons Logging API (JCL). We compile against JCL and we also make JCL Log objects visible for classes that extend the Spring Framework. It's important to users that all versions of Spring use the same logging library: migration is easy because backwards compatibility is preserved even with applications that extend Spring. The way we do this is to make one of the modules in Spring depend explicitly on commons-logging (the canonical implementation of JCL), and then make all the other modules depend on that at compile time. If you are using Maven for example, and wondering where you picked up the dependency on commons-logging, then it is from Spring and specifically from the central module called spring-core.
The nice thing about commons-logging is that you don't need anything else to make your application work. It has a runtime discovery algorithm that looks for other logging frameworks in well known places on the classpath and uses one that it thinks is appropriate (or you can tell it which one if you need to). If nothing else is available you get pretty nice looking logs just from the JDK (java.util.logging or JUL for short). You should find that your Spring application works and logs happily to the console out of the box in most situations, and that's important.
(emphasis mine)
Follow several sections describing how to use various logging frameworks.

Should I exclude Spring Framework jars that are not required?

I am developing a simple web application, using Spring Framework.
When I add Spring framework to my class path, I see that it has lot of jars which I never use (for example: spring-aop-3.2.3.RELEASE.jar).
Is it a good idea to keep the entire framework intact or remove unused jars?
If you need to remove unused jars, the best way is to use some dependency management tool like Ivy or Maven, and let the tool decide what the required dependencies are. Otherwise it will not be apparent what is really unused or not until you break something.
For instance, if you are using declarative transactions, then removing the AOP jar will cause breakage, because AOP is used to implement that functionality.
If you would rather not use dependency management, it's better to leave everything intact.
There are some cases where you do want to remove/exclude jars. Replacing commons-logging with slf4j is one example. Another example is excluding the log4j dependencies that get dragged in on account of some appender that's packaged with log4j but that you know you will never use. Dependency management tools allow you to tell them what needs to be excluded.
Doing without dependency management management and removing things because you never use them directly is too dangerous.

Runtime dependency (e.g. connection pooling) and classpath?

I have a Maven 3 project that uses Hibernate 3. In the Hibernate properties file, there is an entry for hibernate.connection.provider_class with the class corresponding to the C3P0 connection provider (org.hibernate.connection.C3P0ConnectionProvider). Obviously, this class is only used at runtime, so I don't need to add the corresponding dependency in my POM with the compile scope. Now, I want to give the possibility to use any connection pooling framework desired, so I also don't add a runtime dependency to the POM.
What is the best practice?
I thought about adding an entry to the classpath corresponding to the runtime dependency (in this case, hibernate-c3p0) when the application is run (for example, using the command line). But, I don't know if it's possible.
This is almost (maybe exactly) the same problem as with SLF4J. I don't know if Hibernate also uses the facade pattern for connection pooling.
Thanks
Since your code doesn't depend on the connection pooling (neither the main code nor the tests need it), there is no point to mention the dependency anywhere.
If anyone should mention it, then that would be Hibernate because Hibernate offers this feature in its config.
But you can add it to your POM with optional: true to indicate:
I support this feature
If you use it, then I recommend this framework and this version
That will make life slightly more simple for consumers of your project.
But overall, you should not mention features provided/needed by other projects unless they have some impact on your code (like when you offer a more simple way to configure connection pooling for Hibernate).
[EDIT] Your main concern is probably how to configure the project for QA. The technical term for this new movement is "DevOps" - instead of producing a dump WAR which the customer (QA) has to configure painstakingly, configuration is part of the development process just like everything else. What you pass on is a completely configured, ready-to-run setup.
To implement this, create another Maven module called "project-qa" which depends on your project and everything else you need to turn the dead code into a running application (so it will depend on DBCP plus it will contain all the necessary config files).
Maven supports overlayed WARs which will allow you to implement this painlessly.
You can mark your dependency as optional. In this case it will not be packaged into archives. In this case you have to ensure that your container provides required library.
You could use a different profile for each connection provider. In each profile you put the runtime dependency that correspond to the connection provider you want to use and change the hibernate.connection.provider_class property accordingly.
For more details about how to configure dependencies in profiles, see Different dependencies for different build profiles in maven.
To see how to change the value of the hibernate.connection.provider_class property see How can I change a .properties file in maven depending on my profile?

dealing with spring dependencies

I have a bunch of top level packages that are owned by the same team. All top level packages use spring. Then there are some libraries (jars) that are shared functionality and utilities between the top level packages. All pretty standard stuff.
In some cases it makes sense to make use of spring in a library package. Let's say I have a series of Aspects that are shared and I want to use spring's #Aspect/#Before/#After etc. annotations.
The problem is that there are fairly large top-level packages that are written with spring 2.5.6 dependencies, and the newer top-level packages are being created in spring 3. I generally think this is a good thing.
But then there's this little problem that I now have libraries that depend on spring-2.5.6, and those can't be used by spring-3 top level packages because of the mis-matching versions (I'm making the assumption here that sucking in two different spring versions is a very bad idea). It seems that my options are to avoid spring dependencies in libraries, or to accept that my libraries will need to provide multiple versions (1 for each version of spring used at the top package level). Option 1 seems preferable to option 2, but I'm hoping there's some interesting trick that gives me the best of both worlds.
If there was java standards for some of these things (#Inject?) then I could depend on the javax stuff and not on the spring specific stuff. Unfortunately #Inject etc. is only supported for spring 3+ so that fixes my problem at some future point but as long as I have 2.5.6 apps it doesn't help.
Ideas?
I should note that there are some existing libraries that depend on spring-2.5.6 and I view that as a deterrent to upgrading the top level package to spring-3. Because upgrading the top level package means also version bumping N libraries that depend on spring, which is annoying, given that I'm not even aware of the complete set of consumers of these libraries and what they might think of the spring version suddenly being bumped.
EDIT:
I'm wondering if I can create some library, and make a spring dependency with scope of provided. The spring version would be the minimum version that I can pick to get the feature set I want, so ideally I'd use 2.5.6 to make it compatible with a spring 2.5.6 app and a spring-3.0.5 app. Then when a consuming app creates a dependency on my library, it will also create a spring dependency for the real spring version in effect for that application. I'm thinking this should work, so long as the spring version is >= the version I pick for my library (and I happen to know that 2.5.6 is the lowest version in use for all apps).
My other option is to not create a spring dependency at all but DO provide a spring.xml that can be imported in the calling app. This means I can't use annotations or stuff like InitializingBean, but generally spring has provided a way to do things via annotation, or xml, or both, so this should work as well.
Thoughts?
As you rightly mentioned, having both 2.5.6 and 3.0 Spring dependencies will create problems. Only one version will be picked up at runtime which means either your top level packaged modules will fail or the other shared utilities will fail (depending on the version of Spring).
A similar issue was discussed in Java Classloader - how to reference different versions of a jar. It is not directly related to Spring but I cannot think of a direct solution to your problem. OSGi is a possible solution but not sure how feasible it is in your environment since it requires change in the container itself.
a) #Aspect #Before #After: These annotations are all in aspectjrt.jar, not Spring itself. They should work with either version
b) #Inject won't work but #Autowired will.

Resources