Geekflare is supported by our audience. We may earn affiliate commissions from buying links on this site.
Share on:

Logging with Log4j2: A Guide for Java Developers

Log4j2 for Java developers
Invicti Web Application Security Scanner – the only solution that delivers automatic verification of vulnerabilities with Proof-Based Scanning™.

One of the most crucial parts of the software development process is having proper logging. With a lot of different Java logging frameworks available, it is important to choose one that’s easy to use. At the same time, your framework of choice should have high performance, extensible features, and allow customization. Log4j2 is a free Java logging library that ticks all the boxes.

Integrating Log4j2 with any application unlocks options like advanced filtering, Java 8 lambda support, property lookups, and custom log levels. Let’s take a look at how you can add Log4j2 to your projects and what features can help you stay on top of your game.

What is Log4j2?

A developer writing code on his laptop

Logging is the method of capturing useful information, known as logs, that can be referenced and analyzed later. You can use the logs to quickly debug application code. Application logs help understand the code flow and tackle production issues and errors.

Other than the diagnostic use cases, logs are also used for audit purposes—for example, tracking whether or not a notification message has been successfully sent to the user.

Log4j2 is one of the most popular Java logging libraries. It’s a successor to the very influential Log4j library. Developed by the Apache Software Foundation and a part of the Apache Logging Services, Log4j2 is a Free and Open Source Software (FOSS) distributed under the Apache License, version 2.0.

Log4j2 is built on top of the solid foundation of the original Log4j. There are advantages of using a Logger over simple print statements of System.out.println(). This includes having control over which messages to show up while avoiding other log messages. Having proper logs is crucial in a production environment where debuggers aren’t available.

How to add Log4j2 to your project?

There are several ways of adding Log4j2 to your Java project. It is advisable to be on Java 8 or above in order to use all the features of Log4j2.

Let’s discuss the various methods by which you can add Log4j2 depending on the requirements you might have.

Adding Log4j2 to projects using Apache Maven

If your project uses Apache Maven as the build system, the Log4j2 dependencies need to be added to the pom.xml file.

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

In order to make it easier to maintain the same version across different artifacts, Log4j2 has a Bill of Material (BOM) pom.xml file. If you add it under your dependency management, you don’t have to individually add the versions.

<!-- Add the BOM to the dependencyManagement -->
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-bom</artifactId>
      <version>2.20.0</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

<!-- Once the BOM is added, the versions are not required -->
<dependencies>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
  </dependency>
</dependencies>

Adding Log4j2 to projects using Apache Gradle

In case you use Apache Gradle as your build tool, you can add the Log4j2 dependencies to your build.gradle file.

dependencies {
  implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
  implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
}

If you’re on Gradle version 5.0 or above, you have the option of importing the Log4j2 Maven Bill Of Materials (BOM) in order to maintain consistent dependency versions. This can be achieved by adding the following to your build.gradle file.

dependencies {
  implementation platform('org.apache.logging.log4j:log4j-bom:2.20.0')

  implementation 'org.apache.logging.log4j:log4j-api'
  runtimeOnly 'org.apache.logging.log4j:log4j-core'
}

For Gradle versions 2.8-4.10, there is no option for directly importing the Maven BOM. You need to add an additional plugin for the dependency management functionality.

