A Guide to Log4j for Logging in Java

Log4j is a logging framework for Java, facilitating the systematic recording of runtime information in software applications. Developed by the Apache Software Foundation, Log4j has become a standard tool in Java development since its inception in 1996.

Its primary purpose is to generate log messages that provide insights into the application's execution, aiding developers in debugging, monitoring, and analysing software behaviour. Developers use Log4j by incorporating its libraries into their Java projects.

Log 4j Scenario

Through Log4j, programmers can instrument their code with logging statements, placed to capture specific events or conditions during program execution. Log4j allows for fine-grained control over log messages, enabling developers to direct them to different outputs, set logging levels, and configure formatting.

Let us look into each aspects of Log 4j extensively in this blog post.

Table of Contents

  1. Getting Started with Log4j
  2. Formatting using Pattern Parameter
  3. Undertsanding Log levels
  4. Performance Optimization Strategies
  5. Log4j Vulnerability Explained
  6. Simple Steps to Log4j Security

Getting started with Log4j

Setting up Log4j in your Java application is essential for effective logging. Follow these steps to integrate Log4j, enhancing your ability to track and manage runtime information conveniently.

Setting up Log4j

Step 1: Make sure you have Java installed on your system. Log4j is a Java-based framework, so you need Java to run Log4j in your applications.

Step 2: Consider using an Integrated Development Environment (IDE) like Eclipse or IntelliJ IDEA. This is an optional step, but an IDE can make your development process more convenient.

Step 3: Download Log4j from the official website and include it in your project. This library enables advanced logging features in your Java application.

Step 4: If you are using a build tool like Maven or Gradle, add Log4j as a dependency in your project configuration file.

For Maven, add the following to your pom.xml

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.x.x</version> <!-- Replace with the latest version -->
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.x.x</version> <!-- Replace with the latest version -->
</dependency>

For Gradle, add the following to your build.gradle

implementation 'org.apache.logging.log4j:log4j-api:2.x.x' // Replace with the latest version
implementation 'org.apache.logging.log4j:log4j-core:2.x.x' // Replace with the latest version

If you are not using a build tool, manually download the JAR files and add them to your project's classpath.

Step 5: Create a Log4j configuration file (e.g., log4j2.xml or log4j2.properties). This file specifies how Log4j should behave and where the logs should be output.

log4j2.xml configuration:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

Step 6: Import Log4j classes into your Java classes

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Step 7: Get a logger instance

private static final Logger logger = LogManager.getLogger(YourClassName.class);

Step 8: Use the logger to log messages

logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");

Run your Java application, and Log4j will start logging messages according to your configuration.

Formatting using Pattern Parameter

In Log4j, the "pattern parameter" refers to the layout pattern used to format log messages. In simple terms, it typically refers to a predefined format or template that defines how log messages should be structured. Log messages are often formatted in a standardized way to make it easier for developers, system administrators, or tools to analyse and interpret them.

In many logging frameworks or libraries, developers can use placeholders or variables in the log message pattern to represent dynamic data that will be filled in when a log message is generated. These placeholders might include information like timestamps, log levels, source class/method names, or custom data relevant to the specific log event.

Let us consider an example to understand this,

[{timestamp}] [{logLevel}] {className}.{methodName}: {message}

In the example provided, terms like timestamp, log level, class name, method name, and message serve as placeholders, called specifiers. These specifiers determine which details should be part of a log message and how they should be formatted.

  1. The {timestamp} specifier represents the timestamp when the log message was generated.
  2. The {logLevel} specifier stands for the severity level of the log message (e.g., INFO, WARNING, ERROR).
  3. The {className} specifier represents the name of the class where the log message originated.
  4. The {methodName} specifier represents the name of the method or function where the log message was created.
  5. The {message} specifier holds the actual log message content.

These specifiers act as codes that get replaced with actual information when generating log messages, making it easier to include specific details in a standardized format.

[2024-01-11 14:30:00] [INFO] MyClass.myMethod: Operation successful

