Slf4j Commons Logging Example
In this example, we are going to see how SLF4J fares vis-à-vis Apache Commons Logging.
1. Introduction
From time immemorial, logging has remained an integral part of programming enterprise applications and so does the logging frameworks. With Java, comes a wide variety of logging frameworks to choose from. While writing a library (to be used any application), it is desirable that the library remains decoupled with the underlying logging framework so that the application which integrates the library can use a logging framework of it’s choice.
For this example, we use the following tools on a Windows 10 platform:
- Eclipse IDE
- Apache Maven
- JDK 1.8
- Slf4j 1.7.25
- JCL 1.2
SLF4J and Apache Commons Logging, both aims to achieve the above mentioned feature. Let’s understand a little what these both are.
2. Apache Commons Logging
Previously known as Jakarta Commons Logging (JCL), is an “ultra-thin bridge between different logging implementations”. A library that uses the commons-logging API can be used with any logging implementation at runtime[1]. In simple terms, this means JCL is an abstraction layer or an interface between the java code and the actual logging implementation; the actual logging implementation has to be provided at the runtime.
The advantage of that is, the code will be free from any specific logging framework/implementation (and will use classes/interfaces of JCL) and will auto detect (we will see how later, stay tuned) the underlying logging implementation.
2.1 How to use Commons Logging
JCL provides org.apache.commons.logging.Log
(the basic logger) and org.apache.commons.logging.LogFactory
(which knows how to create org.apache.commons.logging.Log
instances).
2.1.1 Create a Maven Project
We will create a bare minimum Maven project. Once you have the Maven installed and running on your machine, issue the following command from the command line.
mvn archetype:generate -DgroupId=com.javacodegeeks -DartifactId=jclexample -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
This will create a App.java by default in a default project structure with a pom.xml. Later on we will add JCL dependencies to it. In our example, Maven will manage dependencies for us and we don’t need to download any jars explicitly.
2.1.2 Add Commons Logging Dependency
Add the dependency to the latest commons-logging.jar to your class path. With this you will be able to log to an abstract logging layer.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javacodegeeks</groupId> <artifactId>jclexample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>jclexample</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> </dependencies> </project>
You can code without an actual logging implementation and still able to use all the features in your code that any logging implementation provides. Rename App.java to JclExample.java. Create an instance of org.apache.commons.logging.Log
and let’s just output an info for our example.
JclExample.java
package com.javacodegeeks.jclexample; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * JclExample! * */ public class JclExample { private static Log log = LogFactory.getLog(JclExample.class); public static void main( String[] args ) { log.info("Let's try Commons Logging"); } }
Note: At this point we haven’t provided any logging implementation in which case JCL falls back to the default simple logging wrapper (SimpleLog) which in turn sends all enabled log messages, for all defined loggers, to System.err. If you run the program now, the output will be like this:
Mar 31, 2017 5:10:21 PM com.javacodegeeks.jclexample.JclExample main INFO: Let's try Commons Logging
2.1.3 Add Logging Framework
In order to direct logging messages to your desired medium and to customize, you need to add a logging implementation of your choice. Again, JCL is just an abstraction layer which facilitates switching logging implementation without changing the code.
Let’s try log4j for this example.
2.1.3.1 Add Maven Dependency
Add the dependency for log4j into pom.xml
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2.1.3.2 Configure Underlying Logging Framework
Now we must add a log4j configuration file for log4j to work and place it at the root of the applications’ classpath.
log4j.properties
# Root logger option log4j.rootLogger=INFO, stdout # Direct log messages to stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n
2.1.3.3 Configure Commons Logging
Create a file called commons-logging.properties and place it in the src folder.
commons-logging.properties
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
Note:
- Even without configuring, the JCL discovery mechanism will auto identify and use Log4J. We will talk about the JCL discovery process in brief in next section.
- The
org.apache.commons.logging.Log
property should be set to the implementation of this interface; you cannot directly setorg.apache.commons.logging.Log
toorg.apache.log4j.Logger
.org.apache.commons.logging.impl.Log4JLogger
acts as an adapter betweenorg.apache.commons.logging.Log and
org.apache.log4j.Logger
.
2.2 How Commons Logging Works
We saw how to write the application/library code independent of the logging framework and how to tell JCL which implementation to use via commons-logging.properties by providing a suitable adapter of the underlying logging implementation.
We also noticed that if we run the code above without commons-logging.properties, JCL is still able to utilise log4j to out the log messages. How? Here comes the JCL discovery mechanism into play.
When no particular logging library is specified then JCL will silently ignore any logging library that it finds but cannot initialise and continue to look for other alternatives[1].
In the absence of commons-logging.properties, org.apache.commons.logging.LogFactory
implementation uses the following discovery process to determine what type of org.apache.commons.logging.Log
implementation it should use (the process terminates when the first positive match – in order – is found):
- Look for a system property named
org.apache.commons.logging.Log
- If the Log4J logging system is available in the application class path, use the corresponding wrapper class (Log4JLogger; the one we have used in our example).
- If the application is executing on a JDK 1.4 system, use the corresponding wrapper class (Jdk14Logger).
- Fall back to the default simple logging wrapper (SimpleLog).
3. SLF4J
The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time[2]. In simple terms, this means SLF4J is an abstraction layer or an interface between the java code and the actual logging implementation; the actual logging implementation has to be provided during deployment time. Sounds familiar, right? (Those who didn’t get the question, read the Apache Commons Logging introduction again, section 2 of this example)
The advantage of that is, the code will be free from any specific logging framework/implementation (and will use classes/interfaces of SLF4J) and will use the SLF4J bindings to use the underlying logging implementation.
3.1 How to use SLF4J
Just like JCL, SLF4J provides org.slf4j.Logger
(the basic logger) and org.slf4j.LoggerFactory
(which knows how to create org.slf4j.Logger
instances).
3.1.1 Create a Maven Project
We will create a bare minimum Maven project. Once you have the Maven installed and running on your machine, issue the following command from the command line.
mvn archetype:generate -DgroupId=com.javacodegeeks -DartifactId=slf4jconfig-log4j -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
This will create a App.java by default in a default project structure with a pom.xml. Later on we will add SLF4J dependencies to it. In our example, Maven will manage dependencies for us and we don’t need to download any jars explicitly.
3.1.2 Add SLF4J Dependency
Add the dependency to the latest slf4j-api.jar to your class path. With this you will be able to log to an abstract logging layer.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javacodegeeks</groupId> <artifactId>slf4jconfig-log4j</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>slf4jconfig-log4j</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> </dependencies> </project>
You can code without an actual logging implementation and still able to use all the features in your code that any logging implementation provides. Rename App.java to HelloLog4J.java. Create an instance of org.slf4j.Logger
and let’s just output an info for our example.
HelloLog4J.java
package com.javacodegeeks.slf4jconfig_log4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HelloLog4J! * */ public class HelloLog4J { public static void main( String[] args ) { Logger logger = LoggerFactory.getLogger(HelloLog4J.class); logger.info("This is how you configure Log4J with SLF4J"); } }
Note: At this point we haven’t provided any logging implementation in which case SLF4J silently discards all logging (SLF4J in this case binds to NOPLogger). At this point if you run the program, it will give an output like this:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
3.1.3 Add Logging Framework
In order to direct logging messages to your desired medium and to customize, you need to add a logging implementation of your choice. Again, SLF4J is just an abstraction layer which facilitates switching logging implementation without changing the code.
Let’s try log4j for this example.
3.1.3.1 Add Maven Dependency
Add the dependency for log4j into pom.xml
>dependency> >groupId>log4j>/groupId> >artifactId>log4j>/artifactId> >version>1.2.17>/version> >/dependency>
3.1.3.2 Configure Underlying Logging Framework
Now we must add a log4j configuration file for log4j to work and place it at the root of the applications’ classpath.
log4j.properties
# Root logger option log4j.rootLogger=INFO, stdout # Direct log messages to stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n
2.1.3.3 Configure SLF4J
Unlike Commons logging, SLF4J doesn’t need a properties file or system property to find the underlying implementation. Instead, SLF4J depends on static binding to bind with the underlying logging framework for which we need to provide a SLF4J binding jar. In our case, since we are using log4j as the underlying framework, we need a corresponding binding as shown below. Add the following SLF4J binding as dependency.
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency>
Note:
- Unlike JCL, SLF4J always needs a binding jar to know about the corresponding logging framework.
- Unlike JCL, SLF4J won’t output anything (even to console) in the absence of any suitable binding.
3.2 How SLF4J Works
During deployment, it is necessary to provide the actual logging implementation otherwise SLF4J will ignore all the log messages. The SLF4J API talks to the actual logging implementation via a SLF4J binding. Also, the SLF4J binding is specific to the logging framework you want to use in your project. In the absence of the correct SLF4J binding, SLF4J API won’t recognize the logging framework.
4. Summary
In this example We saw how Commons Logging and SLF4J are both built to achieve abstraction in logging. They both decouples the code from the underlying logging framework and hence are very useful for creating reusable libraries.
However, the way they both achieve this abstraction is very different. SLF4J was built later to solve the problems encountered with JCL. JCL has a runtime discovery mechanism (which is an expensive process) while SLF4J binds during deployment time. Hence, no class loader issues. Moreover, SLF4J comes with support for new frameworks like logback and Java Logging. In short, SLF4J is preferable to JCL.
Read more on JCL vs SLF4J, here.
Read how to configure SLF4J with other libraries, here.
5. References
- https://commons.apache.org/proper/commons-logging/guide.html
- https://www.slf4j.org/manual.html
- https://docs.oracle.com/cd/E29542_01/doc.1111/e35342/file_commons_logging_ini.htm#WBCSP137
- https://examples.javacodegeeks.com/enterprise-java/slf4j/slf4j-configuration-file-example/
If I have a library A using slf4j which has a dependency to a library B using commons-logging, what is the best/recommended way to deal with the binding/bridges dependencies? Let’s say, I want to use slf4j with log4j implementation.
Where should be jcl-over-slf4j included and commons-logging excluded: in POM of library A POM or in POM of an app that uses the library A?
And if the second, should the library A exclude dependency to commons-logging?