×
Community Blog Java Logging Frameworks: Summary and Best Practices

Java Logging Frameworks: Summary and Best Practices

In this blog, we will talk about commong Java logging tools and discuss their best practices.

By Cangtang from TaoXi Technology

Overview

Of the many Java logging tools, Log4j was the first widely used, and Simple Logging Facade for Java (SLF4J) with Logback is currently popular. Developers sometimes need to encapsulate some components (second-party packages) into an application, so they cannot guarantee that a specified logging tool is used for all components. In addition, the application may use a lot of third-party packages that use different logging tools. For example, if an application has two components that use different logging tools, the application will have output logs from both logging tools, resulting in complicated logging.

I will give a brief introduction to common logging tools.

Common Java Logging Tools

JUL

java.util.logging (JUL) was a logging system of the Java Development Kit (JDK) since JDK 1.4. Due to Log4j, JUL was not widely used. In fact, JUL is more convenient than Log4j in some test code. Similar to Log4j and Logback, JUL has its own implementation logic for log output. However, JCL and SLF4J do not.

import java.util.logging.Level;
import java.util.logging.Logger;

private static final Logger LOGGER = Logger.getLogger(MyClass.class.getName());

Loggers have globally unique names. Loggers are typically named by hierarchical namespaces separated by periods. Logger names can be any character strings. However, they are usually based on the package names or class names of components, such as java.net or javax.swing.

The default configuration file is jre/lib/logging.properties, and the default log level is INFO.

You can use the java.util.logging.config.file property to specify a path and replace the default configuration file. The log levels are SEVERE, WARNING, INFO, CONFIG, FINE, FINER, and FINEST, in decreasing order of severity. Two global switches are available: OFF (disable logging) and ALL (enable logging for all messages).

In the logging.properties file, the default log levels can be controlled using .level= ALL or hierarchical namespaces. Loggers match with rules in the configuration file based on the logger name prefix, and the rule with the highest match degree is preferentially used by the current logger. The log levels must be written in all uppercase letters.

JUL uses handlers to generate logs. You can use the configuration file to specify one or more handlers. If multiple handlers are specified, they are separated by commas. Each handler has a log level, and only logs of the specified level or higher will be generated. Handlers can be bound to log formatters. For example, java.util.logging.ConsoleHandler uses String.format.

Here is an example configuration file:

handlers= java.util.logging.ConsoleHandler

.level= ALL
com.suian.logger.jul.xxx.level = CONFIG
com.suian.logger.jul.xxx.demo2.level = FINE
com.suian.logger.jul.xxx.demo3.level = FINER

java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=%1$tF %1$tT [%4$s] %3$s -  %5$s %n

JCL

Apache Commons Logging (JCL), previously called Jakarta Commons Logging, is a common logging API provided by Apache. It enables applications to no longer rely on specific logging implementations.

The commons-logging package simply packages other logging tools, including Log4j, Avalon LogKit, and JUL. In this case, a running application can adapt logs generated by the JCL API to the corresponding logging implementations.

JCL uses the dynamic search mechanism to find the used logging library during application running. However, SLF4J statically binds the real logging implementation library during compilation.

The following list shows wrapper classes and simple implementations in the commons-logging package.

  • org.apache.commons.logging.impl.Jdk14Logger: adapted to JUL in JDK 1.4.
  • org.apache.commons.logging.impl.Log4JLogger: adapted to Log4j.
  • org.apache.commons.logging.impl.LogKitLogger: adapted to Avalon LogKit.
  • org.apache.commons.logging.impl.SimpleLog: a logging implementation class of JCL. It implements a logging API and exports log messages to System.err.
  • org.apache.commons.logging.impl.NoOpLog: a logging implementation class of JCL. It implements a logging API. No operations are performed in the log output method.

If only JCL is used, no adapter is bound using the commons-logging.properties configuration file, and the LogFactory implementation is not redefined using system properties or SPI, java.util.logging.Logger of the JDK is used for log output.

JCL uses the commons-logging.properties configuration file. You can specify the logging tool to use in the configuration file. If no logging tool is specified, JUL is used for log output by default. A configuration example is as follows:

Avalon LogKit

Avalon LogKit is a high-speed logging toolkit. It is used by all Avalon components, such as Framework, Excalibur, Cornerstone, and Phoenix. Its model adopts the same principle as the JDK 1.4 logging package. However, it is compatible with JDK 1.2 and later versions. LogKit is used due to Context and LogTargets.

If Log4j is used, the log content can be only one sentence. However, with LogKit, you can record many items and even record them to corresponding database fields. If you use Log4j to store logs in different storage media, such as databases, you must use appenders. However, LogKit supports multiple storage targets.

Log4j

Log4j is an open-source code project of Apache. With Log4j, we can control the destination of output logs, such as a console, file, or database. We can also control the format and define the level of each output log to control the log generation process.

Log4j provides seven log levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, and OFF in ascending order of severity. If the log level is set to OFF, logging is disabled. Log4j supports configuration files in properties and xml formats. Log4j consists of three main components: logger, appender, and layout.

SLF4J

SLF4J is not a specific logging solution. It provides some Java logging APIs in facade pattern, similar to JCL. It was previously developed to replace JCL.

The core API provided by SLF4J is some interfaces and a LoggerFactory class. With SLF4J, you do not need to specify the logging system you plan to use in the code or configuration file. You can use a logging implementation scheme without modifying any configuration during deployment. During compilation, the real logging library is statically bound.

If you need to use a specific logging implementation when using SLF4J, you must select the correct JAR package set (various bridge packages) of SLF4J. SLF4J provides a unified logging API. You only need to log data based on the provided method. The final log format, log level, and output method are determined based on the configuration of the specific logging system. This allows you to flexibly switch logging systems in applications.

Logback is a natural implementation of slf4j-api and can be used without needing any bridge package. SLF4J encapsulates many other bridge packages and can be used in other logging implementations. For example, slf4j-log4j12 allows you to use Log4j for log output, and slf4j-jdk14 allows you to use JUL for log output.

Logback

Logback is a reliable, universal, fast, and flexible Java logging framework. Logback consists of three modules: logback-core, logback-classic, and logback-access. logback-core is the base of the other two modules. logback-classic is an advanced version of Log4j that fully implements the SLF4J API.

logback-access integrates with servlet containers so that you can use it to write HTTP-access logs. Logback depends on the logback.xml configuration file and can also support the groovy format. Compared with Log4j, Logback has many advantages. I will not go into detail here, but you can find more information through an Internet search.

Log4j 2

Log4j 2 is an advanced version of Log4j 1.x and Logback. Log4j 2 uses some new technologies, such as lock-free and asynchronous, to improve the logging throughput and performance by a factor of 10 when compared with Log4j 1.x, solve some deadlock bugs, and make the configuration simpler and more flexible.

Log4j 2 offers a plugin architecture, which makes it extensible on demand and helps you implement your own appenders, loggers, and filters. Unlike other logging frameworks, Log4j 2 does not lose its previous log files during reconfiguration.

Log4j 2 uses the concurrency feature of Java 5 to execute locks at the lowest layer. This solves the deadlock problem in Log4j 1.x. Log4j 2 is based on the LMAX Disruptor library. In multi-thread scenarios, asynchronous loggers are about 10 times more efficient than those in existing logging frameworks.

The following figure shows the Log4j 2 architecture.

1

Scenarios

Using only java.util.logging.Logger

This is the simplest scheme, which can be used when you write a demo or test cases yourself. Formal systems typically do not only use java.util.logging.Logger. java.util.logging.Logger does not require any third-party dependencies and is natively supported by the JDK.

Using only JCL

You need to import the commons-logging package. The following code snippet shows an example commons-logging package.

<dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.2</version>
      </dependency>

Using JCL and Log4j Together

You need to import the commons-logging and Log4j packages. The following code snippet shows example commons-logging and Log4j packages.

<dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>1.2</version>
      </dependency>
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
      </dependency>

In this mode, the following logging APIs can be used:

  • org.apache.commons.logging.Log: API in the commons-logging package
  • org.apache.log4j.Logger: API in the log4j package

