Log4j2 - From Vision to Architecture

To capture the architectural elements and relationships of Log4j2, in this essay, we first explore the architectural style and design patterns applied in Log4j2, then we depict the architecture from multiple views, finally, we briefly summarize the API design principles of Log4j2.

Main architectural style

At the beginning of Log4j2 design, its goal is to implement a simple and efficient logging framework that is perfectly compatible with existing logging tools such as SLF4J or Log4j 1.x. To achieve this goal, the members of the Apache Logging Project Management Committee (PMC)1 chose a Layered Architecture using the Facade Design Pattern to develop Log4j2. Furthermore, in order to make Log4j2 more flexible and easy to extend, they applied the Microkernel Architecture in the logging implementation. The whole picture of Log4j2’s architecture is as follows.

Figure: Main architecture of Log4j2

Layered Architecture using the Facade Design Pattern

Layered architecture2 is one of the most common architectural styles and is the de facto standard for most applications, primarily because of its simplicity, familiarity, and low cost. The Facade Pattern3 is widely used in the Java logging framework because it allows the unified use of different logging implementations that exist simultaneously in a project. As regards the Log4j2, its main components are log4j-api and log4j-core, which represent the logging facade layer and logging implementation layer respectively. Besides, there are some adapters and bridges that can use Log4j2 in combination with other logging frameworks, and we consider these components as the support layer.

Microkernel Architecture

The Microkernel Architecture2 is a relatively simple monolithic architecture consisting of two components: a core system and plugin components, which provide extensibility and adaptability to the application, it meets the flexibility requirements of Log4j2 well and therefore, Log4j2 used this architecture to implement its plugin system. In the Log4j2 logging implementation, it designed everything to be plugin style and classified them as five kinds of plugins as well as the custom plugins4, so Log4j2 is easy to extend to new application scenarios.

Containers view

The container view describes the possible execution environments of the system and where the system may be deployed in.

Figure: The container view of Log4j2

Container Description
Java web application Log4j2 can be deployed in a Java EE Web application on the server in a Servlet container5.
Local Java application Log4j2 can be deployed in a local Java application. Log4j2 can also operate with a GUI, with the help of JMX GUI or JConsole6. This enables users to configure the logger and monitor the output.
Abstract logging APIs There are different logging facades serve as the abstraction for logging frameworks, which allows the user to plug in the desired logging framework at deployment time, via the facade APIs. For example, the Log4j2 SLF4J Binding allows applications coded to the SLF4J API to use Log4j2 as the implementation7.
Log4j2 API The Log4j2 API is the interface between applications and the Log4j2 implementation. It defines how the applications should call the Log4j2 functions and components required for implementers when creating a logging implementation8.
Log4j2 core The Log4j2 core contains the implementation of the core functionalities of Log4j2 to build a logging system.

Components view

The components of Log4j2 can be pretty simple: a log4j2-api as the facade and a log4j2-core as the implementation. So in this section, we would like to extend the meaning of “Component” to the Java classes in the log4j2-core, which could help developers understand how does Log4j2 perform its functions. The main components used in Log4j2 core are shown below.9

Figure: Classes in log4j2-core

LoggerContext

The LoggerContext acts as the anchor point for logging implementations. The Logger is obtained from the LoggerContext.

Configuration

Every LoggerContext has at least one Configuration. The Configuration contains all the Appenders, context-wide Filters, LoggerConfigs and contains the reference to the StrSubstitutor9.

Logger

Loggers are created by calling LogManager.getLogger() The Logger has a name and is associated with a LoggerConfig. Loggers can be associated with a different LoggerConfig to modify its behavior.

LoggerConfig

LoggerConfig objects are created when Loggers are declared in the logging configuration. The LogEvents must pass the Filters in the LoggerConfig before it will be passed to any Appenders. LoggerConfig also contains references to the set of Appenders to deliver the events.

Filter

Log4j2 provides Filters that can be applied before control is passed to any LoggerConfig, after control is passed to a LoggerConfig but before calling any Appenders, and on each Appender9.

Appender

Appender is the output destination of LogEvents. Each Logger can have multiple Appenders. Currently, appenders can be the console, files, remote socket servers, various database APIs, etc.

Layout

Associating a Layout with an Appender can enable the user to customize the output format, whereas the Appender takes care of sending the formatted output to its destination.

