Apache Log4j (Legacy)
Historic Java logging framework by Apache Foundation. Used in many Java applications for years, but migration to Log4j2 is strongly recommended due to discovery of security vulnerabilities (Log4Shell).
GitHub Overview
Topics
Star History
Library
Apache Log4j
Overview
Apache Log4j is the most established and proven logging library in the Java ecosystem. Designed as a "versatile, feature-rich, efficient logging API for Java," it's used across a wide range of applications from standalone applications to enterprise systems. It provides rich output destinations including files, networks, databases, SMTP, and flexible formatting capabilities, making it the de facto standard for Java logging.
Details
Apache Log4j 2.25.0, released in June 2025, is the latest version featuring complete GraalVM native image support and performance improvements. With its modular design separating API, implementation, and deployment support components, it achieves high extensibility and customization. Supporting Java 8 and above, it's widely used in modern Java application development with robust enterprise-grade features.
Key Features
- Rich Appenders: Multiple output destinations including files, networks, databases, SMTP, JMS, and more
- Flexible Layouts: Support for CSV, HTML, JSON, Syslog, and other formatting options
- High-Performance Filters: Advanced filtering by rate, regex, scripts, time, and more
- Dynamic Configuration Reload: Automatic reload upon configuration file changes (without log event loss)
- GraalVM Support: Complete support for native image generation
- Garbage Collector Free: High-performance design that doesn't burden garbage collection
Pros and Cons
Pros
- Most established logger in Java ecosystem (20+ years of development)
- Stable governance and support by Apache Software Foundation
- Rich ecosystem (integration with Spring Boot, Hibernate, etc.)
- Enterprise-grade features (asynchronous logging, clustering, etc.)
- High performance and scalable (garbage collector-free design)
- Comprehensive documentation and active community
Cons
- Complex initial configuration with high learning curve
- XML configuration files can be detailed and difficult to manage
- Modern Java annotation-based configuration lags behind other libraries
- Error messages during debugging can sometimes be unclear
- Relatively high memory usage due to extensive features
- May be overkill for lightweight logging needs
References
Code Examples
Basic Setup
<!-- Maven dependencies -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.25.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.25.0</version>
</dependency>
<!-- SLF4J bridge (when using SLF4J in existing apps) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.25.0</version>
</dependency>
// Gradle dependencies
implementation 'org.apache.logging.log4j:log4j-core:2.25.0'
implementation 'org.apache.logging.log4j:log4j-api:2.25.0'
Simple Logger Usage
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MyApp {
// Get logger instance
private static final Logger logger = LogManager.getLogger(MyApp.class);
public static void main(String[] args) {
// Basic log output
logger.trace("Trace message");
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");
// Parameterized log output
String userId = "user123";
int age = 25;
logger.info("User info: ID={}, Age={}", userId, age);
// Exception logging
try {
int result = 10 / 0;
} catch (Exception e) {
logger.error("Calculation error occurred", e);
}
}
}
Basic XML Configuration (log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<!-- Console output -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<!-- File output -->
<File name="FileAppender" fileName="logs/app.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
<!-- Rolling file -->
<RollingFile name="RollingFileAppender" fileName="logs/app.log"
filePattern="logs/app.%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 />
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- Specific package log level settings -->
<Logger name="com.example.database" level="DEBUG" additivity="false">
<AppenderRef ref="FileAppender"/>
</Logger>
<!-- Root logger -->
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileAppender"/>
</Root>
</Loggers>
</Configuration>
Asynchronous Logging Configuration
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<!-- Async logger configuration -->
<AsyncLogger name="com.example.performance" level="INFO" additivity="false">
<AppenderRef ref="FileAppender"/>
</AsyncLogger>
<!-- Full async mode -->
<AsyncRoot level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="FileAppender"/>
</AsyncRoot>
<!-- LMAX Disruptor dependency required -->
<!--
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
-->
</Configuration>
Programmatic Configuration
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.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 ProgrammaticConfig {
public static void main(String[] args) {
// Programmatic configuration using Configuration Builder
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
// Console Appender configuration
AppenderComponentBuilder consoleAppender = builder.newAppender("Console", "CONSOLE")
.addAttribute("target", "SYSTEM_OUT")
.add(builder.newLayout("PatternLayout")
.addAttribute("pattern", "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"));
// File Appender configuration
AppenderComponentBuilder fileAppender = builder.newAppender("File", "File")
.addAttribute("fileName", "logs/programmatic.log")
.add(builder.newLayout("PatternLayout")
.addAttribute("pattern", "%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"));
builder.add(consoleAppender);
builder.add(fileAppender);
// Root Logger configuration
RootLoggerComponentBuilder rootLogger = builder.newRootLogger("INFO")
.add(builder.newAppenderRef("Console"))
.add(builder.newAppenderRef("File"));
builder.add(rootLogger);
// Apply configuration
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = builder.build();
context.start(config);
// Use logger
Logger logger = LogManager.getLogger(ProgrammaticConfig.class);
logger.info("Log output with programmatic configuration");
}
}
Filters and Markers Usage
<?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"/>
<!-- Level filter -->
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<File name="ErrorFile" fileName="logs/error.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<!-- Error level only -->
<LevelRangeFilter minLevel="ERROR" maxLevel="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</File>
<File name="SecurityFile" fileName="logs/security.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<!-- Marker filter -->
<MarkerFilter marker="SECURITY" onMatch="ACCEPT" onMismatch="DENY"/>
</File>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console"/>
<AppenderRef ref="ErrorFile"/>
<AppenderRef ref="SecurityFile"/>
</Root>
</Loggers>
</Configuration>
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
public class FilterExample {
private static final Logger logger = LogManager.getLogger(FilterExample.class);
private static final Marker SECURITY_MARKER = MarkerManager.getMarker("SECURITY");
private static final Marker PERFORMANCE_MARKER = MarkerManager.getMarker("PERFORMANCE");
public static void main(String[] args) {
// Regular log
logger.info("Application started");
// Security marker log (output to security.log)
logger.warn(SECURITY_MARKER, "Unauthorized access attempt detected: IP={}", "192.168.1.100");
// Performance marker log
logger.debug(PERFORMANCE_MARKER, "DB query execution time: {}ms", 150);
// Error log (output to error.log)
logger.error("Database connection error occurred");
}
}
Spring Boot Integration
<!-- Replace default logback with Log4j2 in Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
# application.yml
logging:
config: classpath:log4j2-spring.xml
level:
com.example: DEBUG
org.springframework: INFO
root: WARN
// Usage in Spring Boot application
@RestController
@Slf4j // When using Lombok
public class UserController {
private static final Logger logger = LogManager.getLogger(UserController.class);
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
logger.info("User info request: userId={}", id);
try {
User user = userService.findById(id);
logger.debug("User info retrieved successfully: {}", user);
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
logger.warn("User not found: userId={}", id);
return ResponseEntity.notFound().build();
} catch (Exception e) {
logger.error("Error occurred while retrieving user info: userId={}", id, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}