Regardless of which logging API is used, Log4j is used for logging. We recommend that you use the API in the commons-logging package. If you directly use the API in the log4j package, it is the same as using Log4j only and you do not need to add the commons-logging package.

The output log level and target are controlled by the Log4j configuration file because Log4j is used for logging. The following code snippet shows an example Log4j configuration file log4j.properties.

#log4j.rootLogger = error,console
log4j.logger.com.suian.logtest = trace,console

# Export to the console.
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %c - [log4j]%m%n

If the API in the commons-logging package is used for logging, you must explicitly determine the binding with Log4j in the commons-logging.properties configuration file to find the Log4j logging implementation. The following code snippet shows an example.

org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger

In the code, the JCL API is used for logging, and Log4j is used for log output. The Log4j configuration file controls log output, and binding with Log4j is explicitly specified in the commons-logging.properties configuration file.

Using only Log4j

This was the most popular mode just a few years ago. However, due to problems with Log4j and the emergence of new logging frameworks, this mode is no longer used. For more information on how to use Log4j alone, search for information on the Internet.

Using SLF4J and Logback

This is currently the most popular mode. SLF4J is the most widely used logging facade, and Logback is a natural implementation. SLF4J with Logback ensures simple, unified, and quick logging.

The following code snippet shows third-party dependencies that need to be added.

<dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>${slf4j.version}</version>
      </dependency>
      <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-core</artifactId>
          <version>${logback.version}</version>
      </dependency>
      <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>${logback.version}</version>
      </dependency>

Using only Log4j 2

Log4j 2 can be thought of SLF4J with Logback. log4j-api is equivalent to SLF4J, and log4j-core is equivalent to Logback.

The following code snippet shows third-party dependencies that need to be introduced.

<dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-api</artifactId>
          <version>2.6.2</version>
      </dependency>
      <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <version>2.6.2</version>
      </dependency>

Conflict Processing

Theoretically, log output methods such as Log4j, Log4j 2, and Logback can coexist. However, we have to maintain multiple configuration files and fully understand the logging tool that each component uses to configure the corresponding configuration files.

To design a general logging solution, we must handle compatibility issues in a special way. Currently, only SLF4J and Log4j 2 provide an integrated log output mechanism. Other logging tools are weak in processing conflicts.

The following list shows the logging APIs that may be used in code.

  • java.util.logging.Logger: provided by the JDK
  • org.apache.commons.logging.Log: contained in the commons-logging package
  • org.apache.log4j.Logger: contained in the Log4j package
  • org.apache.logging.log4j.Logger: provided by Log4j 2 and contained in the log4j-api package
  • org.slf4j.Logger: provided by SLF4J and contained in the slf4j-api package

The preceding logging methods may coexist in an application. However, even if your code ensures that the same types of APIs are used, the APIs in added third-party dependencies may be of different types.

As mentioned previously, SLF4J and Log4j 2 can properly handle various conflicts. They provide many binders and bridges.

Binders are also adapters or wrapper classes. They bind logs generated by specific APIs to logging implementation tools for log output. For example, JCL can be bound to Log4j or JUL for log output, and SLF4J can be bound to Logback, Log4j, Log4j 2, or JUL for log output.

Bridges are fake logging implementation tools. For example, if you place jcl-over-slf4j.jar in CLASS_PATH, a component that is bound to JCL for log output will be redirected to SLF4J. SLF4J then provides the logs to the specific logging implementation tool based on the binder.

SLF4J Integrated Log Output

java.util.logging.Logger

This is used to integrate JUL logs to SLF4J for unified output. You must add the dependency package provided by SLF4J.

<dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>jul-to-slf4j</artifactId>
          <version>1.7.22</version>
      </dependency>

With only the dependency package, JUL logs cannot be integrated because the package only provides a JUL handler. You still need to configure the JUL configuration file. The log levels set on the SLF4J binder (such as Logback) are equivalent to the log levels on the JUL handler. The JUL log levels are still controlled by the JUL configuration file logging.properties and the configuration file of the SLF4J binder, such as logback.xml or log4j2.xml.

Create the jdk14-logger configuration file logger.properties and add the handler configuration and log level configuration.

