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.
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.
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
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 StrSubstitutor
9.
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 Appender
9.
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.
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.
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.
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 thegetLogger()
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 withget-
,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.
-
Apache Software Foundation. Apache Committee Information. Retrieved 12 March 2022 from: https://projects.apache.org/committee.html?logging ↩︎
-
Richards, Mark, and Neal Ford. Fundamentals of Software Architecture: An Engineering Approach. O’Reilly Media, 2020. ↩︎
-
Gamma, Erich, et al. Design patterns: elements of reusable object-oriented software. Pearson Deutschland GmbH, 1995. ↩︎
-
Apache Log4j2. Plugins. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/plugins.html ↩︎
-
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 ↩︎
-
Apache Log4j2. Log4j JMX GUI. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/jmx.html ↩︎
-
QOS.CH Sarl. Simple Logging Facade for Java (SLF4J). Retrieved 12 March 2022 from: https://www.slf4j.org/ ↩︎
-
Apache Log4j2. Log4j 2 API. Retrieved 13 March 2022 from: https://logging.apache.org/log4j/2.x/manual/api.html ↩︎
-
Apache Log4j2. Log4j2 Architecture. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/architecture. ↩︎
-
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 ↩︎
-
Apache Log4j2 Github. README. Retrieved 10 March 2022 from: https://github.com/apache/logging-log4j2/blob/release-2.x/README.md ↩︎
-
Apache Log4j2. Frequently Asked Questions. Retrieved 11 March 2022 from: https://logging.apache.org/log4j/2.x/faq.html#which_jars ↩︎
-
Apache Log4j2. API Separation. Retrieved 11 March 2022 from: https://logging.apache.org/log4j/2.x/manual/api-separation.html ↩︎
-
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/ ↩︎
-
Apache Log4j2. Asynchronous Loggers for Low-Latency Logging. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/manual/async.html ↩︎
-
Apache Log4j2. Apache Log4j Security Vulnerabilities. Retrieved 12 March 2022 from: https://logging.apache.org/log4j/2.x/security.html ↩︎
-
Apache Log4j2 Github. Pull request: Update documentation. Retrieved 13 March 2022 from: https://github.com/apache/logging-log4j2/pull/788#discussion_r822975410 ↩︎
-
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 ↩︎