How Spring Boot Integrates Log4j2: Deep Dive into Logging Mechanics and Hot Updates

This article provides a comprehensive analysis of how Spring Boot 2.7.2 works with Log4j2 2.17.2, covering Log4j2's core components, Spring Boot's logging configuration options, the internal startup sequence, the LoggersEndpoint for runtime level changes, and custom hot‑update techniques, all illustrated with code snippets and diagrams.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
How Spring Boot Integrates Log4j2: Deep Dive into Logging Mechanics and Hot Updates

Preface

Logging is a familiar yet often overlooked component in development; most developers simply call a logger without understanding how the log is produced, especially when using Spring Boot's default integration with Log4j2. Understanding the inner workings of the logging framework and how Spring Boot integrates Log4j2 or Logback enables advanced extensions, graceful log output, and even performance improvements by adjusting logging strategies.

Spring Boot version: 2.7.2

Log4j2 version: 2.17.2

1. Simple Working Principle of Log4j2

The most frequently used object is the Logger, which acts as a log printer. Internally, a Logger is just a shell; the real logic resides in a LoggerConfig that determines which Appender s are used and the logger's level.

Log4j2 reads its configuration from a file typically named log4j2.xml. During initialization, the file is parsed into a Configuration object, which contains <Appenders> and <Loggers> sections. Adding an entry under <Appenders> creates a new appender in the configuration, while adding an entry under <Loggers> creates a new LoggerConfig linked to the specified appenders.

Each Logger is obtained from a LoggerContext that holds the Configuration. When a logger is requested, the context looks up the corresponding LoggerConfig and binds it to the logger, allowing dynamic level changes via the LoggerConfig.

2. Simple Spring Boot Logging Configuration

Spring Boot allows configuration through application.yml (or application.properties) without requiring a custom log4j2.xml. The most common properties are:

2.1 logging.file.name

logging:
  file:
    name: test.log

This writes log output to test.log in the project root.

2.2 logging.file.path

logging:
  file:
    path: /

This writes log output to spring.log in the specified directory.

2.3 logging.level

logging:
  level:
    com.pww.App: warn

This sets the logger named com.pww.App to warn level.

3. Spring Boot Logging Startup Mechanism

The startup process is driven by LoggingApplicationListener, which listens to three key events:

ApplicationStartingEvent : Occurs after SpringApplication starts but before the environment is available.

ApplicationEnvironmentPreparedEvent : Fires when the environment is ready.

ApplicationPreparedEvent : Fires after the ApplicationContext is fully prepared but before the container refreshes.

When ApplicationStartingEvent is received, the listener loads the LoggingSystem class name from the system property org.springframework.boot.logging.LoggingSystem and calls beforeInitialize() to add a filter that blocks all log output until initialization completes.

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
    this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    this.loggingSystem.beforeInitialize();
}

For ApplicationEnvironmentPreparedEvent, the listener sets logging‑related system properties, determines the log file location, initializes early logging levels, and finally delegates to the concrete LoggingSystem (e.g., Log4J2LoggingSystem) to initialize the logging framework.

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    SpringApplication springApplication = event.getSpringApplication();
    if (this.loggingSystem == null) {
        this.loggingSystem = LoggingSystem.get(springApplication.getClassLoader());
    }
    initialize(event.getEnvironment(), springApplication.getClassLoader());
}

The initialize() method performs three main tasks:

Transfer logging‑related configuration values to system properties (e.g., logging.pattern.console becomes CONSOLE_LOG_PATTERN).

Invoke the logging system's initialize() to load the actual Log4j2 (or Logback) configuration.

Apply the configured logging levels to logger groups and individual loggers.

When ApplicationPreparedEvent is received, the listener registers the LoggingSystem, LogFile, and LoggerGroups as beans in the Spring container, completing the initialization.

private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
    ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
    if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
        beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
    }
    if (this.logFile != null && !beanFactory.containsBean(LOG_FILE_BEAN_NAME)) {
        beanFactory.registerSingleton(LOG_FILE_BEAN_NAME, this.logFile);
    }
    if (this.loggerGroups != null && !beanFactory.containsBean(LOGGER_GROUPS_BEAN_NAME)) {
        beanFactory.registerSingleton(LOGGER_GROUPS_BEAN_NAME, this.loggerGroups);
    }
}

4. Spring Boot Integration with Log4j2