handlers= org.slf4j.bridge.SLF4JBridgeHandler
.level= ALL

Add the JVM parameter configuration (Djava.util.logging.config.file = /path/logger.properties) when a program or container is started. You can also use the programming method. For example, you can initialize the logging component during system startup using the main method or the listener of an extended container. However, the programming method cannot be used in certain scenarios, such as proxying system log output of tomcat.

org.apache.commons.logging.Log

This is used to integrate JCL logs with SLF4J for unified output. You must add the dependency package provided by SLF4J.

<dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>jcl-over-slf4j</artifactId>
          <version>1.7.22</version>
      </dependency>

The root path of all classes in the jcl-over-slf4j package is org.apache.commons.logging. The package also contains Log and LogFactory classes. To bridge JCL logs, the commons-logging package is rewritten. Log is exactly the same as that in the commons-logging package. The LogFactory class implementation and code use org.apache.commons.logging.impl.SLF4JLogFactory.

In the commons-logging package, org.apache.commons.logging.impl.LogFactoryImpl is used by default. This bridging method ensures seamless interworking and does not need to introduce extra configurations like JUL. However, class library conflicts may occur. The commons-logging and jcl-over-slf4j packages cannot coexist. You need to delete the commons-logging package from the classpath.

JCL can be bound to an adapter using the commons-logging.properties configuration file. Therefore, I personally prefer the method of adapter encapsulation. It is like org.apache.commons.logging.impl.Log4JLogger in the commons-logging package, which fits the way programmers think.

Bridge packages are named based on specific rules. Bridge packages for overriding are named in xxx-over-slf4j format, for example, jcl-over-slf4j in this example. Bridge packages for pure bridging are named in xxx-to-slf4j format, for example, jul-to-slf4j.

org.apache.log4j.Logger

This is used to integrate Log4j logs to SLF4J for unified output. You must add the dependency package provided by SLF4J.

<dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>log4j-over-slf4j</artifactId>
          <version>1.7.22</version>
      </dependency>

According to the bridge package name, log4j-over-slf4j overrides the log4j:log4j package. Therefore, you only need to add dependencies. No additional configuration is required. However, you still need to handle conflicts. The log4j and log4j-over-slf4j packages cannot coexist.

org.apache.logging.log4j.Logger

This is used to integrate Log4j 2 logs to SLF4J for unified output. SLF4J does not provide a bridging package, but Log4j 2 does. First, add the Log4j 2 bridge package.

<dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-to-slf4j</artifactId>
          <version>2.6.2</version>
      </dependency>

Log4j 2 provides the org.apache.logging.log4j:log4j-api and org.apache.logging.log4j:log4j-core dependency packages, and their names indicate their functions. log4j-core is the standard implementation of log4j-api, and log4j-to-slf4j is also an implementation of log4j-api.

log4j-to-slf4j is used to bridge the output logs of Log4j 2 to SLF4J for actual output. In theory, log4j-core and log4j-to-slf4j should not be able to coexist because there are two Log4j 2 implementations.

However, actual testing shows that they can coexist. This is because Log4j 2 uses a priority-based policy to load providers. An available provider can be determined even when multiple providers are found. Each dependency package that provides a Log4j 2 implementation has a META-INF/log4j-provider.properties configuration file, in which the FactoryPriority attribute is used to configure the priority of providers. log4j-to-slf4j (15) has a higher priority than log4j-core (10). Therefore, the test result is as expected, and the logs of Log4j 2 are bridged to SLF4J for output.

To ensure that the system is deterministic and avoid problems caused by decision policy changes of the Log4j 2 provider, we recommend that you delete log4j-core from the classpath. This is also recommended by the Log4j 2 development team.

Log4j 2 Integrated Log Output

java.util.logging.Logger

This is used to integrate JUL logs to Log4j 2 for unified output. You must add the dependency package provided by Log4j 2.

<dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-jul</artifactId>
          <version>2.6.2</version>
      </dependency>

Log4j 2 integrates JUL logs in a different way than SLF4J. SLF4J only defines a handler and still depends on the JUL configuration file. Log4j 2 directly rewrites java.util.logging.LogManager.

