Apache Log4j2
High-performance logging framework by Apache Foundation, successor to Log4j. Supports garbage-free logging, asynchronous loggers, and lazy evaluation using lambda expressions. Optimized for low-latency systems through lock-free data structures.
GitHub Overview
apache/logging-log4j2
Apache Log4j is a versatile, feature-rich, efficient logging API and backend for Java.
Topics
Star History
Library
Apache Log4j2
Overview
Apache Log4j2 is a "high-performance logging framework by Apache Foundation, successor to Log4j," developed as the fastest and most advanced logging solution in the Java ecosystem. It supports garbage-free logging, asynchronous loggers, and lazy evaluation using lambda expressions, optimized for low-latency systems through lock-free data structures. With plugin architecture extensibility and API/implementation separation design, it has established its position in enterprise applications.
Details
Apache Log4j2 2025 edition is evaluated as the fastest and most advanced Java logging framework, with rapidly increasing adoption in high-throughput applications. It has established its position in enterprise systems through garbage collection pressure reduction features and multi-threading capabilities. Provides SLF4J compatibility, automatic configuration reload, and advanced filtering features, achieving significant evolution from Log4j 1.x. With rich appenders (file, database, Kafka, NoSQL, etc.) and layouts (JSON, XML, CSV, etc.), it can handle diverse output requirements.
Key Features
- Garbage-Free Logging: Ultra-low latency through memory allocation minimization
- Asynchronous Loggers: High throughput processing through lock-free structures
- Lambda Expression Support: Lazy evaluation for performance improvement
- Plugin Architecture: Flexible addition of custom components
- Automatic Configuration Reload: Non-stop application of runtime configuration changes
- Advanced Filtering: Scripts, regular expressions, rate limiting, etc.
Pros and Cons
Pros
- Fastest logging performance in Java with ultra-low latency through garbage-free operation
- High throughput achievement through asynchronous processing and multi-threading optimization
- Support for diverse output destinations through rich appenders and layouts
- High extensibility and customization through plugin architecture
- Flexible configuration changes during operation through automatic configuration reload
- Long-term support guarantee through continuous maintenance by Apache Foundation
Cons
- Increased configuration complexity and learning cost due to rich functionality
- Excessive features and dependencies for small-scale applications
- Specialized knowledge required for effective utilization of garbage-free features
- High migration cost from other logging frameworks
- Deep framework understanding required for plugin development
- Risk of performance degradation due to configuration mistakes
Reference Pages
Usage Examples
Installation and Basic Setup
<!-- Maven dependencies -->
<dependencies>
<!-- Log4j2 API -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.23.1</version>
</dependency>
<!-- Log4j2 implementation -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<!-- SLF4J integration (optional) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.23.1</version>
</dependency>
<!-- For asynchronous loggers -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
</dependencies>
// Gradle dependencies
dependencies {
implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
implementation 'org.apache.logging.log4j:log4j-core:2.23.1'
implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.23.1'
implementation 'com.lmax:disruptor:3.4.4'
}
Basic Log Output
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class BasicLoggingExample {
// Get logger (auto-named by class name)
private static final Logger logger = LogManager.getLogger(BasicLoggingExample.class);
public static void main(String[] args) {
// Basic log level output
logger.trace("Most detailed level: method entry/exit trace");
logger.debug("Debug info: variable value={}, state={}", "test", "active");
logger.info("General info: application start");
logger.warn("Warning: memory usage exceeds 80% - {}MB", 1200);
logger.error("Error: database connection failed", new RuntimeException("Connection timeout"));
logger.fatal("Fatal error: system shutdown");
// Parameterized messages (high efficiency)
String userId = "user123";
int transactionId = 98765;
logger.info("Transaction processing complete: User={}, TransactionID={}, Amount=${}",
userId, transactionId, 150.00);
// Lazy evaluation with lambda expressions (not executed if log level is disabled)
logger.debug("Heavy processing result: {}", () -> expensiveOperation());
// Classification using markers
org.apache.logging.log4j.Marker marker =
org.apache.logging.log4j.MarkerManager.getMarker("AUDIT");
logger.info(marker, "Audit log: User {} performed action {}", userId, "LOGIN");
}
private static String expensiveOperation() {
// Heavy processing simulation
try {
Thread.sleep(100);
return "Calculation result: " + System.currentTimeMillis();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Interrupted";
}
}
}
Advanced Configuration with XML Configuration File (log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="60">
<Properties>
<!-- Common settings -->
<Property name="logPattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
<Property name="logDir">logs</Property>
</Properties>
<Appenders>
<!-- Console appender -->
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="${logPattern}" />
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<!-- Rolling file appender -->
<RollingFile name="FileAppender"
fileName="${logDir}/application.log"
filePattern="${logDir}/application-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${logPattern}" />
<Policies>
<!-- Rotate daily or at 10MB -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- Error-specific file -->
<RollingFile name="ErrorFileAppender"
fileName="${logDir}/error.log"
filePattern="${logDir}/error-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${logPattern}" />
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
<!-- JSON format logs -->
<RollingFile name="JsonFileAppender"
fileName="${logDir}/application.json"
filePattern="${logDir}/application-%d{yyyy-MM-dd}-%i.json.gz">
<JsonTemplateLayout eventTemplateUri="classpath:LogstashJsonEventLayoutV1.json"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- Asynchronous appender (high performance) -->
<AsyncAppender name="AsyncFileAppender" bufferSize="2048">
<AppenderRef ref="FileAppender"/>
<AppenderRef ref="JsonFileAppender"/>
</AsyncAppender>
<!-- SMTP appender (critical error notifications) -->
<SMTP name="MailAppender"
subject="[ERROR] Application Error"
to="[email protected]"
from="[email protected]"
smtpHost="smtp.example.com"
smtpPort="587"
smtpUsername="[email protected]"
smtpPassword="${env:SMTP_PASSWORD}"
bufferSize="10">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<HtmlLayout title="Error Report"/>
</SMTP>
</Appenders>
<Loggers>
<!-- Detailed logs for specific packages -->
<Logger name="com.example.service" level="DEBUG" additivity="false">
<AppenderRef ref="FileAppender"/>
</Logger>
<!-- Database-related logs -->
<Logger name="com.example.dao" level="TRACE" additivity="false">
<AppenderRef ref="FileAppender"/>
</Logger>
<!-- Framework logs (restricted) -->
<Logger name="org.springframework" level="WARN" additivity="false">
<AppenderRef ref="ConsoleAppender"/>
</Logger>
<!-- Asynchronous root logger -->
<AsyncRoot level="INFO" includeLocation="false">
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="AsyncFileAppender"/>
<AppenderRef ref="ErrorFileAppender"/>
<AppenderRef ref="MailAppender"/>
</AsyncRoot>
</Loggers>
</Configuration>
Programmatic Configuration and Custom Appenders
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.builder.api.*;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
public class ProgrammaticConfigurationExample {
public static void main(String[] args) {
// Create programmatic configuration
Configuration configuration = createConfiguration();
// Apply configuration
LoggerContext context = (LoggerContext) LogManager.getContext(false);
context.setConfiguration(configuration);
context.updateLoggers();
// Use logger
Logger logger = LogManager.getLogger(ProgrammaticConfigurationExample.class);
logger.info("Log output with programmatic configuration");
logger.debug("Debug info: configuration applied successfully");
logger.error("Error test: intentional error message");
// Test custom appender
testCustomAppender(logger);
}
private static Configuration createConfiguration() {
ConfigurationBuilder<BuiltConfiguration> builder =
ConfigurationBuilderFactory.newConfigurationBuilder();
// Property settings
builder.setStatusLevel(Level.DEBUG)
.setConfigurationName("ProgrammaticConfig");
// Pattern layout settings
LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout")
.addAttribute("pattern", "%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n");
// Console appender
AppenderComponentBuilder consoleAppender = builder.newAppender("Console", "CONSOLE")
.addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT)
.add(layoutBuilder);
builder.add(consoleAppender);
// File appender
AppenderComponentBuilder fileAppender = builder.newAppender("File", "File")
.addAttribute("fileName", "logs/programmatic.log")
.addAttribute("append", true)
.add(layoutBuilder);
builder.add(fileAppender);
// Asynchronous appender
AppenderComponentBuilder asyncAppender = builder.newAppender("Async", "Async")
.addAttribute("bufferSize", 1024)
.addComponent(builder.newAppenderRef("File"));
builder.add(asyncAppender);
// Root logger configuration
RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.INFO)
.add(builder.newAppenderRef("Console"))
.add(builder.newAppenderRef("Async"));
builder.add(rootLogger);
// Package-specific logger
LoggerComponentBuilder packageLogger = builder.newLogger("com.example", Level.DEBUG)
.add(builder.newAppenderRef("File"))
.addAttribute("additivity", false);
builder.add(packageLogger);
return builder.build();
}
private static void testCustomAppender(Logger logger) {
// Using MDC (Mapped Diagnostic Context)
org.apache.logging.log4j.ThreadContext.put("userId", "user123");
org.apache.logging.log4j.ThreadContext.put("sessionId", "session456");
org.apache.logging.log4j.ThreadContext.put("traceId", "trace789");
try {
logger.info("Log with MDC: user processing start");
// Business process simulation
processUserData();
logger.info("Log with MDC: user processing complete");
} finally {
// Clear MDC
org.apache.logging.log4j.ThreadContext.clearAll();
}
}
private static void processUserData() {
Logger logger = LogManager.getLogger("com.example.service.UserService");
logger.debug("User data retrieval start");
try {
// Process simulation
Thread.sleep(100);
logger.info("User data retrieval success: record count={}", 150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("User data retrieval interrupted", e);
}
}
}
Asynchronous Logging and Performance Optimization
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ObjectMessage;
import org.apache.logging.log4j.message.StringFormattedMessage;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class HighPerformanceLoggingExample {
private static final Logger logger = LogManager.getLogger(HighPerformanceLoggingExample.class);
public static void main(String[] args) throws InterruptedException {
// Performance test
performanceTest();
// Structured logging test
structuredLoggingTest();
// Multi-thread test
multiThreadTest();
// Garbage-free logging test
garbageFreeLoggingTest();
}
private static void performanceTest() {
logger.info("Performance test start");
long startTime = System.nanoTime();
int logCount = 100000;
for (int i = 0; i < logCount; i++) {
// High-speed log output (parameterized messages)
logger.info("Performance test: number={}, timestamp={}",
i, System.currentTimeMillis());
if (i % 10000 == 0) {
long currentTime = System.nanoTime();
double elapsedMs = (currentTime - startTime) / 1_000_000.0;
logger.debug("Progress: {}/{}, elapsed time={}ms, throughput={} logs/sec",
i, logCount, elapsedMs, (i * 1000.0 / elapsedMs));
}
}
long endTime = System.nanoTime();
double totalMs = (endTime - startTime) / 1_000_000.0;
double throughput = logCount * 1000.0 / totalMs;
logger.info("Performance test complete: total time={}ms, throughput={} logs/sec",
totalMs, throughput);
}
private static void structuredLoggingTest() {
logger.info("Structured logging test start");
// Structured logging with Map object
Map<String, Object> logData = new HashMap<>();
logData.put("userId", "user123");
logData.put("action", "purchase");
logData.put("productId", "prod456");
logData.put("amount", 15000);
logData.put("timestamp", System.currentTimeMillis());
logger.info(new ObjectMessage(logData));
// Structured logging with custom object
UserTransaction transaction = new UserTransaction("user789", "sale", 25000);
logger.info("Transaction log: {}", transaction);
// JSON-style structured logging
logger.info("API call: {\"endpoint\":\"/api/users\", \"method\":\"GET\", \"responseTime\":{}}", 150);
}
private static void multiThreadTest() throws InterruptedException {
logger.info("Multi-thread test start");
ExecutorService executor = Executors.newFixedThreadPool(10);
int tasksPerThread = 1000;
for (int threadId = 0; threadId < 10; threadId++) {
final int finalThreadId = threadId;
executor.submit(() -> {
for (int i = 0; i < tasksPerThread; i++) {
logger.info("Thread{}: task number={}, execution time={}",
finalThreadId, i, System.currentTimeMillis());
if (i % 100 == 0) {
logger.debug("Thread{}: progress={}%", finalThreadId,
(i * 100 / tasksPerThread));
}
}
logger.info("Thread{}: all tasks complete", finalThreadId);
});
}
executor.shutdown();
executor.awaitTermination(30, TimeUnit.SECONDS);
logger.info("Multi-thread test complete");
}
private static void garbageFreeLoggingTest() {
logger.info("Garbage-free logging test start");
// Primitive type logging (minimal allocation)
long startMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
for (int i = 0; i < 10000; i++) {
// Garbage-free compatible writing
logger.info("GC-free test: iteration count={}", i);
}
// Execute GC
System.gc();
Thread.yield();
long endMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
logger.info("Memory usage: start={}KB, end={}KB, difference={}KB",
startMemory/1024, endMemory/1024, (endMemory-startMemory)/1024);
logger.info("Garbage-free logging test complete");
}
// User transaction data class
static class UserTransaction {
private final String userId;
private final String action;
private final int amount;
private final long timestamp;
public UserTransaction(String userId, String action, int amount) {
this.userId = userId;
this.action = action;
this.amount = amount;
this.timestamp = System.currentTimeMillis();
}
@Override
public String toString() {
return String.format("{\"userId\":\"%s\", \"action\":\"%s\", \"amount\":%d, \"timestamp\":%d}",
userId, action, amount, timestamp);
}
}
}
Spring Boot Integration and Production Configuration
// Spring Boot configuration class
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.UUID;
@SpringBootApplication
public class Log4j2SpringBootApplication {
private static final Logger logger = LogManager.getLogger(Log4j2SpringBootApplication.class);
public static void main(String[] args) {
logger.info("Spring Boot application start");
SpringApplication.run(Log4j2SpringBootApplication.class, args);
logger.info("Spring Boot application start complete");
}
}
@RestController
@RequestMapping("/api")
class ApiController {
private static final Logger logger = LogManager.getLogger(ApiController.class);
@GetMapping("/users/{userId}")
public ResponseEntity<UserResponse> getUser(@PathVariable String userId,
HttpServletRequest request) {
// Generate and set request ID
String requestId = UUID.randomUUID().toString();
org.apache.logging.log4j.ThreadContext.put("requestId", requestId);
org.apache.logging.log4j.ThreadContext.put("userId", userId);
try {
logger.info("User retrieval request: URL={}, IP={}, UserAgent={}",
request.getRequestURL(),
request.getRemoteAddr(),
request.getHeader("User-Agent"));
// Execute business logic
UserResponse user = userService.findUser(userId);
logger.info("User retrieval success: username={}", user.getName());
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
logger.warn("User not found: userId={}", userId, e);
return ResponseEntity.notFound().build();
} catch (Exception e) {
logger.error("User retrieval error: userId={}", userId, e);
return ResponseEntity.status(500).build();
} finally {
// Clear ThreadContext
org.apache.logging.log4j.ThreadContext.clearAll();
}
}
@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(@RequestBody CreateUserRequest request,
HttpServletRequest httpRequest) {
String requestId = UUID.randomUUID().toString();
org.apache.logging.log4j.ThreadContext.put("requestId", requestId);
try {
logger.info("User creation request: name={}, email={}",
request.getName(), request.getEmail());
// Validation
if (request.getName() == null || request.getName().trim().isEmpty()) {
logger.warn("User creation failed: name is empty");
return ResponseEntity.badRequest().build();
}
// Create user
UserResponse user = userService.createUser(request);
logger.info("User creation success: ID={}, name={}", user.getId(), user.getName());
return ResponseEntity.status(201).body(user);
} catch (EmailDuplicateException e) {
logger.warn("Email address duplicate: email={}", request.getEmail(), e);
return ResponseEntity.status(409).build();
} catch (Exception e) {
logger.error("User creation error", e);
return ResponseEntity.status(500).build();
} finally {
org.apache.logging.log4j.ThreadContext.clearAll();
}
}
}
// Log configuration profile (application-production.yml)
/*
logging:
config: classpath:log4j2-production.xml
level:
com.example: INFO
org.springframework: WARN
org.hibernate: WARN
# System property settings
log4j2:
asyncLoggerWaitStrategy: Block
asyncLoggerRingBufferSize: 2048
garbageFreeThresholdBytes: 1024
enableThreadLocals: true
*/
<!-- log4j2-production.xml (production environment configuration) -->
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="300">
<Properties>
<Property name="appName">MySpringBootApp</Property>
<Property name="logPattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%X{requestId}] %logger{36} - %msg%n</Property>
<Property name="logDir">/var/log/${appName}</Property>
</Properties>
<Appenders>
<!-- Application logs -->
<RollingFile name="AppFileAppender"
fileName="${logDir}/application.log"
filePattern="${logDir}/application-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${logPattern}" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="90"/>
</RollingFile>
<!-- Error logs -->
<RollingFile name="ErrorFileAppender"
fileName="${logDir}/error.log"
filePattern="${logDir}/error-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${logPattern}" />
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="50MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- Access logs -->
<RollingFile name="AccessFileAppender"
fileName="${logDir}/access.log"
filePattern="${logDir}/access-%d{yyyy-MM-dd}-%i.log.gz">
<JsonTemplateLayout eventTemplateUri="classpath:AccessLogLayout.json"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="90"/>
</RollingFile>
<!-- Asynchronous wrapper -->
<AsyncAppender name="AsyncAppender" bufferSize="4096"
shutdownTimeout="3000" includeLocation="false">
<AppenderRef ref="AppFileAppender"/>
</AsyncAppender>
</Appenders>
<Loggers>
<!-- Application logger -->
<Logger name="com.example" level="INFO" additivity="false">
<AppenderRef ref="AsyncAppender"/>
<AppenderRef ref="ErrorFileAppender"/>
</Logger>
<!-- Access log logger -->
<Logger name="ACCESS_LOG" level="INFO" additivity="false">
<AppenderRef ref="AccessFileAppender"/>
</Logger>
<!-- Framework log restriction -->
<Logger name="org.springframework" level="WARN" additivity="false">
<AppenderRef ref="AsyncAppender"/>
</Logger>
<!-- Asynchronous root logger -->
<AsyncRoot level="INFO" includeLocation="false">
<AppenderRef ref="AsyncAppender"/>
<AppenderRef ref="ErrorFileAppender"/>
</AsyncRoot>
</Loggers>
</Configuration>