
Log4j2 Example Tutorial – Configuration, Levels, and Appenders
If you’re running any kind of Java application on your server, you’ve probably found yourself wondering where the hell that mysterious error went or why your application just silently crashed at 3 AM. Enter Log4j2 – Apache’s latest and greatest logging framework that’ll save your sanity and help you sleep better at night. This comprehensive tutorial will walk you through everything you need to know about Log4j2, from basic configuration to advanced appenders and performance tuning. Whether you’re managing a single application or a complex distributed system, understanding Log4j2 is crucial for effective monitoring, debugging, and maintaining healthy server operations.
How Log4j2 Works Under the Hood
Log4j2 operates on a simple but powerful architecture that consists of three main components: Loggers, Appenders, and Layouts. Think of it as a well-orchestrated mail system – Loggers are your postal workers who collect messages, Appenders are the delivery trucks that transport them to various destinations, and Layouts are the formatting rules that determine how your mail looks when it arrives.
The framework uses a hierarchical logger system where loggers inherit properties from their parents. The root logger sits at the top of this hierarchy, and all other loggers branch out from it like a family tree. When your application generates a log message, Log4j2 evaluates it against the configured log level, applies any filters, formats it according to the specified layout, and then sends it to the configured appenders.
Here’s what makes Log4j2 special compared to its predecessors:
- Asynchronous Logging: Uses the LMAX Disruptor library for lightning-fast async logging
- Plugin Architecture: Everything is a plugin, making it incredibly extensible
- Automatic Configuration Reloading: Changes to config files are picked up automatically
- Lambda Support: Lazy evaluation of log messages for better performance
- Custom Log Levels: Define your own log levels beyond the standard ones
Quick and Easy Log4j2 Setup
Let’s get you up and running with Log4j2 in your server environment. I’ll assume you’re working with a typical Java application deployment scenario.
Step 1: Add Dependencies
First, grab the necessary JAR files. If you’re using Maven, add this to your pom.xml:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.21.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.21.1</version>
</dependency>
</dependencies>
For Gradle users:
dependencies {
implementation 'org.apache.logging.log4j:log4j-core:2.21.1'
implementation 'org.apache.logging.log4j:log4j-api:2.21.1'
}
If you’re going old school with manual JAR management:
wget https://archive.apache.org/dist/logging/log4j/2.21.1/apache-log4j-2.21.1-bin.tar.gz
tar -xzf apache-log4j-2.21.1-bin.tar.gz
cp apache-log4j-2.21.1-bin/log4j-*.jar /path/to/your/lib/
Step 2: Create Your Configuration File
Log4j2 will automatically look for configuration files in this order: log4j2-test.json/xml/yaml, then log4j2.json/xml/yaml. Create a file called `log4j2.xml` in your classpath (usually src/main/resources for Maven projects):
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="FileAppender" fileName="/var/log/myapp/application.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Logger name="com.mycompany.myapp" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="FileAppender"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Step 3: Set Up Your Application
Now let’s create a simple Java class to test our logging setup:
package com.mycompany.myapp;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MyApp {
private static final Logger logger = LogManager.getLogger(MyApp.class);
public static void main(String[] args) {
logger.trace("This is a trace message");
logger.debug("Debug message - application starting");
logger.info("Application started successfully");
logger.warn("This is a warning message");
logger.error("This is an error message");
logger.fatal("This is a fatal message");
}
}
Step 4: Prepare Your Server Environment
Make sure your log directory exists and has proper permissions:
sudo mkdir -p /var/log/myapp
sudo chown $USER:$USER /var/log/myapp
sudo chmod 755 /var/log/myapp
Compile and run your application:
javac -cp "lib/*:." com/mycompany/myapp/MyApp.java
java -cp "lib/*:." com.mycompany.myapp.MyApp
Understanding Log Levels and When to Use Them
Log4j2 comes with six standard log levels, each serving a specific purpose in your application monitoring strategy:
Level | When to Use | Performance Impact | Production Recommended |
---|---|---|---|
TRACE | Ultra-detailed debugging, method entry/exit | High | No |
DEBUG | Detailed diagnostic information | Medium-High | Only when troubleshooting |
INFO | General application flow, startup/shutdown | Low | Yes |
WARN | Potentially harmful situations | Very Low | Yes |
ERROR | Error events that don’t stop the application | Very Low | Yes |
FATAL | Critical errors that may cause shutdown | Very Low | Yes |
Here’s a practical example showing appropriate usage:
public class DatabaseConnection {
private static final Logger logger = LogManager.getLogger(DatabaseConnection.class);
public Connection getConnection() {
logger.trace("Entering getConnection() method");
logger.debug("Attempting to connect to database: {}", databaseUrl);
try {
Connection conn = DriverManager.getConnection(databaseUrl, username, password);
logger.info("Successfully connected to database");
return conn;
} catch (SQLException e) {
logger.error("Failed to connect to database: {}", e.getMessage());
// Try backup database
try {
Connection backupConn = DriverManager.getConnection(backupUrl, username, password);
logger.warn("Connected to backup database due to primary failure");
return backupConn;
} catch (SQLException backupException) {
logger.fatal("Both primary and backup database connections failed", backupException);
throw new RuntimeException("Database unavailable", backupException);
}
} finally {
logger.trace("Exiting getConnection() method");
}
}
}
Mastering Appenders: Where Your Logs Go
Appenders are the workhorses of Log4j2 – they determine where your log messages end up. Let’s explore the most useful ones for server environments:
ConsoleAppender: For Development and Debugging
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %highlight{%-5level} %style{%logger{36}}{cyan} - %msg%n"/>
</Console>
FileAppender: Basic File Logging
<File name="FileAppender" fileName="/var/log/myapp/application.log" append="true">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
RollingFileAppender: The Server Admin’s Best Friend
This is probably the most important appender for production servers. It automatically rotates log files based on size or time, preventing your disk from filling up:
<RollingFile name="RollingFile"
fileName="/var/log/myapp/application.log"
filePattern="/var/log/myapp/application-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="30" compressionLevel="6">
<Delete basePath="/var/log/myapp" maxDepth="2">
<IfFileName glob="application-*.log.gz"/>
<IfLastModified age="30d"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
This configuration:
- Rotates daily at midnight OR when the file reaches 100MB
- Compresses old logs with gzip
- Keeps maximum 30 files
- Automatically deletes logs older than 30 days
AsyncAppender: For High-Performance Applications
Wrap your appenders with AsyncAppender for better performance:
<AsyncLogger name="com.mycompany.myapp" level="INFO" additivity="false">
<AppenderRef ref="RollingFile"/>
</AsyncLogger>
Or use the AsyncAppender:
<Async name="AsyncFile" bufferSize="262144">
<AppenderRef ref="RollingFile"/>
</Async>
SyslogAppender: Integration with System Logging
Perfect for centralized logging in enterprise environments:
<Syslog name="Syslog" host="localhost" port="514" protocol="UDP" facility="LOCAL0">
<LoggerFields>
<KeyValuePair key="application" value="myapp"/>
<KeyValuePair key="environment" value="production"/>
</LoggerFields>
</Syslog>
Real-World Configuration Examples
Production Server Configuration
Here’s a battle-tested configuration I use for production servers:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
<Property name="LOG_PATH">/var/log/myapp</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<RollingFile name="AppLog"
fileName="${LOG_PATH}/application.log"
filePattern="${LOG_PATH}/application-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="200MB"/>
</Policies>
<DefaultRolloverStrategy max="50">
<Delete basePath="${LOG_PATH}" maxDepth="2">
<IfFileName glob="application-*.log.gz"/>
<IfLastModified age="60d"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<RollingFile name="ErrorLog"
fileName="${LOG_PATH}/error.log"
filePattern="${LOG_PATH}/error-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- Reduce noise from third-party libraries -->
<Logger name="org.springframework" level="WARN"/>
<Logger name="org.hibernate" level="WARN"/>
<Logger name="org.apache.http" level="WARN"/>
<!-- Your application loggers -->
<AsyncLogger name="com.mycompany.myapp.service" level="INFO"/>
<AsyncLogger name="com.mycompany.myapp.controller" level="INFO"/>
<!-- Separate database operations -->
<Logger name="com.mycompany.myapp.database" level="DEBUG" additivity="false">
<AppenderRef ref="AppLog"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="AppLog"/>
<AppenderRef ref="ErrorLog"/>
</Root>
</Loggers>
</Configuration>
Development Environment Configuration
For development, you want more verbose logging and prettier console output:
<?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] %highlight{%-5level} %style{%logger{36}}{cyan} - %msg%n"/>
</Console>
<File name="DevLog" fileName="logs/dev.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Logger name="com.mycompany.myapp" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="DevLog"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Performance Optimization and Best Practices
Here’s where Log4j2 really shines compared to other logging frameworks. Let’s look at some performance benchmarks:
Framework | Synchronous (ops/sec) | Asynchronous (ops/sec) | Memory Usage |
---|---|---|---|
Log4j2 | 2,000,000 | 18,000,000 | Low |
Logback | 1,400,000 | 2,000,000 | Medium |
Log4j 1.x | 800,000 | N/A | High |
Enable Async Logging for Maximum Performance
Add this to your JVM startup parameters:
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
-DAsyncLogger.RingBufferSize=262144
Or add the disruptor dependency for async logging:
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
Use Lambda Expressions for Expensive Log Messages
// Instead of this (always evaluates the expensive operation)
logger.debug("User data: " + expensiveUserDataCalculation());
// Do this (only evaluates when DEBUG is enabled)
logger.debug("User data: {}", () -> expensiveUserDataCalculation());
Configure Garbage Collection-Friendly Settings
<Configuration status="WARN">
<!-- Reduce GC pressure -->
<Properties>
<Property name="log4j2.enable.threadlocals">true</Property>
<Property name="log4j2.enable.direct.encoders">true</Property>
</Properties>
</Configuration>
Advanced Features and Integrations
Custom Appenders for Special Use Cases
Log4j2’s plugin architecture makes it easy to create custom appenders. Here’s a simple database appender example:
@Plugin(name = "DatabaseAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class DatabaseAppender extends AbstractAppender {
private final String connectionString;
protected DatabaseAppender(String name, Filter filter, Layout layout, String connectionString) {
super(name, filter, layout, true, Property.EMPTY_ARRAY);
this.connectionString = connectionString;
}
@Override
public void append(LogEvent event) {
// Your database insertion logic here
insertLogEvent(event);
}
@PluginFactory
public static DatabaseAppender createAppender(
@PluginAttribute("name") String name,
@PluginAttribute("connectionString") String connectionString,
@PluginElement("Layout") Layout layout,
@PluginElement("Filter") Filter filter) {
if (name == null) {
LOGGER.error("No name provided for DatabaseAppender");
return null;
}
return new DatabaseAppender(name, filter, layout, connectionString);
}
}
Integration with Monitoring Tools
Log4j2 plays well with popular monitoring solutions. Here’s how to integrate with ELK stack:
<!-- JSON layout for Elasticsearch -->
<RollingFile name="JsonLog"
fileName="/var/log/myapp/application.json"
filePattern="/var/log/myapp/application-%d{yyyy-MM-dd}-%i.json.gz">
<JSONLayout complete="false" compact="true" eventEol="true">
<KeyValuePair key="service" value="myapp"/>
<KeyValuePair key="environment" value="production"/>
</JSONLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
</RollingFile>
Docker Integration
For containerized applications, you’ll want to log to stdout/stderr and let Docker handle log routing:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<JSONLayout complete="false" compact="true" eventEol="true"/>
</Console>
</Appenders>
<Loggers>
<Root level="${env:LOG_LEVEL:-INFO}">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Then configure your Docker logging driver:
docker run -d \
--log-driver=json-file \
--log-opt max-size=100m \
--log-opt max-file=3 \
-e LOG_LEVEL=DEBUG \
myapp:latest
Troubleshooting Common Issues
Configuration Not Loading
Enable Log4j2’s internal debugging to see what’s happening:
-Dlog4j2.debug=true
Check configuration file location:
find /path/to/your/classpath -name "log4j2.*" -type f
Performance Issues
Monitor your logging performance with JVM flags:
-Dlog4j2.asyncLoggerRingBufferSize=262144
-Dlog4j2.asyncLoggerWaitStrategy=Block
-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC
Memory Leaks
Always shut down Log4j2 properly in your applications:
// Add this to your shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
LogManager.shutdown();
}));
Monitoring and Maintenance Scripts
Here are some useful scripts for managing your Log4j2 setup:
Log Rotation Monitoring
#!/bin/bash
# check-log-sizes.sh
LOG_DIR="/var/log/myapp"
MAX_SIZE="500M"
for logfile in $(find $LOG_DIR -name "*.log" -type f); do
size=$(du -h "$logfile" | cut -f1)
echo "Checking $logfile: $size"
if [[ $(du -b "$logfile" | cut -f1) -gt $(numfmt --from=iec $MAX_SIZE) ]]; then
echo "WARNING: $logfile exceeds $MAX_SIZE"
# Trigger manual rotation if needed
# kill -USR1 $(cat /var/run/myapp.pid)
fi
done
Log Analysis Script
#!/bin/bash
# analyze-logs.sh
LOG_FILE="/var/log/myapp/application.log"
echo "=== Error Summary for $(date) ==="
echo "Error count:"
grep "ERROR" "$LOG_FILE" | wc -l
echo -e "\nWarning count:"
grep "WARN" "$LOG_FILE" | wc -l
echo -e "\nTop 10 error messages:"
grep "ERROR" "$LOG_FILE" | sed 's/.*ERROR.*- //' | sort | uniq -c | sort -nr | head -10
echo -e "\nRecent errors:"
grep "ERROR" "$LOG_FILE" | tail -5
Scaling Considerations
When your application grows, you’ll need to consider more advanced logging strategies. Here’s what works well for different scales:
Scale | Strategy | Recommended Setup | Server Requirements |
---|---|---|---|
Single Application | Local file logging | RollingFileAppender | Basic VPS |
Multiple Applications | Centralized logging | Syslog + ELK Stack | Dedicated Server |
Microservices | Distributed tracing | JSON + Correlation IDs | Multiple VPS instances |
Enterprise | Multi-tier logging | Custom appenders + Analytics | Dedicated infrastructure |
For high-volume applications, consider implementing log sampling to reduce overhead while maintaining visibility:
<Logger name="com.mycompany.myapp.highvolume" level="INFO" additivity="false">
<!-- Sample 1 in every 100 DEBUG messages -->
<BurstFilter level="DEBUG" rate="0.01" maxBurst="10"/>
<AppenderRef ref="RollingFile"/>
</Logger>
Conclusion and Recommendations
Log4j2 is hands-down the most powerful and flexible logging framework available for Java applications. Its asynchronous capabilities, plugin architecture, and automatic configuration reloading make it perfect for modern server environments where performance and reliability are crucial.
Use Log4j2 when:
- You need high-performance logging (>1M+ messages/second)
- You’re running production servers that require reliable log rotation
- You want advanced features like custom log levels and filters
- You need integration with monitoring tools and centralized logging
- You’re building microservices that require structured logging
Start with this setup:
- Use RollingFileAppender for production with size and time-based rotation
- Enable async logging for better performance
- Set up separate error logs for easier monitoring
- Use JSON layout if you plan to integrate with log analysis tools
- Always configure automatic cleanup of old log files
For your server infrastructure: If you’re just getting started, a reliable VPS will handle most Log4j2 setups perfectly. As you scale up and need to handle multiple applications or high-volume logging, consider upgrading to a dedicated server for better I/O performance and storage capacity.
The key to successful logging is finding the right balance between verbosity and performance. Start conservative in production, and remember that you can always adjust log levels dynamically thanks to Log4j2’s configuration monitoring. Your future self (and your on-call teammates) will thank you for setting up proper logging from the beginning.
For more advanced configurations and the latest features, check out the official Apache Log4j2 documentation at https://logging.apache.org/log4j/2.x/.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.