plugins {
  id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

dependencyManagement {
  imports {
    mavenBom 'org.apache.logging.log4j:log4j-bom:2.20.0'
  }
}

dependencies {
  implementation 'org.apache.logging.log4j:log4j-api'
  runtimeOnly 'org.apache.logging.log4j:log4j-core'
}

Adding Log4j2 to standalone applications without a build tool

If your project does not have a build tool then you can download the required artifact version of Log4j2 from the official Log4j2 download page.

Once you download them, you need to ensure that your application’s classpath includes the following jars.

  • log4j-api-2.20.0.jar
  • log4j-core-2.20.0.jar

What are the components in Log4j2?

In order to understand the features of Log4j2 and utilize its capabilities to the fullest, it is important to understand how Log4j2 works. Under the surface, several building blocks make up Log4j2. Let’s talk about them one by one.

Block diagram showing the different components that build up Log4j2

#1. LoggerContext

The LoggerContext is the central unit of the logging system. It holds all the Loggers requested in the application. It also holds a reference to the Configuration.

#2. Configuration

The Configuration contains all the information required by the logging system. This includes the Loggers, Appenders, Filters, and more. In Log4j2, you can define the Configuration using various file formats such as XML, JSON, and YAML, and also programmatically through the Log4j2 API.

An automatic reload takes place whenever any property changes in the Configuration. Hence, there is no requirement for an application restart.

#3. Logger

The main component of the Log4j2 system is the Logger. Loggers are obtained inside the application code using the LogManager.getLogger() statement and are used to generate logs. Log messages can be generated at various severity levels, such as debug, info, warn, error, and fatal.

#4. LoggerConfig

The LoggerConfig is responsible for the behavior of a specific Logger. It defines the behavior and settings for logging events generated by that particular logger. It allows the configuration of different logging levels, set up of appenders, and application of filters.

#5. Filter

You can selectively process log events in Log4j2 using Filters. Filters are applied based on specific criteria. You can apply these filters to loggers or appenders. Filters control which log events are allowed to pass through the logging pipeline for further processing. With the help of filters, the logging behavior can be fine-tuned, ensuring only the relevant logs are processed.

#6. Appender

The destination of any log message is determined by the Appender. A single Logger can have multiple Appenders. A log event will be sent to all the Appenders for the given Logger. Log4j2 has a lot of preconfigured appenders. For example, ConsoleAppender is used to log messages to the console, and FileAppender is used to output messages to a file. Each Appender needs its own Layout that determines how the final log message will look like.

#7. Layout

In Log4j2, the Layout is used to define how the final log message will look like. A Layout is associated with an Appender. While an Appender determines the output destination, the Layout describes how the message will be outputted.

Top 5 features of Log4j2

A person sitting and learning different techniques in Java

Log4j2 is feature-rich, and that’s what sets it apart from other Java logging frameworks available. From having Asynchronous Loggers to supporting Java 8 lambdas, Log4j2 has an edge over others. Let’s discuss some of the notable features of this framework.

#1. Extending the functionalities using Plugins

In Log4j 1.x, in order to create extensions, a lot of code modifications were required. Log4j2 solves the problem of extensibility by introducing the Plugin system.

You can declare a new Plugin by using the @Plugin annotation on your class. By using the power of Plugins, you can create your own components like Filters and Appenders. Third-party components can also be easily added to the library.

#2. Java 8 Lambda support

With the release of Log4j2 version 2.4, the support for Java 8 lambda expressions were introduced. With lambda expressions, you can define your logging logic inline. This reduces the need for multi-line checks or anonymous inner classes. This also ensures that expensive methods are not executed unnecessarily. Thus not only is the code cleaner and easier to read but the system overhead is also reduced.

Let’s consider an example where you log the result of an expensive operation, but only if the debug level is enabled. Prior to the support for lambdas, this would be performed using the below-mentioned code:

if (logger.isDebugEnabled()) {
    logger.debug("The output of the given operation is: {}", expensiveOperation());
}

Having multiple such use cases would unnecessarily introduce conditional checks. However, with Log42, the same action can be performed as follows:

logger.debug("The output of the given operation is: {}", () -> expensiveOperation()

The method exprensiveOperation() is only evaluated if the debug level is enabled. There is no need for any explicit checks.

#3. Asynchronous Loggers

Every log event is an I/O operation, which increases system overhead. In order to mitigate this, Log4j2 introduces Asynchronous Loggers which run in a separate thread from the application thread. When using Asynchronous Loggers, the caller thread immediately gets back control after invoking the logger.log() method.

This allows it to continue with the application logic rather than waiting for the logging event to complete. Leveraging this asynchronous behavior achieves a greater logging throughput. You can either choose to make all loggers asynchronous by default or have a mixture of both synchronous and asynchronous behavior.

#4. Garbage-free Logging

In Java, garbage collection is the process by which unused objects in the application are automatically cleared up. Although you do not have to manually take care of this operation, garbage collection does have its own overhead.

If your application creates too many objects in a short period of time, the garbage collection process might take up more system resources than necessary. Several logging libraries, including previous versions of Log4j, create a lot of temporary objects during the logging process. Subsequently, the increased pressure on the garbage collector impacts the system’s performance.

Since version 2.6, Log4j2 runs in “garbage-free” mode. This is the default behavior. Hence objects are reused, and the creation of temporary ones is heavily reduced.

The following images showcase how Log4j2 version 2.6 mitigates the problem of unnecessary objects, compared to Log4j2 version 2.5.

Java Flight Recording statistics showing objects created by log4j2-2.5
In Log4j2 version 2.5, a lot of temporary objects are created during the logging process; Source: apache.org
Java Flight Recording statistics showing almost no objects created by Log4j2 version 2.6
In Log4j2.6, there are no temporary objects created during the logging process; Source: apache.org

#5. Lookups

In log4j2, you can add contextual information to your logs using Lookups. By utilizing them, you can add data from various sources, such as system properties, environment variables, or custom-defined values. Thus, you can include relevant information that is dynamically fetched, making the logs more useful.

Let’s consider an example where you want to log the user’s session id with all log lines. This would allow you to search for all logs corresponding to a session id.

The brute-force way of doing this would be to explicitly add the session id individually, which becomes difficult to maintain. Soon you might forget to add it, thus losing valuable information.

logger.info("The user data has been fetched for session id {}", sessionId);
...
logger.info("The transaction has been processed for session id {}", sessionId);
...
logger.info("Request has been successfully processed for session id {}", sessionId);

A better way of doing it would be to use the Context Map Lookup. The session id can be added to the Thread Context in the application code. The value can then be used inside the Log4j2 configuration. Thus the need for explicitly mentioning it in log messages is eliminated.

ThreadContext.put("sessionId", sessionId);

Once the value is added, the same can be used in Lookup using the keyword ctx.

<File name="Application" fileName="application.log">
  <PatternLayout>
    <pattern>%d %p %c{1.} [%t] $${ctx:sessionId} %m%n</pattern>
  </PatternLayout>
</File>

How to make Custom Log Levels in Log4j2?

Log levels in Log4j2 are used to categorize log events based on their severity or importance. You can control the log level when you are logging a message in the application code.

For example, logger.debug() adds the DEBUG level. Correspondingly, logger.error() adds the ERROR level. This determines which messages finally show up in the output. You can configure the log level in the Configuration file.

The preconfigured Log Levels in Log4j2 and their corresponding values are mentioned below.

OFF0
FATAL100
ERROR200
WARN300
INFO400
DEBUG500
TRACE600
ALLMAX VALUE

If the Log Level is set to a particular level, then all the log lines for that corresponding value and the ones above it (with lesser value) are outputted. The others are ignored.

For example, if you set the log level to WARN, then WARN, ERROR, and FATAL messages will be shown. Any log line with a different level will be ignored. This is especially useful when you are running the same code in different environments.

You might want to set the Log Level to INFO or DEBUG when running the code in your development environment. This will allow you to see more logs and help in the development process. However, when running in a production environment, you would want to set it to ERROR. Thus, you’ll be able to focus on finding the issue in case any anomaly occurs and not have to go through unnecessary log lines.

It might happen that you want to add your own Custom Log Level in addition to the pre-configured ones. Log4j2 allows you to do so easily. Let’s see how you can add your own Log Levels and use them in your application.

#1. Adding Custom Log Level using the Configuration File

You can add custom log levels by declaring them in the configuration file.

In the example below, a custom log level named NOTICE has been defined with a value of 450. This places it between INFO (with a value of 400) and DEBUG (with a value of 500). This means that if the level is set to NOTICE, then INFO messages will be logged, but DEBUG messages will be skipped.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <CustomLevels>
    <CustomLevel name="NOTICE" intLevel="450" />
  </CustomLevels>
 
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout pattern="%d %-7level %logger{36} - %msg%n"/>
    </File>
  </Appenders>
  <Loggers>
    <Root level="trace">
      <AppenderRef ref="MyFile" level="NOTICE" />
    </Root>
  </Loggers>
</Configuration>

#2. Adding Custom Log Level in code

Other than declaring them in the Configuration file, you can define your own custom log levels in your code.

final Level VERBOSE = Level.forName("VERBOSE", 550);

This will create a new log level named VERBOSE. This log level will lie between DEBUG (with a value of 500), and TRACE (with a value of 600). If the logger is set to the level VERBOSE, then all log messages of VERBOSE and above will be logged, including DEBUG. However, TRACE messages will be skipped.

#3. Using the Custom Log Level in the code

Custom log levels first need to be declared before they are used. You can declare them either in the configuration file or in your code. Once declared, you are free to use them.

This code example shows how you can declare a custom level called NOTICE, and then use the same.

final Level NOTICE = Level.forName("NOTICE", 550);

final Logger logger = LogManager.getLogger();
logger.log(NOTICE, "a notice level message");

Although this will generate the required message with the newly created level, it might become cumbersome to always pass the level explicitly. Thankfully, you can generate source code so that you get helper methods for logging your custom levels. Using the same, you will be able to use your own method of logger.notice() similar to how you would use logger.debug() or logger.error().

Log4j2 comes with a utility that helps you create your own extended loggers. The following command creates a Java file called CustomLogger.java. This file contains the existing log methods, along with the newly generated methods for the NOTICE level.

java -cp log4j-core-2.20.0.jar org.apache.logging.log4j.core.tools.ExtendedLoggerGenerator \
        com.example.CustomLogger NOTICE=450 > com/example/CustomLogger.java

Once the file is generated, you can use the class in your code for creating new loggers. These loggers will contain additional methods for your custom log level. You can thus extend the functionality of your loggers.

final Logger logger = CustomLogger.create(ValueFirstSmsSender.class);

//this new method is similar to using logger.debug()
logger.notice("a notice level message");

Conclusion

Log4j2 is a very powerful Java logging framework, that offers a wide range of features, configurations, performance improvements, and more. With logs being a very important part of the software development process, having a robust framework like Log4j2 enhances the application’s abilities.

Log4j2’s flexibility and extensibility allow proper capturing of events happening in your application. Subsequently, it empowers you to think of logs as a powerful tool for debugging and auditing. With all of its features and improvements, Log4j2 stands apart and makes itself the preferred choice in a diverse range of software projects.

You may also be interested in these Java IDEs and online compliers.

This article was reviewed by Usha Romesh
Thanks to our Sponsors
More great readings on Development
Power Your Business
Some of the tools and services to help your business grow.
  • Invicti uses the Proof-Based Scanning™ to automatically verify the identified vulnerabilities and generate actionable results within just hours.
    Try Invicti
  • Web scraping, residential proxy, proxy manager, web unlocker, search engine crawler, and all you need to collect web data.
    Try Brightdata
  • Monday.com is an all-in-one work OS to help you manage projects, tasks, work, sales, CRM, operations, workflows, and more.
    Try Monday
  • Intruder is an online vulnerability scanner that finds cyber security weaknesses in your infrastructure, to avoid costly data breaches.
    Try Intruder