
Log4j Levels Explained – Order, Priority, and Custom Filters
Log4j logging levels form the foundation of application logging strategy, determining which messages get recorded and which get ignored based on their severity and importance. Understanding the hierarchy, priority system, and how to implement custom filters can dramatically improve your debugging capabilities and application performance. This guide will walk you through the complete Log4j level system, from basic configuration to advanced filtering techniques that experienced developers use in production environments.
How Log4j Levels Work – The Technical Foundation
Log4j uses a hierarchical level system where each level has a numeric priority value. When you set a logging level for a logger, it will process all messages at that level and above, while filtering out messages below the threshold. The standard levels, from lowest to highest priority, are:
Level | Priority Value | Purpose | Performance Impact |
---|---|---|---|
TRACE | 600 | Fine-grained debug information | Highest overhead |
DEBUG | 500 | General debugging information | High overhead |
INFO | 400 | General application flow | Medium overhead |
WARN | 300 | Potentially harmful situations | Low overhead |
ERROR | 200 | Error events that allow application to continue | Minimal overhead |
FATAL | 100 | Severe errors that cause application termination | Minimal overhead |
The level inheritance works through the logger hierarchy. If you have a logger named “com.example.service” and set the root logger to WARN, this specific logger will inherit that level unless explicitly configured otherwise.
Step-by-Step Configuration Guide
Let’s start with basic Log4j2 configuration using XML. Create a log4j2.xml file in your classpath:
<?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"/>
</Console>
<File name="FileAppender" fileName="logs/application.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Logger name="com.example.database" level="DEBUG" additivity="false">
<AppenderRef ref="FileAppender"/>
</Logger>
<Logger name="com.example.security" level="INFO" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="FileAppender"/>
</Logger>
<Root level="WARN">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
For programmatic configuration, you can set levels dynamically:
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.LoggerConfig;
import org.apache.logging.log4j.Level;
public class LogLevelManager {
public static void setLogLevel(String loggerName, Level level) {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = context.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig(loggerName);
loggerConfig.setLevel(level);
context.updateLoggers();
}
public static void main(String[] args) {
Logger logger = LogManager.getLogger("com.example.service");
// Set to DEBUG level dynamically
setLogLevel("com.example.service", Level.DEBUG);
// Test different levels
logger.trace("This is a trace message");
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");
}
}
Implementing Custom Filters
Custom filters give you granular control over log message processing beyond simple level-based filtering. Here’s how to create and implement various filter types:
Threshold Filter Example
<Configuration>
<Appenders>
<Console name="Console">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d %-5level %logger - %msg%n"/>
</Console>
<File name="DebugFile" fileName="logs/debug.log">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d %-5level %logger - %msg%n"/>
</File>
</Appenders>
</Configuration>
Custom Filter Implementation
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.filter.AbstractFilter;
@Plugin(name = "CustomFilter", category = "Core", elementType = "filter", printObject = true)
public class CustomFilter extends AbstractFilter {
private final String keyword;
private CustomFilter(String keyword, Filter.Result onMatch, Filter.Result onMismatch) {
super(onMatch, onMismatch);
this.keyword = keyword;
}
@Override
public Filter.Result filter(LogEvent event) {
String message = event.getMessage().getFormattedMessage();
// Filter out messages containing sensitive information
if (message.toLowerCase().contains(keyword.toLowerCase())) {
return onMismatch;
}
return onMatch;
}
@PluginFactory
public static CustomFilter createFilter(
@PluginAttribute("keyword") String keyword,
@PluginAttribute("onMatch") Filter.Result match,
@PluginAttribute("onMismatch") Filter.Result mismatch) {
return new CustomFilter(keyword, match != null ? match : Filter.Result.NEUTRAL,
mismatch != null ? mismatch : Filter.Result.DENY);
}
}
Use this custom filter in your configuration:
<Configuration packages="com.example.filters">
<Appenders>
<Console name="Console">
<CustomFilter keyword="password" onMatch="NEUTRAL" onMismatch="DENY"/>
<PatternLayout pattern="%d %-5level %logger - %msg%n"/>
</Console>
</Appenders>
</Configuration>
Real-World Examples and Use Cases
Here are practical scenarios where proper level configuration and custom filters make a significant difference:
Microservices Architecture
In a microservices environment, you’ll want different logging strategies for different services. Here’s a production-ready configuration:
<Configuration>
<Appenders>
<RollingFile name="ServiceLogs" fileName="logs/service.log"
filePattern="logs/service-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} [%X{traceId}] - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
<RollingFile name="ErrorLogs" fileName="logs/error.log"
filePattern="logs/error-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} [%X{traceId}] - %msg%n%throwable"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="50MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.example.auth" level="INFO"/>
<Logger name="com.example.payment" level="WARN"/>
<Logger name="com.example.notification" level="DEBUG"/>
<Logger name="org.springframework" level="WARN"/>
<Logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
<AppenderRef ref="ServiceLogs"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="ServiceLogs"/>
<AppenderRef ref="ErrorLogs"/>
</Root>
</Loggers>
</Configuration>
High-Performance Applications
For applications where logging performance is critical, use asynchronous loggers with appropriate levels:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class PerformanceOptimizedLogging {
private static final Logger logger = LogManager.getLogger();
public void processOrder(Order order) {
// Use level checks for expensive operations
if (logger.isDebugEnabled()) {
logger.debug("Processing order: {}", order.toDetailedString());
}
try {
// Business logic here
processPayment(order);
logger.info("Order {} processed successfully", order.getId());
} catch (PaymentException e) {
logger.error("Payment failed for order {}: {}", order.getId(), e.getMessage());
throw e;
}
}
// Use markers for categorization
private static final org.apache.logging.log4j.Marker PERFORMANCE =
org.apache.logging.log4j.MarkerManager.getMarker("PERFORMANCE");
public void logPerformanceMetric(String operation, long duration) {
logger.info(PERFORMANCE, "Operation {} took {}ms", operation, duration);
}
}
Performance Comparison and Best Practices
Here’s how different logging levels impact application performance based on typical enterprise application testing:
Configuration | Throughput (req/sec) | Memory Usage | Disk I/O | Recommended For |
---|---|---|---|---|
ERROR only | 10,000 | Low | Minimal | Production |
WARN and above | 9,500 | Low | Low | Production |
INFO and above | 8,000 | Medium | Medium | Staging/Production |
DEBUG and above | 4,000 | High | High | Development/Testing |
TRACE (all levels) | 1,500 | Very High | Very High | Deep debugging only |
Best Practices for Production Environments
- Set root logger to WARN or ERROR in production to minimize performance impact
- Use specific logger configurations for critical components that need detailed logging
- Implement log rotation to prevent disk space issues
- Use asynchronous appenders for high-throughput applications
- Always check log level before expensive string operations
- Use structured logging with JSON format for better parsing and analysis
- Implement custom filters to exclude sensitive information like passwords or API keys
- Monitor log file sizes and implement alerting for unusual logging volume
Common Pitfalls and Troubleshooting
Level Inheritance Issues
One frequent mistake is misunderstanding logger inheritance. Consider this scenario:
// This won't work as expected
Logger rootLogger = LogManager.getLogger();
Logger specificLogger = LogManager.getLogger("com.example.service");
// If root is set to ERROR, but you expect DEBUG messages from specificLogger
// You need explicit configuration:
<Logger name="com.example.service" level="DEBUG">
<AppenderRef ref="Console"/>
</Logger>
Performance Problems
Avoid this common performance killer:
// BAD - String concatenation happens regardless of level
logger.debug("User " + user.getName() + " performed " + action.getDescription());
// GOOD - Use parameterized messages
logger.debug("User {} performed {}", user.getName(), action.getDescription());
// BETTER - Check level for expensive operations
if (logger.isDebugEnabled()) {
logger.debug("User {} performed {}", user.getName(), action.getComplexDescription());
}
Filter Configuration Mistakes
Filter order matters. Filters are processed in the order they appear:
<!-- WRONG - ThresholdFilter will never be reached -->
<Console name="Console">
<RegexFilter regex=".*ERROR.*" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<!-- CORRECT - ThresholdFilter first, then RegexFilter -->
<Console name="Console">
<ThresholdFilter level="ERROR" onMatch="NEUTRAL" onMismatch="DENY"/>
<RegexFilter regex=".*ERROR.*" onMatch="DENY" onMismatch="ACCEPT"/>
</Console>
When running applications on managed infrastructure like VPS or dedicated servers, proper log level configuration becomes even more critical for system monitoring and troubleshooting. The examples and practices outlined here will help you implement robust logging strategies that scale with your application’s needs while maintaining optimal performance.
For additional information on Log4j configuration and advanced features, refer to the official Log4j documentation and the comprehensive filter guide.

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.