In this example:

  • {timestamp} is replaced with "2024-01-11 14:30:00" because that was the timestamp when the log message was created.
  • {logLevel} is replaced with "INFO" because it is an informational message.
  • {className} is replaced with "MyClass" because the message originated from that class.
  • {methodName} is replaced with "myMethod" because the log message was generated in that method.
  • {message} is replaced with "Operation successful," which is the actual content of the log message.

This kind of structured logging is particularly helpful in debugging and monitoring applications.

Understanding Log Levels

Log levels are used in software development and systems to categorize and prioritize log messages based on their severity or importance. They help developers and administrators understand and troubleshoot issues by providing a clear indication of the nature and impact of a particular event. The common log levels, in increasing order of severity are,

  1. TRACE: This log level is the least critical and is commonly employed to record intricate details about a program's execution.
  2. DEBUG: Messages that provide detailed information for debugging purposes. These are typically used during development and are not meant for production.
  3. INFO: Informational messages that confirm that things are working as expected. These messages are used to provide general information about the application's operation.
  4. WARN: Messages that indicate potential issues or situations that might cause problems in the future but don't necessarily require immediate attention. The application is still functioning, but caution is advised.
  5. ERROR: Messages that indicate a failure or error in the application. These events usually require attention as they signify a problem that needs to be addressed. The application may still be operational, but there might be degraded functionality.
  6. CRITICAL / FATAL: Messages that indicate a severe error that prevents the application from continuing to run. These events are critical and require immediate attention. The application is likely in an unstable state or has crashed.

Here is an example that involves all the common log levels,

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

public class LogLevelExample {

    private static final Logger logger = Logger.getLogger(LogLevelExample.class.getName());

    public static void main(String[] args) {
        // Configure logging to display messages of all levels
        logger.setLevel(Level.ALL);

        // Create a console handler and set its level
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setLevel(Level.ALL);

        // Add the console handler to the logger
        logger.addHandler(consoleHandler);

        divideNumbers(10, 2);
        divideNumbers(8, 0);
    }

    private static void divideNumbers(int a, int b) {
        try {
            int result = a / b;
            logger.log(Level.FINE, "DEBUG: Division result: " + result);
            logger.log(Level.INFO, "INFO: Successful division of " + a + " by " + b);
        } catch (ArithmeticException e) {
            logger.log(Level.WARNING, "WARNING: Attempted division by zero!");
        } catch (Exception e) {
            logger.log(Level.SEVERE, "ERROR: An error occurred during division: " + e.getMessage(), e);
        } finally {
            logger.log(Level.SEVERE, "CRITICAL: Division operation completed");
        }
    }
}

Output:

Performance Optimization Strategies

i.) Async Loggers: Async Loggers in Apache Log4j 2.x are designed to improve logging performance by processing log events asynchronously in a separate thread. This feature helps applications to continue their execution without waiting for the logging operations to complete, thus reducing the impact on the application's performance.

<AsyncLogger name="com.example" level="info" includeLocation="true">
   <AppenderRef ref="Console"/>
</AsyncLogger>

ii.) Appender Configuration: Choose the appropriate appenders based on your needs. For example, the ConsoleAppender is generally slower than a file appender. Also, be cautious with appenders like SocketAppender, as they can introduce additional overhead.

iii.) Log Level: Choose appropriate log levels in production to reduce logging volume and enhance Log4j's performance. Lower log levels like DEBUG and TRACE generate more entries and may impact throughput. Optimize log levels based on the importance of information and the desired balance between performance and logging detail.

iv.) Buffering: Buffering  involves the practice of collecting and batching log events before writing them to the output destination. This can significantly improve efficiency by reducing the number of individual write operations. Adjust buffer sizes and configurations based on thorough performance testing and monitoring.

v.) Garbage collection: Be aware of the impact on garbage collection, especially in high-throughput systems. Logging excessively or creating too many loggers dynamically can contribute to increased garbage collection activity.

