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.

loggingJavahigh-performancegarbage-freeasynchronouslambda expressionslow-latency

GitHub Overview

apache/logging-log4j2

Apache Log4j is a versatile, feature-rich, efficient logging API and backend for Java.

Stars3,538
Watchers107
Forks1,691
Created:June 12, 2013
Language:Java
License:Apache License 2.0

Topics

apacheapijavajvmlibrarylog4jlog4j2loggerloggingsyslog

Star History

apache/logging-log4j2 Star History
Data as of: 10/22/2025, 04:10 AM

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>