BLOG POSTS
    MangoHost Blog / Log4j Levels Explained – Order, Priority, and Custom Filters
Log4j Levels Explained – Order, Priority, and Custom Filters

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.

Leave a reply

Your email address will not be published. Required fields are marked