StrSubstitutor and StrLookup

The StrSubstitutor class and StrLookup interface were used to support evaluating LogEvents. Together they provide a mechanism to allow the configuration to reference variables coming from System Properties, the configuration file, the ThreadContext Map, StructuredData in the LogEvent9.

Connector View

The connection of the core components has been mentioned in the previous section, which is mainly implemented through function calls as these components have access to each other’s methods and classes.

The following connector view focuses on the connection between different logging APIs. With the Microkernel Architecture2, Log4j2 can use the specific plugins to make it applicable to different scenarios. The most widely used structure is a facade pattern, which makes logging independent of the actual implementation. Adapter jars like JUL Adapter are necessary if the application calls the API of another logging framework and the user wants to route logging calls to the Log4j2 implementation. On the other hand, since Log4j2 API is generic, it can depend on other logging libraries like SLF4J and Logback to log messages with a bridge jar like log4j-to-slf4j-2.x. Basicly, in order to use other logging libraries, we have to specify the binding of the logger we want to use, and we can use the corresponding logger implementation. The related libraries and their interactions are shown in the below graph 10.

Figure: Connection between different logging frameworks

Development view

You already know the structure of Log4j2, but there are a few things you need to do before you can run it. The requirements for running Log4j2 are shown in the README11:

  • Java 8 users should use 2.17.1 or greater.
  • Java 7 users should use 2.12.4.
  • Java 6 users should use 2.3.2.
  • Some features require optional dependencies; the documentation for these features specifies the dependencies.

As a framework for providing logging services for Java development, the first three requirements for the Java version are easy to understand, so this section focuses on the optional dependencies in the last one.

Because log4j2 has an API separation design, interfaces and implementations are placed in separate modules, so at least two JAR files are required. One of them is log4j-api, which provides the interface that applications should code to. The other should contain implementations of interfaces, and the user can choose Log4j2 implementation, SLF4J, or Logback according to his or her needs. Note that if users do not use log4j-core as an implementation, an additional adapter jar log4j-to-slf4j and bridge jar log4j-to-slf4j are needed (as shown below12). With this separation design that Log4j2 provides, it is possible to use multiple logging APIs and implementations in the same project. This allows for greater flexibility, ensuring that developers are not tied to a single API or implementation13. In addition, there are many modules for Log4j2 to support different usage scenarios, such as log4j-docker, log4j-mongodb, etc.

Figure: SLF4J Implementation

Run time view

Now that we know the building blocks of the program, let’s see what happens when we start Log4j2. In this section, we use the log4j-api as a facade and log4j-core as implementation

This part may be a little difficult to understand because of the specific methods involved. But just to be clear, initialization is the process of parsing the log configuration files and converting their structure into Java objects.

In the Log4j2 documentation, there is no concrete and detailed overview. But if we take a look at the source code we can get the diagram below14.

Figure: Initialization workflow

As shown in the diagram, LogManager is the entry point for Log4j2 startup. It is a critical component, and subsequent LoggerContext and Logger are obtained by calling static methods of LogManager. When the logManager.getLogger() method is invoked for the first time, initialization of the log system is triggered. It will generate a LoggerContext factory, which the user can specify, Log4jContextFactory by default. This method returns the result of a chained call that first calls getContext(), which returns a LoggerContext object. Then it calls the start() function to convert the configuration file structure into Java objects (Appender, Logger, etc.). As mentioned above, Log4j2 supports multiple configuration file formats, each managed by its corresponding ConfigurationFactory(XML is used as an example in the diagram).

Quality attributes

The key quality attributes defined before in essay 1 are performance and security.

Log4j2 features using a low latency asynchronous logger15. This is realized by the logger component in the system architecture. The application thread only captures the necessary information for the log event and the backstage thread will handle it asynchronously. This introduces the trade-off between performance and mechanism selection. While asynchronous logger has higher peak throughput and lower logging response latency, synchronous loggers offer quicker error responding for the exceptions during the logging process.

The separation of API and implementation in the architecture design provides benefits for the quality attribute of system security. In the Apache Log4j security vulnerabilities history16, applications using Log4j2 API but not Log4j2 core are uninfluenced in several Log4j2 vulnerabilities.