You only need to use the java.util.logging.manager property to bind the rewritten LogManager (org.apache.logging.log4j.jul.LogManager). This is simpler than the method provided by SLF4J.

org.apache.commons.logging.Log

This is used to integrate JCL logs to Log4j 2 for unified output. You must add the dependency package provided by Log4j 2.

<dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-jcl</artifactId>
          <version>2.6.2</version>
      </dependency>

Integrating JCL logs based on the log4j-jcl package is simple. You only need to place the log4j-jcl package in the classpath. This seems to be more elegant than the integration method provided by SLF4J. The underlying principle is as follows: The LogFactory implementation is found when JCL LogFactory is initialized. The detailed process is as follows:

  1. First, the LogFactory implementation class is found based on the org.apache.commons.logging.LogFactory property.
  2. If the LogFactory implementation class is not found, the SPI method is used to find the implementation class META-INF/services/org.apache.commons.logging.LogFactory. log4j-jcl is supported through this method. However, to use this method, an application, including the third-party JAR packages that the application depends on can contain only one org.apache.commons.logging.LogFactory file. If multiple files exist, the first loaded file prevails. If conflicts exist, it is difficult to troubleshoot the issue.
  3. If the LogFactory implementation class is still not found, the system reads the commons-logging.properties configuration file and uses the LogFactory class specified by the org.apache.commons.logging.LogFactory property.
  4. If the LogFactory implementation class still cannot be found, the default implementation org.apache.commons.logging.impl.LogFactoryImpl is used.

org.apache.log4j.Logger

This is used to integrate Log4j 1.x logs to Log4j 2 for unified output. You must add the dependency package provided by Log4j 2.

<dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-1.2-api</artifactId>
          <version>2.6.2</version>
      </dependency>

Log4j 1.x logs are integrated to Log4j 2 by overriding log4j 1.x api, so the implementation works in the same way as that in SLF4J. Therefore, class library conflicts also exist. If log4j-1.2-api is used, all Log4j 1.x packages in the classpath must be deleted.

org.slf4j.Logger

This is used to integrate SLF4J logs to Log4j 2 for unified output. You must add the dependency package provided by Log4j 2.

<dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-slf4j-impl</artifactId>
          <version>2.6.2</version>
      </dependency>

log4j-slf4j-impl implements the SLF4J API based on Log4j 2. It is a bridge between slf4j-api and log4j2-core. log4j-slf4j-impl and log4j-to-slf4j cannot coexist in the classpath. Otherwise, events will be routed back and forth between SLF4J and Log4j 2 endlessly.

Binding Logging APIs to Implementations

Both slf4j-api and log4j-api are APIs, and no specific implementations are provided. Theoretically, logs generated based on these two APIs can be bound to many logging implementations. SLF4J and Log4j 2 provide many binders. The following list shows several possible binding chains.

  • SLF4J → Logback
  • SLF4J → slf4j-log4j12 → Log4j
  • SLF4J → log4j-slf4j-impl → Log4j 2
  • SLF4J → slf4j-jdk14 → JUL
  • SLF4J → slf4j-jcl → JCL
  • JCL → JUL
  • JCL → Log4j
  • log4j2-api → log4j2-cor
  • log4j2-api → log4j-to-slf4j → SLF4J

The following diagram shows the binding relationship.

2

0 0 0
Share on

Alibaba Cloud Community

864 posts | 196 followers

You may also like

Comments

Alibaba Cloud Community

864 posts | 196 followers

Related Products

  • Web Hosting Solution

    Explore Web Hosting solutions that can power your personal website or empower your online business.

    Learn More
  • Web Hosting

    Explore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.

    Learn More
  • Function Compute

    Alibaba Cloud Function Compute is a fully-managed event-driven compute service. It allows you to focus on writing and uploading code without the need to manage infrastructure such as servers.

    Learn More
  • Elastic High Performance Computing Solution

    High Performance Computing (HPC) and AI technology helps scientific research institutions to perform viral gene sequencing, conduct new drug research and development, and shorten the research and development cycle.

    Learn More