Logging in Java
Logging is an essential part of any Java application, providing insights into the behavior and health of the application. However, with multiple logging APIs, frameworks, and bridges available, it can be confusing to differentiate between them. This guide will help you understand these components and how to choose the right ones for your project.
As a Java developer, you have noticed the code like
logger.info("user added in database : " + user.getUserId());
These lines are responsible for generating logs in your application, which you normally find in the log files on the servers where your application runs.
📙 Logging APIs: Normally the classes you use in your applications to log the information come from the thing called logging facade or logging API. These APIs send your logs to the logging implementation (or framework). These are facades that provide a standard interface for logging, allowing you to write log statements without being tied to a specific logging framework.
commons-logging, SLF4J, log4j-api are various logging facade or logging APIs and not the actual logging framework.
📘 Logging Framework: These are the actual implementations that handle how and where logs are stored, such as in files, databases, or remote servers. These are called as logging implementation or logging framework.
log4j-core, logback, java.util.logging(JUL) are logging frameworks or logging implementations.
📗 There is third thing called logging bridges/bindings/adapters, these are used between logging API and logging Implementations when API and Implementation are not compatible with each other.
Or to pass the logs written by one API (eg. SLF4J) to another API (eg. log4j-api), so that logging implementation at runtime (eg. log4-core in this case) can write the logs.
These adapters ensure compatibility between different logging APIs and frameworks, allowing them to work together seamlessly.
log4j-to-slf4j, logj-slf4j-impl, log4j-over-slf4j are logging bridges.
📙 Logging APIs in Java
As a developer of your application, you need to decide which logging API you are going to use to write your logs in your application’s Java classes. Let’s say, you decide to use SLF4J API to write your logs, but it is also possible that the other libraries you are using in your application, like spring-kafka, apache-lang3 etc, are probably using different APIs to write their own internal logs. For example — Spring uses commons-logging API.
Having more than one logging API in your application, is not an issue because all these logging APIs allow developers to write their logs without worrying too much about which logging implementation they are going to use to write the logs in files at runtime.
This section explores three notable logging APIs in the Java ecosystem.
🎏 In the next section, please focus on the import statements.
1. Commons Logging
Commons Logging is an abstraction layer for logging in Java that provides a simple interface while allowing developers to choose their preferred logging implementation. It’s often used in Apache projects.
🔖 Spring framework uses commons-logging.
Implementation Example:
/*
* Commons Logging Example Explanation:
*
- In this example, `LogFactory.getLog(MyClass.class)` creates a logger specific to `MyClass`.
- The `logger.info` method is then used to log informational messages.
*/
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class MyClass {
private static final Log logger =
LogFactory.getLog(MyClass.class);
public void performAction() {
logger.info("Performing action...");
}
}
2. SLF4J (Simple Logging Facade for Java)
SLF4J serves as a simple and efficient logging facade for various logging frameworks, including Logback, Log4j, and java.util.logging. It allows developers to switch between logging implementations easily.
🎬 SLF4J is default logging API for logback framework.
Implementation Example:
/*
* SLF4J Example Explanation:
*
- In this example, `LoggerFactory.getLogger(MyClass.class)` creates a logger specific to `MyClass`.
- The `logger.info` method is then used to log informational messages.
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClass {
private static final Logger logger =
LoggerFactory.getLogger(MyClass.class);
public void performAction() {
logger.info("Performing action...");
}
}
3. Log4j API
Log4j API is a logging API designed to work seamlessly with different logging frameworks, providing a flexible and extensible approach to logging. Though log4J is implementation but that is written in log4j-core while log4j-api is a facade or API library. You can use any logging framework with log4j-api, like log4j-core or you can send logs from log4j-api to another facade like slf4j.
Implementation Example:
/*
* Log4J API Example Explanation:
*
- In this example, `LogManager.getLogger(MyClass.class)` creates a logger specific to `MyClass`.
- The `logger.info` method is then used to log informational messages.
*/
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MyClass {
private static final Logger logger =
LogManager.getLogger(MyClass.class);
public void performAction() {
logger.info("Performing action...");
}
}
📘 Logging Implementations in Java
Now that we’ve explored the logging APIs, let’s move on to the actual workhorses that record the logs: the logging frameworks.
Logging APIs don’t do anything special, they just hand over the application logs to the logging framework to write them in log files.
Java applications leverage various logging implementations to record runtime events, errors, and performance metrics.
Logging frameworks provide extensive logging capabilities like writing the logs into file, databases, sending logs to remote services.
This section explores three prominent logging implementations: java.util.logging, Log4j, and Logback.
1. java.util.logging (JUL)
Java’s built-in logging facility, JUL, is part of the Java standard library. It provides a simple and lightweight logging API, making it a natural choice for many Java applications. However, some developers find it lacking in advanced features compared to dedicated logging frameworks.
Implementation Example:
/*
* JUL Example Explanation:
*
- In this example, `Logger.getLogger(MyClass.class.getName())` creates a logger specific to `MyClass`.
- The `logger.info` method is then used to log informational messages.
*/
import java.util.logging.Logger;
public class MyClass {
private static final Logger logger =
Logger.getLogger(MyClass.class.getName());
public void performAction() {
logger.info("Performing action...");
}
}
2. Log4j-core
Log4j is a robust logging framework that has been widely adopted in the Java ecosystem. Known for its configurability and flexibility, Log4j allows developers to fine-tune logging behaviors and outputs according to their application’s requirements.
log4j-core library is the actual implementation which prints logs in log file. While, as explained in API section, log4j-api library is the API used to write logs in your application code.
log4j-core implementation can be used with other logging APIs like SLF4J.
Here, when we talk about log4j, we talk about org.apache.logging.log4j with version 2.x.x, but not log4j.log4j which was predecessor of apache Log4j.
Log4J version 2 is also called as log4j2.
Configuration Example:
<!-- log4j.xml -->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
</layout>
</appender>
<root>
<priority value="debug"/>
<appender-ref ref="console"/>
</root>
</log4j:configuration>
3. Logback
Logback, developed as a successor to Log4j, offers improved features and performance. It is designed to be backward-compatible with Log4j while providing a more efficient and flexible logging solution. Logback incorporates SLF4J, a simple logging facade for Java.
Logback uses similar setup like log4j however instead of writing logback-api, it relies on slf4j-api but it has its own core logback-core.
Both logback-core and slf4j-api will be pulled together into your project if you use logback-classic dependency.
Configuration Example:
<!-- logback.xml -->
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5p %c{1} - %m%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="console"/>
</root>
</configuration>
📗Logging Bridges/Bindings/Adapters in Java
As we discussed above that not all logging APIs compatible with all the logging frameworks. You will find cases when you decided to use compatible logging API and logging framework but one of the library you are using is using a logging API which is not compatible with the selected logging framework.
In such cases, you need logging bridges to send logs from incompatible logging API to logging framework. If you don’t use a logging bridge then you will see logs from your code but will not be able to see logs written by the library which has incompatible logging API.
1. log4j-to-slf4j Adapter
When some of your code or the library code used in your application, has the logs written using log4j-api, however rest of your code uses Slf4j API then you should choose an implementation based on the Slf4j API.
But how will the logs written using log4j-API will be transferred to Slf4j?
This is done by log4j-to-slf4j. This is not the implementation which will write your logs into the log files, however it only transfers your logs to Slf4j API. This is not the API which you use to write the logs but it is a library which sits between your code (which uses log4j API) and the implementation which is writing Slf4j logs.
Use of this adapter may cause some loss of performance as the Log4j 2 Messages must be formatted before they can be passed to SLF4J. With Log4j 2 as the implementation these would normally be formatted only when they are accessed by a Filter or Appender.
Use of the SLF4J adapter (log4j-to-slf4j-2.0.jar) together with the SLF4J bridge (log4j-slf4j-impl-2.0.jar) should never be attempted as it will cause events to endlessly be routed between SLF4J and Log4j 2.
2. log4j-slf4j-impl (Log4j2 SLF4J Binding)
The Log4j 2 SLF4J Binding allows applications coded to the SLF4J API to use Log4j 2 as the implementation.
Due to a break in compatibility in the SLF4J binding, as of release 2.19.0 two SLF4J to Log4j Adapters are provided.
log4j-slf4j-impl
should be used with SLF4J 1.7.x releases or older.log4j-slf4j2-impl
should be used with SLF4J 2.0.x releases or newer.
The SLF4J binding provided in this component cause all the SLF4J APIs to be routed to Log4j 2. Simply include the Log4j 2 SLF4J Binding jar along with the Log4j 2 jars and SLF4J API jar to cause all SLF4J logging to be handled by Log4j 2.
3. SLF4J Bridge
Now you saw that SLF4J API is widely accepted by the implementations hence you prefer SLF4J while writing the logs in your application. But it is possible that the libraries you are using in your application, are using other APIs like commons logging or log4j-api. In such cases, SLF4J has created bridges to pull the logs from all those different API logs.
More details here. https://www.slf4j.org/legacy.html
Which Logging API and Logging Framework to choose
🎴 Logging APIs in Java provide a layer of abstraction that enables developers to write log statements without being tied to a specific logging implementation. Whether it’s the simplicity of Commons Logging, the flexibility of SLF4J, or the compatibility of Log4j API, these logging APIs empower developers to choose the logging approach that best fits their application’s needs. Understanding these APIs is crucial for seamlessly integrating logging into Java applications and facilitating efficient log management and analysis.
🎴 Selecting the right logging implementation for a Java application involves considering factors such as ease of use, configurability, performance, and community support. Whether opting for the simplicity of java.util.logging, the configurability of Log4j, or the performance improvements offered by Logback, developers can tailor their logging strategy to match the specific needs of their projects. Understanding the key aspects of these logging implementations empowers developers to make informed decisions and implement effective logging solutions in their Java applications.
Things to consider
- 🍩 When you write a library, use a versatile logging API which is compatible with almost all well known logging frameworks because you don’t know that the application which is going to use your library, what logging framework that application is going to use at runtime.
- 🍩 Though applications use actual framework to write their logs in log files, and framework like log4j bring their logging API with the implementation library then you are not forced to use the logging API of log4j which is log4j-api, you are free to use any logging API like SLF4J, commons-logging etc. This makes changing the logging framework easy in the future.
- 🍩 A particular logging framework can’t work with all the logging APIs. But developers of logging APIs and logging framework created bridges to have inter-operatibility between popular logging APIs and logging frameworks. So that they can work with each other.
Conclusion
Understanding the distinctions between logging APIs, frameworks, and bridges in Java is crucial for efficient logging practices. Whether you’re writing a library or developing an application, choosing the right combination of these components can greatly enhance your application’s logging capabilities.
📚 Want to dive deeper? Check out the official documentation for
[SLF4J](https://www.slf4j.org/) and
[Log4j2](https://logging.apache.org/log4j/2.x/).