The architecture design of Log4j2 also contributes other quality attributes, including modularity and customizability. API separation provided by Log4j2 allows developers to use multiple logging framework APIs and realizations13, which offers great flexibility and caters for different needs in different situations. Log4j2 is designed and separated into multiple functional modules. This makes it easy for users to choose the functionalities they need or expand their current logging system for different deployment or new needs. Furthermore, this architecture design allows the Log4j2 team to make modifications in a safe and compatible way.

API design principles

The Log4j2 API provides the interface that applications should code to and provides the adapter components required for implementers to create a logging implementation8. All the APIs available to the user are in the log4j-api package. Although Log4j2 does not have explicit documentation on the principles that must be followed when developing APIs, by analyzing the existing API methods and pull requests related to API development, we summarize the design principles as follows.

  • Principle of Least Surprise: The Log4j2 APIs are very similar to the existing logging facade API such as SLF4J or Logback in that they both get the Logger instances via the getLogger() method, so users who have used these logging APIs can easily get started with Log4j2. In addition, the APIs’ name communicates what the method is and what it does, such as most methods start with get-, set-, is-, etc.
  • Backward Compatibility Principle: When making changes to an existing API, developers are asked not to change the name of the existing method, but to annotate it using the @Deprecated mark and add another new API17, which has the advantage of allowing developers to gradually transition to the new API without affecting the normal functioning of the application based on the old API version.
  • Small Interfaces Principle: API should be as small as possible but no smaller18. In Log4j2, each API is designed to be as single-responsibility as possible and to focus on only one thing.

  1. Apache Software Foundation. Apache Committee Information. Retrieved 12 March 2022 from: https://projects.apache.org/committee.html?logging ↩︎

  2. Richards, Mark, and Neal Ford. Fundamentals of Software Architecture: An Engineering Approach. O’Reilly Media, 2020. ↩︎

  3. Gamma, Erich, et al. Design patterns: elements of reusable object-oriented software. Pearson Deutschland GmbH, 1995. ↩︎

  4. Apache Log4j2. Plugins. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/plugins.html ↩︎

  5. Apache Log4j2. Using Log4j 2 in Web Applications. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/webapp.html#Servlet-3.0 ↩︎

  6. Apache Log4j2. Log4j JMX GUI. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/jmx.html ↩︎

  7. QOS.CH Sarl. Simple Logging Facade for Java (SLF4J). Retrieved 12 March 2022 from: https://www.slf4j.org/ ↩︎

  8. Apache Log4j2. Log4j 2 API. Retrieved 13 March 2022 from: https://logging.apache.org/log4j/2.x/manual/api.html ↩︎

  9. Apache Log4j2. Log4j2 Architecture. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/architecture↩︎

  10. Ralph Goers. *Getting the most out of the Log4j 2 API *. Retrieved 3 April 2022 from: https://www.ralphgoers.com/post/getting-the-most-out-of-the-log4j-2-api ↩︎

  11. Apache Log4j2 Github. README. Retrieved 10 March 2022 from: https://github.com/apache/logging-log4j2/blob/release-2.x/README.md ↩︎

  12. Apache Log4j2. Frequently Asked Questions. Retrieved 11 March 2022 from: https://logging.apache.org/log4j/2.x/faq.html#which_jars ↩︎

  13. Apache Log4j2. API Separation. Retrieved 11 March 2022 from: https://logging.apache.org/log4j/2.x/manual/api-separation.html ↩︎

  14. Zamperini. Log4j2 source code analysis. Retrieved 11 March 2022 from: https://dorgenjones.github.io/2017/03/13/%E5%9F%BA%E7%A1%80%E5%B7%A5%E5%85%B7/log/3.log4j2%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/ ↩︎

  15. Apache Log4j2. Asynchronous Loggers for Low-Latency Logging. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/async.html ↩︎

  16. Apache Log4j2. Apache Log4j Security Vulnerabilities. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/security.html ↩︎

  17. Apache Log4j2 Github. Pull request: Update documentation. Retrieved 13 March 2022 from: https://github.com/apache/logging-log4j2/pull/788#discussion_r822975410 ↩︎

  18. Joshua Bloch. How to Design a Good API and Why it Matters. Retrieved 13 March 2022 from: http://www.cs.bc.edu/~muller/teaching/cs102/s06/lib/pdf/api-design ↩︎