How to Configure Multiple Log Appenders in Log4j2 (Console and File)

In modern application development, logging isn’t a one-size-fits-all task. During development, you want to see logs immediately on your console. In production, you need those same logs persisted to a file for auditing, debugging, and historical analysis. What if you want both at the same time? This is where Log4j2’s powerful feature of multiple appenders comes in.

An Appender in Log4j2 is simply a destination for your log messages. By configuring multiple appenders, you can direct your application’s logs to various destinations simultaneously—like the console, a rolling file, a database, or even a messaging queue.

In this tutorial, we’ll walk through the most common use case: configuring Log4j2 to write logs to both the console and a rolling file.

The Core Concept: Define and Attach

Configuring multiple appenders in Log4j2 is a straightforward two-step process:

  1. Define Appenders: In the <Appenders> section of your configuration file, you define each output destination you want to use. Each appender is given a unique name (e.g., “Console”, “File”).
  2. Attach Appenders to Loggers: In the <Loggers> section, you tell a logger (like the root logger) which of the defined appenders it should use. You do this by referencing the appenders by their names.

Let’s see this in action.

Step 1: Maven Dependencies

First, ensure your project has the necessary Log4j2 dependencies. If you’re using Maven, add the following to your pom.xml:

<!-- pom.xml -->

<properties>
    <log4j2.version>2.23.1</log4j2.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>${log4j2.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>${log4j2.version}</version>
    </dependency>
</dependencies>

Always try to use the latest stable version of Log4j2.

Step 2: Create the log4j2.xml Configuration

Create a log4j2.xml file in your project’s src/main/resources directory. This is where we’ll define and attach our appenders.

Here is the complete configuration file. We’ll break it down section by section below.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">

    <!-- Define all Appenders -->
    <Appenders>
        <!-- 1. Console Appender for stdout -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <!-- 2. Rolling File Appender for log files -->
        <RollingFile name="File"
                     fileName="logs/app.log"
                     filePattern="logs/app-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>
    </Appenders>

    <!-- Attach Appenders to Loggers -->
    <Loggers>
        <!-- Root logger: applies to all classes -->
        <Root level="debug">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File"/>
        </Root>
    </Loggers>

</Configuration>

Breaking Down the Configuration

The <Appenders> Section

This block contains the definitions for all our output destinations.

  • <Console name="Console" ...>: We define a console appender and give it the name “Console”. It’s configured to write to SYSTEM_OUT and uses a PatternLayout to format the log messages.
  • <RollingFile name="File" ...>: This defines our second appender, which we name “File”.
    • fileName="logs/app.log": This is the active file where logs are currently being written.
    • filePattern="logs/app-%d{yyyy-MM-dd}-%i.log.gz": This pattern defines the naming convention for archived log files. When a log file is “rolled over,” it will be renamed and compressed (due to .gz).
    • <Policies>: This determines when a rollover should occur. We’ve specified two conditions: TimeBasedTriggeringPolicy (rolls over based on the date pattern in filePattern, which is daily) and SizeBasedTriggeringPolicy (rolls over when the file reaches 10 MB).
    • <DefaultRolloverStrategy max="10">: This keeps a maximum of 10 archived log files.

The <Loggers> Section

This is where we connect our loggers to the appenders.

  • <Root level="debug">: We are configuring the root logger, which is the ancestor of all other loggers. By setting its level to debug, we are telling it to capture all log events of severity DEBUG and higher (i.e., DEBUG, INFO, WARN, ERROR, FATAL).
  • <AppenderRef ref="Console"/>: This is the magic link. It tells the root logger to send all its captured log events to the appender named “Console”.
  • <AppenderRef ref="File"/>: This second reference tells the root logger to also send the same log events to the appender named “File”.

And that’s it! Every log message processed by the root logger will now go to both destinations.

Step 3: Java Code to Test the Setup

Now, let’s write a simple Java class to generate some logs and see our configuration in action.

package com.ankurm.log4j2.demo;

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

public class MultiAppenderTest {

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

    public static void main(String[] args) {
        System.out.println("--- Starting Log Test ---");

        logger.debug("This is a debug message.");
        logger.info("This is an info message.");
        logger.warn("This is a warning message.");
        logger.error("This is an error message.");

        System.out.println("--- Log Test Finished ---");
    }
}

Step 4: Verify the Output

When you run the MultiAppenderTest class, you will see two things happen.

1. Console Output

Your IDE’s console or terminal will display the log messages, formatted according to the “Console” appender’s pattern.

--- Starting Log Test ---
14:30:15.123 [main] DEBUG com.ankurm.log4j2.demo.MultiAppenderTest - This is a debug message.
14:30:15.125 [main] INFO  com.ankurm.log4j2.demo.MultiAppenderTest - This is an info message.
14:30:15.125 [main] WARN  com.ankurm.log4j2.demo.MultiAppenderTest - This is a warning message.
14:30:15.126 [main] ERROR com.ankurm.log4j2.demo.MultiAppenderTest - This is an error message.
--- Log Test Finished ---

2. File Output

A new directory named logs will be created in your project’s root directory. Inside it, you will find a file named app.log. Its content will be formatted according to the “File” appender’s pattern:

2023-10-27 14:30:15,123 DEBUG MultiAppenderTest [main] This is a debug message.
2023-10-27 14:30:15,125 INFO  MultiAppenderTest [main] This is an info message.
2023-10-27 14:30:15,125 WARN  MultiAppenderTest [main] This is a warning message.
2023-10-27 14:30:15,126 ERROR MultiAppenderTest [main] This is an error message.

Success! You’re now logging to both the console and a file simultaneously.

Advanced Tip: Different Log Levels for Different Appenders

A common requirement is to show detailed (DEBUG) logs on the console but only save important (INFO and above) logs to the file. You can achieve this by adding a level filter to the AppenderRef.

Modify your <Root> logger configuration like this:

<Loggers>
    <Root level="debug">
        <!-- Console gets everything from DEBUG up -->
        <AppenderRef ref="Console"/>
        
        <!-- File only gets logs from INFO and higher -->
        <AppenderRef ref="File" level="info"/>
    </Root>
</Loggers>

With this change, the debug message will appear on the console but will not be written to app.log, helping you keep your log files clean and focused on significant events.

Conclusion

Configuring Log4j2 to send logs to multiple destinations is a powerful feature that provides immense flexibility for both development and production environments. By simply defining your desired appenders and referencing them in your loggers, you can create a robust and comprehensive logging strategy tailored to your application’s needs.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.