This is an automated email from the ASF dual-hosted git repository. ramanathan1504 pushed a commit to branch ramanathan1504-issue-2351 in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 0814ed564c8a0fca2730a96a0a9e28e2ec183b07 Author: Ramanathan <[email protected]> AuthorDate: Wed Jun 17 23:53:27 2026 +0530 Deprecate `externalContext` and migrate to Map-backed `getObject/putObject` for improved servlet context resolution in composite configurations --- .../apache/logging/log4j/spi/LoggerContext.java | 3 + .../logging/log4j/core/LoggerContextTest.java | 20 ++++++ .../apache/logging/log4j/core/LoggerContext.java | 16 ++++- .../log4j/core/impl/Log4jContextFactory.java | 39 ++++++++--- .../apache/logging/log4j/web/WebLookupTest.java | 81 ---------------------- .../logging/log4j/web/Log4jWebInitializerImpl.java | 60 +++++++++++++--- .../logging/log4j/web/WebLoggerContextUtils.java | 30 ++++++-- .../apache/logging/log4j/web/WebLookupTest.java | 52 ++++++++++++++ ...l_context_and_migrate_to_map_get_put_object.xml | 13 ++++ 9 files changed, 208 insertions(+), 106 deletions(-) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java index d5a9119a1e..7a75ce27f7 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java @@ -34,7 +34,10 @@ public interface LoggerContext { /** * Gets the anchor for some other context, such as a ClassLoader or ServletContext. * @return The external context. + * @deprecated Use {@link #getObject(String)} instead. + * @since 2.27.0 */ + @Deprecated Object getExternalContext(); /** diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java index e6d58f66c7..074fdbb994 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.core; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.withSettings; @@ -101,4 +102,23 @@ class LoggerContextTest { assertThat(loggerContext.getConfiguration()).isSameAs(configuration); } } + + @Test + public void testLegacyExternalContextCompatibility() { + LoggerContext ctx = new LoggerContext("TestContext"); + String legacyValue = "Spring-Boot-Flag"; + ctx.setExternalContext(legacyValue); + assertEquals(legacyValue, ctx.getExternalContext()); + assertEquals(legacyValue, ctx.getObject("__EXTERNAL_CONTEXT_KEY__")); + } + + @Test + public void testCollisionPrevention() { + LoggerContext ctx = new LoggerContext("CollisionTest"); + ctx.setExternalContext("Spring-Flag"); + String mockServletContext = "MockServletContext"; + ctx.putObject("org.apache.logging.log4j.web.servletContext", mockServletContext); + assertEquals("Spring-Flag", ctx.getExternalContext()); + assertEquals(mockServletContext, ctx.getObject("org.apache.logging.log4j.web.servletContext")); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java index da62cc2485..457d2154b7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java @@ -152,7 +152,10 @@ public class LoggerContext extends AbstractLifeCycle * * @param name The context name. * @param externalContext The external context. + * @deprecated Use {@link #LoggerContext(String)} and {@link #putObject(String, Object)} instead. + * @since 2.27.0 */ + @Deprecated public LoggerContext(final String name, final Object externalContext) { this(name, externalContext, (URI) null); } @@ -163,11 +166,14 @@ public class LoggerContext extends AbstractLifeCycle * @param name The context name. * @param externalContext The external context. * @param configLocn The location of the configuration as a URI. + * @deprecated Use {@link #LoggerContext(String)} and {@link #putObject(String, Object)} instead. + * @since 2.27.0 */ + @Deprecated public LoggerContext(final String name, final Object externalContext, final URI configLocn) { this.contextName = name; if (externalContext != null) { - externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext); + this.putObject(EXTERNAL_CONTEXT_KEY, externalContext); } this.configLocation = configLocn; } @@ -531,7 +537,10 @@ public class LoggerContext extends AbstractLifeCycle * Sets the external context. * * @param context The external context. + * @deprecated Use {@link #putObject(String, Object)} instead. + * @since 2.27.0 */ + @Deprecated public void setExternalContext(final Object context) { if (context != null) { this.externalMap.put(EXTERNAL_CONTEXT_KEY, context); @@ -544,8 +553,11 @@ public class LoggerContext extends AbstractLifeCycle * Returns the external context. * * @return The external context. + * @deprecated Use {@link #getObject(String)} instead. + * @since 2.27.0 */ @Override + @Deprecated public Object getExternalContext() { return this.externalMap.get(EXTERNAL_CONTEXT_KEY); } @@ -766,7 +778,7 @@ public class LoggerContext extends AbstractLifeCycle * Reconfigures the context. */ private void reconfigure(final URI configURI) { - final Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY); + final Object externalContext = getExternalContext(); final ClassLoader cl = externalContext instanceof ClassLoader ? (ClassLoader) externalContext : null; LOGGER.debug( "Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}", diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java index c279fa2dd9..d69548c2bb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java @@ -148,8 +148,11 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba * for the caller if a more appropriate Context can be determined. * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. * @return The LoggerContext. + * @deprecated Use {@link org.apache.logging.log4j.spi.LoggerContext#putObject(String, Object)} instead. + * @since 2.27.0 */ @Override + @Deprecated public LoggerContext getContext( final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext); @@ -171,7 +174,10 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba * for the caller if a more appropriate Context can be determined. * @param source The configuration source. * @return The LoggerContext. + * @deprecated Use {@link org.apache.logging.log4j.spi.LoggerContext#putObject(String, Object)} instead. + * @since 2.27.0 */ + @Deprecated public LoggerContext getContext( final String fqcn, final ClassLoader loader, @@ -205,7 +211,10 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba * for the caller if a more appropriate Context can be determined. * @param configuration The Configuration. * @return The LoggerContext. + * @deprecated Use {@link org.apache.logging.log4j.spi.LoggerContext#putObject(String, Object)} instead. + * @since 2.27.0 */ + @Deprecated public LoggerContext getContext( final String fqcn, final ClassLoader loader, @@ -236,8 +245,11 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba * for the caller if a more appropriate Context can be determined. * @param configLocation The location of the configuration for the LoggerContext (or null). * @return The LoggerContext. + * @deprecated Use {@link org.apache.logging.log4j.spi.LoggerContext#putObject(String, Object)} instead. + * @since 2.27.0 */ @Override + @Deprecated public LoggerContext getContext( final String fqcn, final ClassLoader loader, @@ -293,6 +305,19 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba return ctx; } + /** + * Loads the LoggerContext using the ContextSelector. + * @param fqcn The fully qualified class name of the caller. + * @param loader The ClassLoader to use or null. + * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. + * @param currentContext If true returns the current Context, if false returns the Context appropriate + * for the caller if a more appropriate Context can be determined. + * @param configLocations The locations of the configuration for the LoggerContext (or null). + * @return The LoggerContext. + * @deprecated Use {@link org.apache.logging.log4j.spi.LoggerContext#putObject(String, Object)} instead. + * @since 2.27.0 + */ + @Deprecated public LoggerContext getContext( final String fqcn, final ClassLoader loader, @@ -300,17 +325,11 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba final boolean currentContext, final List<URI> configLocations, final String name) { - final LoggerContext ctx; - if (externalContext instanceof Map.Entry) { - @SuppressWarnings("unchecked") - final Map.Entry<String, Object> entry = (Map.Entry<String, Object>) externalContext; - ctx = selector.getContext(fqcn, loader, entry, currentContext, null); - } else { - ctx = selector.getContext(fqcn, loader, currentContext, null); - if (externalContext != null && ctx.getExternalContext() == null) { - ctx.setExternalContext(externalContext); - } + final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null); + if (externalContext != null && ctx.getExternalContext() == null) { + ctx.setExternalContext(externalContext); } + if (name != null) { ctx.setName(name); } diff --git a/log4j-jakarta-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java b/log4j-jakarta-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java index 1b9b60a739..92874baa5e 100644 --- a/log4j-jakarta-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java +++ b/log4j-jakarta-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java @@ -16,28 +16,8 @@ */ package org.apache.logging.log4j.web; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import jakarta.servlet.ServletContext; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; -import org.apache.logging.log4j.core.impl.ContextAnchor; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - public class WebLookupTest { - @AfterEach - void tearDown() { - ContextAnchor.THREAD_CONTEXT.remove(); - } - // TODO: re-enable when https://github.com/spring-projects/spring-framework/issues/25354 is fixed // @Test @@ -114,66 +94,5 @@ public class WebLookupTest { // initializer.stop(); // ContextAnchor.THREAD_CONTEXT.remove(); // } - /** - * Regression test for GitHub issue #2351: - * "Missing servlet context in web lookup when using composite configuration". - * - * When log4jConfiguration contains a comma-separated list of config files, - * the resulting composite LoggerContext must still expose the ServletContext - * via WebLoggerContextUtils.getServletContext() so that ${web:*} lookups resolve. - */ - @Test - void testCompositeConfigurationServletContextName() throws Exception { - ContextAnchor.THREAD_CONTEXT.remove(); - - final String expectedServletContextName = "CompositeTest"; - - // Use Mockito to create a minimal ServletContext (no Spring dependency needed) - final ServletContext servletContext = mock(ServletContext.class); - when(servletContext.getServletContextName()).thenReturn(expectedServletContextName); - when(servletContext.getContextPath()).thenReturn("/composite-test"); - // Composite configuration: two comma-separated config files - when(servletContext.getInitParameter(Log4jWebSupport.LOG4J_CONFIG_LOCATION)) - .thenReturn("log4j2-combined.xml,log4j2-override.xml"); - // Let the initializer resolve each file via the servlet context resource lookup - when(servletContext.getResource("log4j2-combined.xml")) - .thenReturn(getClass().getResource("/log4j2-combined.xml")); - when(servletContext.getResource("log4j2-override.xml")) - .thenReturn(getClass().getResource("/log4j2-override.xml")); - - final Log4jWebLifeCycle initializer = WebLoggerContextUtils.getWebLifeCycle(servletContext); - try { - initializer.start(); - initializer.setLoggerContext(); - - final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); - assertNotNull(ctx, "No LoggerContext"); - - // The servlet context MUST be reachable via the web lookup for composite config. - // Before the fix this returns null, breaking all ${web:*} lookups. - assertNotNull( - WebLoggerContextUtils.getServletContext(), - "ServletContext is null in composite configuration - " - + "${web:*} lookups will not resolve (issue #2351)"); - - final Configuration config = ctx.getConfiguration(); - assertNotNull(config, "No Configuration"); - assertInstanceOf( - CompositeConfiguration.class, - config, - "Expected CompositeConfiguration for comma-separated log4jConfiguration"); - final StrSubstitutor substitutor = config.getStrSubstitutor(); - assertNotNull(substitutor, "No StrSubstitutor"); - // Core assertion: ${web:servletContextName} must resolve to the actual name - final String value = substitutor.replace("${web:servletContextName}"); - assertEquals( - expectedServletContextName, - value, - "${web:servletContextName} did not resolve in composite configuration (issue #2351)"); - } finally { - initializer.stop(); - ContextAnchor.THREAD_CONTEXT.remove(); - } - } } diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java index 0884f6a69c..b3b2dc9367 100644 --- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java +++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java @@ -30,9 +30,14 @@ import java.util.concurrent.TimeUnit; import javax.servlet.ServletContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.AbstractLifeCycle; +import org.apache.logging.log4j.core.LifeCycle; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.async.AsyncLoggerContext; +import org.apache.logging.log4j.core.config.AbstractConfiguration; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.impl.ContextAnchor; import org.apache.logging.log4j.core.impl.Log4jContextFactory; import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor; @@ -53,6 +58,8 @@ final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWe private static final String WEB_INF = "/WEB-INF/"; + private static final String SERVLET_CONTEXT_KEY = "org.apache.logging.log4j.web.servletContext"; + static { if (Loader.isClassAvailable("org.apache.logging.log4j.core.web.JNDIContextFilter")) { throw new IllegalStateException("You are using Log4j 2 in a web application with the old, extinct " @@ -133,8 +140,8 @@ final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWe final ContextSelector selector = ((Log4jContextFactory) factory).getSelector(); if (selector instanceof NamedContextSelector) { this.namedContextSelector = (NamedContextSelector) selector; - context = this.namedContextSelector.locateContext( - this.name, WebLoggerContextUtils.createExternalEntry(this.servletContext), configLocation); + context = this.namedContextSelector.locateContext(this.name, null, configLocation); + context.putObject(SERVLET_CONTEXT_KEY, this.servletContext); ContextAnchor.THREAD_CONTEXT.set(context); if (context.isInitialized()) { context.start(); @@ -168,17 +175,54 @@ final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWe LOGGER.error("No Log4j context configuration provided. This is very unusual."); this.name = new SimpleDateFormat("yyyyMMdd_HHmmss.SSS").format(new Date()); } + if (location != null && location.contains(",")) { final List<URI> uris = getConfigURIs(location); - this.loggerContext = Configurator.initialize( - this.name, - this.getClassLoader(), - uris, - WebLoggerContextUtils.createExternalEntry(this.servletContext)); + final LoggerContextFactory factory = LogManager.getFactory(); + if (factory instanceof Log4jContextFactory) { + final ContextSelector selector = ((Log4jContextFactory) factory).getSelector(); + this.loggerContext = selector.getContext( + Log4jWebInitializerImpl.class.getName(), this.getClassLoader(), false, null); + if (this.loggerContext != null) { + this.loggerContext.putObject(SERVLET_CONTEXT_KEY, this.servletContext); + if (this.name != null) { + this.loggerContext.setName(this.name); + } + if (this.loggerContext.getState() == LifeCycle.State.INITIALIZED) { + ContextAnchor.THREAD_CONTEXT.set(this.loggerContext); + try { + final List<AbstractConfiguration> configurations = new ArrayList<>(uris.size()); + for (final URI configLocation : uris) { + final Configuration config = ConfigurationFactory.getInstance() + .getConfiguration(this.loggerContext, this.name, configLocation); + if (config instanceof AbstractConfiguration) { + configurations.add((AbstractConfiguration) config); + } + } + if (configurations.size() == 1) { + this.loggerContext.start(configurations.get(0)); + } else if (configurations.size() > 1) { + this.loggerContext.start(new CompositeConfiguration(configurations)); + } else { + this.loggerContext.start(); + } + } finally { + ContextAnchor.THREAD_CONTEXT.remove(); + } + } + } + } else { + this.loggerContext = + Configurator.initialize(this.name, this.getClassLoader(), uris, this.servletContext); + if (this.loggerContext != null) { + this.loggerContext.putObject(SERVLET_CONTEXT_KEY, this.servletContext); + } + } return; } final URI uri = getConfigURI(location); + // Use Map.Entry version for single URI to ensure ServletContext is available before configuration loads this.loggerContext = Configurator.initialize( this.name, this.getClassLoader(), uri, WebLoggerContextUtils.createExternalEntry(this.servletContext)); } @@ -275,7 +319,7 @@ final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWe this.namedContextSelector.removeContext(this.name); } this.loggerContext.stop(timeout, timeUnit); - this.loggerContext.setExternalContext(null); + this.loggerContext.removeObject(SERVLET_CONTEXT_KEY); this.loggerContext = null; } this.setStopped(); diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLoggerContextUtils.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLoggerContextUtils.java index 1e6d506be8..60feab64b6 100644 --- a/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLoggerContextUtils.java +++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLoggerContextUtils.java @@ -37,8 +37,7 @@ public final class WebLoggerContextUtils { private WebLoggerContextUtils() {} private static final Lock WEB_SUPPORT_LOOKUP = new ReentrantLock(); - private static final String SERVLET_CONTEXT = "__SERVLET_CONTEXT__"; - + private static final String SERVLET_CONTEXT = "org.apache.logging.log4j.web.servletContext"; /** * Finds the main {@link org.apache.logging.log4j.core.LoggerContext} configured for the given ServletContext. * @@ -110,18 +109,29 @@ public final class WebLoggerContextUtils { }; } + /** + * @deprecated Use {@link #setServletContext(LoggerContext, ServletContext)} instead. + * @since 2.27.0 + */ + @Deprecated public static Map.Entry<String, Object> createExternalEntry(final ServletContext servletContext) { return new AbstractMap.SimpleEntry<>(SERVLET_CONTEXT, servletContext); } - public static void setServletContext(LoggerContext lc, ServletContext servletContext) { + /** + * Sets the ServletContext for the given LoggerContext. + * + * @param lc the LoggerContext + * @param servletContext the ServletContext + */ + public static void setServletContext(final LoggerContext lc, final ServletContext servletContext) { if (lc != null) { lc.putObject(SERVLET_CONTEXT, servletContext); } } /** - * Gets the current {@link ServletContext} if it has already been assigned to a LoggerContext's external context. + * Gets the current {@link ServletContext} if it has already been assigned to a LoggerContext. * * @return the current ServletContext attached to a LoggerContext or {@code null} if none could be found * @since 2.1 @@ -132,10 +142,20 @@ public final class WebLoggerContextUtils { lc = LogManager.getContext(false); } - final Object obj = lc != null ? lc.getObject(SERVLET_CONTEXT) : null; + if (lc == null) { + return null; + } + + final Object obj = lc.getObject(SERVLET_CONTEXT); if (obj instanceof ServletContext) { return (ServletContext) obj; } + + final Object legacy = lc.getExternalContext(); + if (legacy instanceof ServletContext) { + return (ServletContext) legacy; + } + return null; } } diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java index 4973d179d4..bced45848a 100644 --- a/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java +++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java @@ -18,7 +18,10 @@ package org.apache.logging.log4j.web; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.util.Map; import javax.servlet.ServletContext; @@ -26,6 +29,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.FileAppender; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.impl.ContextAnchor; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.junit.jupiter.api.Test; @@ -107,4 +111,52 @@ class WebLookupTest { initializer.stop(); ContextAnchor.THREAD_CONTEXT.remove(); } + + @Test + void testCompositeConfigurationServletContextName() throws Exception { + ContextAnchor.THREAD_CONTEXT.remove(); + + final String expectedServletContextName = "CompositeTest"; + + final ServletContext servletContext = mock(ServletContext.class); + when(servletContext.getServletContextName()).thenReturn(expectedServletContextName); + when(servletContext.getContextPath()).thenReturn("/composite-test"); + when(servletContext.getInitParameter(Log4jWebSupport.LOG4J_CONFIG_LOCATION)) + .thenReturn("log4j2-combined.xml,log4j2-override.xml"); + when(servletContext.getResource("log4j2-combined.xml")) + .thenReturn(getClass().getResource("/log4j2-combined.xml")); + when(servletContext.getResource("log4j2-override.xml")) + .thenReturn(getClass().getResource("/log4j2-override.xml")); + + final Log4jWebLifeCycle initializer = WebLoggerContextUtils.getWebLifeCycle(servletContext); + try { + initializer.start(); + initializer.setLoggerContext(); + + final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); + assertNotNull(ctx, "No LoggerContext"); + + assertNotNull( + WebLoggerContextUtils.getServletContext(), + "ServletContext is null in composite configuration - " + + "${web:*} lookups will not resolve (issue #2351)"); + + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No Configuration"); + assertTrue( + config instanceof CompositeConfiguration, + "Expected CompositeConfiguration for comma-separated log4jConfiguration"); + final StrSubstitutor substitutor = config.getStrSubstitutor(); + assertNotNull(substitutor, "No StrSubstitutor"); + + final String value = substitutor.replace("${web:servletContextName}"); + assertEquals( + expectedServletContextName, + value, + "${web:servletContextName} did not resolve in composite configuration (issue #2351)"); + } finally { + initializer.stop(); + ContextAnchor.THREAD_CONTEXT.remove(); + } + } } diff --git a/src/changelog/.2.x.x/deprecate_external_context_and_migrate_to_map_get_put_object.xml b/src/changelog/.2.x.x/deprecate_external_context_and_migrate_to_map_get_put_object.xml new file mode 100644 index 0000000000..4326fd1157 --- /dev/null +++ b/src/changelog/.2.x.x/deprecate_external_context_and_migrate_to_map_get_put_object.xml @@ -0,0 +1,13 @@ +<?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="changed"> + <issue id="2351" link="https://github.com/apache/logging-log4j2/pull/2351"/> + <issue id="4070" link="https://github.com/apache/logging-log4j2/pull/4070"/> + <description format="asciidoc"> + Deprecate `externalContext` and migrate to Map-backed `getObject/putObject`. This work resolves the servlet context resolution issue in composite configurations. + </description> +</entry>
