Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,26 @@
package org.apache.logging.log4j.core;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;

import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.MessageFactory2;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junitpioneer.jupiter.Issue;

class LoggerContextTest {

Expand Down Expand Up @@ -75,4 +82,23 @@ void getLoggers_can_be_updated_concurrently(final TestInfo testInfo) {
executorService.shutdown();
}
}

@Test
@Issue("https://github.com/apache/logging-log4j2/issues/3770")
void start_should_fallback_on_reconfigure_if_context_already_started(final TestInfo testInfo) {
final String testName = testInfo.getDisplayName();
try (final LoggerContext loggerContext = new LoggerContext(testName)) {
loggerContext.start();
assertThat(loggerContext.isStarted()).isTrue();
assertThat(loggerContext.getConfiguration()).isInstanceOf(DefaultConfiguration.class);
// Start
Configuration configuration = mock(
AbstractConfiguration.class,
withSettings()
.useConstructor(null, ConfigurationSource.NULL_SOURCE)
.defaultAnswer(CALLS_REAL_METHODS));
loggerContext.start(configuration);
assertThat(loggerContext.getConfiguration()).isSameAs(configuration);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,84 @@
*/
package org.apache.logging.log4j.core;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.apache.logging.log4j.core.util.ReflectionUtil.getFieldValue;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;

import java.lang.reflect.Field;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
import org.apache.logging.log4j.core.util.ReflectionUtil;
import org.apache.logging.log4j.test.junit.SetTestProperty;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

@SetTestProperty(key = "log4j2.isWebapp", value = "false")
@LoggerContextSource("log4j-test3.xml")
class ShutdownDisabledTest {

private static final Field shutdownCallbackField;

static {
try {
shutdownCallbackField = LoggerContext.class.getDeclaredField("shutdownCallback");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}

@Test
@LoggerContextSource("log4j-test3.xml")
void testShutdownFlag(final Configuration config, final LoggerContext ctx) {
assertThat(config.isShutdownHookEnabled())
.as("Shutdown hook is enabled")
.isFalse();
assertThat(getFieldValue(shutdownCallbackField, ctx))
.as("Shutdown callback")
.isNull();
}

@Test
void testShutdownFlag(final Configuration config, final LoggerContext ctx) throws NoSuchFieldException {
Field shutdownCallback = LoggerContext.class.getDeclaredField("shutdownCallback");
Object fieldValue = ReflectionUtil.getFieldValue(shutdownCallback, ctx);
assertFalse(config.isShutdownHookEnabled(), "Shutdown hook is enabled");
assertNull(fieldValue, "Shutdown callback");
void whenLoggerContextInitialized_respectsShutdownDisabled(TestInfo testInfo) {
Configuration configuration = mockConfiguration();
when(configuration.isShutdownHookEnabled()).thenReturn(false);
try (final LoggerContext ctx = new LoggerContext(testInfo.getDisplayName())) {
ctx.start(configuration);
assertThat(ctx.isStarted()).isTrue();
assertThat(ctx.getConfiguration()).isSameAs(configuration);
assertThat(getFieldValue(shutdownCallbackField, ctx))
.as("Shutdown callback")
.isNull();
}
}

@Test
void whenLoggerContextStarted_ignoresShutdownDisabled(TestInfo testInfo) {
// Traditional behavior: during reconfiguration, the shutdown hook is not removed.
Configuration initialConfiguration = mockConfiguration();
when(initialConfiguration.isShutdownHookEnabled()).thenReturn(true);
Configuration configuration = mockConfiguration();
when(configuration.isShutdownHookEnabled()).thenReturn(false);
try (final LoggerContext ctx = new LoggerContext(testInfo.getDisplayName())) {
ctx.start(initialConfiguration);
assertThat(ctx.isStarted()).isTrue();
Object shutdownCallback = getFieldValue(shutdownCallbackField, ctx);
assertThat(shutdownCallback).as("Shutdown callback").isNotNull();
ctx.start(configuration);
assertThat(getFieldValue(shutdownCallbackField, ctx))
.as("Shutdown callback")
.isSameAs(shutdownCallback);
}
}

private static Configuration mockConfiguration() {
return mock(
AbstractConfiguration.class,
withSettings()
.useConstructor(null, ConfigurationSource.NULL_SOURCE)
.defaultAnswer(CALLS_REAL_METHODS));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ public static LoggerContext getContext(
return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
}

/**
* Starts the context using the configuration specified by {@link #getConfigLocation()}.
* <p>
* If the configuration location is {@code null}, Log4j will search for a configuration file
* using the default classpath resources. For details on the search order and supported formats,
* see the
* <a href="https://logging.apache.org/log4j/2.x/manual/configuration.html#automatic-configuration">
* Log4j 2 Configuration File Location documentation</a>.
* </p>
*/
@Override
public void start() {
LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
Expand All @@ -312,21 +322,31 @@ public void start() {
}

/**
* Starts with a specific configuration.
*
* @param config The new Configuration.
* Starts the context using a specific configuration.
* <p>
* <strong>Warning:</strong> For backward compatibility, especially with Spring Boot,
* if the context is already started, this method will fall back to {@link #reconfigure(Configuration)}.
* This behavior is maintained for legacy integrations and may change in future major versions.
* New code should not rely on this fallback.
* </p>
* @param config The new {@link Configuration} to use for this context
*/
public void start(final Configuration config) {
LOGGER.info("Starting {}[name={}] with configuration {}...", getClass().getSimpleName(), getName(), config);
if (configLock.tryLock()) {
try {
if (this.isInitialized() || this.isStopped()) {
if (isInitialized() || isStopped()) {
setStarting();
reconfigure(config);
if (this.configuration.isShutdownHookEnabled()) {
setUpShutdownHook();
}
this.setStarted();
setStarted();
} else {
// Required for Spring Boot integration:
// Both `Log4jSpringBootLoggingSystem` and its Spring Boot 3.x equivalent
// invoke `start()` even during context reconfiguration.
reconfigure(config);
}
} finally {
configLock.unlock();
Expand Down
12 changes: 12 additions & 0 deletions src/changelog/.2.x.x/3770_LoggerContext_start.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="fixed">
<issue id="3770" link="https://github.com/apache/logging-log4j2/issues/3770"/>
<description format="asciidoc">
Restore backward compatibility with the Spring Boot reconfiguration process.
</description>
</entry>