Log4j offers garbage-free logging, a strategy aimed at minimizing garbage collection during the logging process. It seeks to eliminate the necessity for garbage collection by reusing log message objects rather than creating new ones for each message.

This is accomplished by allocating a fixed-size buffer that can be recycled for multiple log messages, thereby reducing the creation of new objects and the subsequent need for garbage collection.

Log4j Vulnerability Explained

The Log4j exploit refers to a critical vulnerability in the Apache Log4j library, a widely used logging library in Java applications. The vulnerability, formally known as CVE-2021-44228, was publicly disclosed in December 2021.

The initial identification of the vulnerability occurred within a version of the Minecraft game. Those with malicious intent identified that Log4j was logging the game's chat, and by injecting harmful code into the chat, it triggered remote code execution (RCE).

The Log4j vulnerability allows attackers to execute arbitrary code remotely by exploiting a flaw in the library's handling of log messages. This can have serious implications for the security of affected systems, as it enables attackers to take control of the affected servers.

The severity of this vulnerability prompted widespread concern in the cybersecurity community, leading to urgent patching efforts by organizations and software developers to mitigate the risk. The vulnerability affects various software applications, and the impact can vary depending on how Log4j is used within those applications.

Simple Steps to Log4j Security

Identifying potential risks and determining effective ways to reduce them is a significant challenge. Organizations can adopt various measures to minimize the impact of Log4j exploits. Here are few keys measures,

  1. Adopt a DevSecOps Approach: Embed security practices throughout development and operations.
  2. Employ Network-Based Filtering: Implement web application firewalls like Cloudflare. Block Log4j exploits before targeting applications.
  3. Scan Applications for Vulnerabilities: Regularly scan for Log4j vulnerabilities. Use tools from CERT-CC and CISA for detection.
  4. Patch Vulnerable Applications: Promptly update applications to the latest Log4j version and mitigate all publicly disclosed vulnerabilities.
  5. Continuous Monitoring for Malicious Activity: Use threat hunting techniques for Log4j attacks. Detect and respond to suspicious activities promptly.
  6. Stay Informed and Responsive: Stay updated on Log4j developments and patches and Proactively address potential security vulnerabilities.
  7. Collaborate with Vendors: Collaborate closely to address Log4j-related risks. Try to reduce vulnerabilities in third-party applications.
  8. Educate and Train Teams: Conduct regular security training for teams, inform teams about Log4j vulnerabilities and ensure adherence to secure coding and deployment practices.

Conclusion

Log4j emerges as a helpful tool in Java development, enriching applications with insightful logs and streamlined debugging capabilities. The journey begins with straightforward steps, ensuring Java installation, IDE utilization, and Log4j library integration into Java projects.

Log4j, renowned for its flexibility, grants developers unparalleled control over log messages, elevating the debugging and monitoring experience. With its pattern parameters and log levels, Log4j transforms raw data into structured, meaningful insights, enriching the debugging process.

However, amidst this coding, lies the Log4j vulnerability (CVE-2021-44228), necessitating a steadfast embrace of DevSecOps principles and proactive security measures. From crafting precise log messages to optimizing performance with Log4j's asynchronous loggers, buffer configurations, and garbage-free logging, the Java landscape thrives with Log4j's influential presence.

Log4j in Java not only captures runtime nuances but also demands a vigilant and informed developer's touch for a resilient and secure coding journey.


Atatus Logs Monitoring and Management

Atatus offers a Logs Monitoring solution which is delivered as a fully managed cloud service with minimal setup at any scale that requires no maintenance. It monitors logs from all of your systems and applications into a centralized and easy-to-navigate user interface, allowing you to troubleshoot faster.

We give a cost-effective, scalable method to centralized logging, so you can obtain total insight across your complex architecture. To cut through the noise and focus on the key events that matter, you can search the logs by hostname, service, source, messages, and more. When you can correlate log events with APM slow traces and errors, troubleshooting becomes easy.

Try your 14-day free trial of Atatus.