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:
- 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”). - 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 toSYSTEM_OUTand uses aPatternLayoutto 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 infilePattern, which is daily) andSizeBasedTriggeringPolicy(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 todebug, we are telling it to capture all log events of severityDEBUGand 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.