If no log4j2.xml is provided, Spring Boot loads a default configuration based on convention. It searches the classpath for standard Log4j2 file names (e.g., log4j2.xml, log4j2-spring.xml) and, if none are found, falls back to the internal log4j2.xml or log4j2-file.xml located next to the LoggingSystem implementation.

The search order is: log4j2-test.properties, log4j2-test.json, log4j2-test.xml, etc.

If not found, the Spring‑specific variants such as log4j2-spring.xml.

If still not found, the built‑in default configuration files.

When a logging.config property is set, Spring Boot directly loads the specified file and skips the convention search.

Loading a configuration involves creating a LoggerContext, parsing the file into a Configuration, and starting the context. If multiple configuration files are specified via logging.log4j2.config.override, a CompositeConfiguration is built from all of them.

protected void loadConfiguration(String location, LogFile logFile, List<String> overrides) {
    Assert.notNull(location, "Location must not be null");
    List<Configuration> configurations = new ArrayList<>();
    LoggerContext context = getLoggerContext();
    configurations.add(load(location, context));
    for (String override : overrides) {
        configurations.add(load(override, context));
    }
    Configuration configuration = configurations.size() > 1 ? createComposite(configurations) : configurations.iterator().next();
    context.start(configuration);
}

5. Hot Updating Logger Levels in Spring Boot

Loggers can be updated at runtime without restarting the application. The LoggersEndpoint (provided by spring-boot-actuator) exposes HTTP endpoints to read and modify logger levels. To enable HTTP access, configure the actuator as follows:

management:
  server:
    address: 127.0.0.1
    port: 10999
  endpoints:
    web:
      base-path: /actuator
      exposure:
        include: loggers
  endpoint:
    loggers:
      enabled: true

GET /actuator/loggers returns all logger names, their configured levels, and effective levels. POST to /actuator/loggers/{name} with JSON {"configuredLevel":"DEBUG"} updates the level instantly.

The endpoint works by delegating to LoggingSystem.setLogLevel(). For Log4j2, the implementation performs the following steps:

Retrieve the LoggerConfig for the given logger name from the current Configuration. If it does not exist, create a LevelSetLoggerConfig (a subclass of LoggerConfig) with the desired level and additive=true so that parent loggers still handle output.

If the level is null, either remove the custom LoggerConfig or set its level to null to revert to inheritance.

Call LoggerContext.updateLoggers() to make all loggers re‑bind to the updated configuration.

@Override
public void setLogLevel(String loggerName, LogLevel logLevel) {
    setLogLevel(loggerName, LEVELS.convertSystemToNative(logLevel));
}

private void setLogLevel(String loggerName, Level level) {
    LoggerConfig logger = getLogger(loggerName);
    if (level == null) {
        clearLogLevel(loggerName, logger);
    } else {
        setLogLevel(loggerName, logger, level);
    }
    getLoggerContext().updateLoggers();
}

If a logger name is not present in the configuration, Spring Boot creates a LevelSetLoggerConfig so that the logger can be controlled independently while still inheriting appenders from its parent logger.

private static class LevelSetLoggerConfig extends LoggerConfig {
    LevelSetLoggerConfig(String name, Level level, boolean additive) {
        super(name, level, additive);
    }
}

6. Custom Hot‑Update API

To avoid pulling in the full actuator dependency, a lightweight REST controller can expose a single endpoint that directly uses LoggingSystem to change logger levels:

@RestController
public class HotModificationLevel {
    private final LoggingSystem loggingSystem;
    public HotModificationLevel(LoggingSystem loggingSystem) {
        this.loggingSystem = loggingSystem;
    }
    @PostMapping("/logger/level")
    public void setLoggerLevel(@RequestBody SetLoggerLevelParam param) {
        loggingSystem.setLogLevel(param.getLoggerName(), param.getLoggerLevel());
    }
    public static class SetLoggerLevelParam {
        private String loggerName;
        private LogLevel loggerLevel;
        // getters and setters omitted for brevity
    }
}

Conclusion

In Log4j2, a Logger is merely a façade; the actual behavior is defined by its associated LoggerConfig. Spring Boot’s LoggingApplicationListener initiates logging by locating configuration files (either by convention or via logging.config) and delegating to the appropriate LoggingSystem. Logger groups ( LoggerGroup) allow batch level management through logging.group. Hot updating logger levels is achieved by manipulating the underlying LoggingSystem, which abstracts away framework‑specific details, making runtime log level changes straightforward and efficient.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

loggingSpring Bootlog4j2Hot UpdateActuatorloggerconfigloggergrouploggingapplicationlistener
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.