This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new c685326e6d Deprecate externalContext and migrate to Map-backed 
getObject/putObject (#4070)
c685326e6d is described below

commit c685326e6d33381fb518a44fb8069c176d9c3b08
Author: Ramanathan <[email protected]>
AuthorDate: Wed Sep 2 17:06:40 2026 +0530

    Deprecate externalContext and migrate to Map-backed getObject/putObject 
(#4070)
    
    Co-authored-by: Volkan Yazıcı <[email protected]>
---
 .../apache/logging/log4j/spi/LoggerContext.java    |  2 +
 .../logging/log4j/core/LoggerContextTest.java      | 20 +++++++
 .../apache/logging/log4j/core/LoggerContext.java   | 12 ++++-
 .../log4j/core/impl/Log4jContextFactory.java       | 20 +++++++
 .../logging/log4j/web/Log4jWebInitializerImpl.java | 58 +++++++++++++++++---
 .../logging/log4j/web/WebLoggerContextUtils.java   | 30 +++++++++--
 .../apache/logging/log4j/web/WebLookupTest.java    | 61 ++++++++++++++++++++++
 .../logging/log4j/web/Log4jWebInitializerImpl.java | 59 ++++++++++++++++++---
 .../logging/log4j/web/WebLoggerContextUtils.java   | 29 ++++++++--
 .../apache/logging/log4j/web/WebLookupTest.java    | 52 ++++++++++++++++++
 ...l_context_and_migrate_to_map_get_put_object.xml | 14 +++++
 11 files changed, 329 insertions(+), 28 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..bee49c1ff1 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,9 @@ 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.
      */
+    @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..7d9fd42096 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,9 @@ 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.
      */
+    @Deprecated
     public LoggerContext(final String name, final Object externalContext) {
         this(name, externalContext, (URI) null);
     }
@@ -163,11 +165,13 @@ 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)} to create a new context, 
and then use {@link #putObject(String, Object)} to attach the {@code 
externalContext}.
      */
+    @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 +535,9 @@ public class LoggerContext extends AbstractLifeCycle
      * Sets the external context.
      *
      * @param context The external context.
+     * @deprecated Use {@link #putObject(String, Object)} instead.
      */
+    @Deprecated
     public void setExternalContext(final Object context) {
         if (context != null) {
             this.externalMap.put(EXTERNAL_CONTEXT_KEY, context);
@@ -544,8 +550,10 @@ public class LoggerContext extends AbstractLifeCycle
      * Returns the external context.
      *
      * @return The external context.
+     * @deprecated Use {@link #getObject(String)} instead.
      */
     @Override
+    @Deprecated
     public Object getExternalContext() {
         return this.externalMap.get(EXTERNAL_CONTEXT_KEY);
     }
@@ -766,7 +774,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 bc89276774..63b9a915e7 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,10 @@ 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#getObject(String)} instead.
      */
     @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 +173,9 @@ 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#getObject(String)} instead.
      */
+    @Deprecated
     public LoggerContext getContext(
             final String fqcn,
             final ClassLoader loader,
@@ -205,7 +209,9 @@ 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#getObject(String)} instead.
      */
+    @Deprecated
     public LoggerContext getContext(
             final String fqcn,
             final ClassLoader loader,
@@ -236,8 +242,10 @@ 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#getObject(String)} instead.
      */
     @Override
+    @Deprecated
     public LoggerContext getContext(
             final String fqcn,
             final ClassLoader loader,
@@ -293,6 +301,18 @@ 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#getObject(String)} instead.
+     */
+    @Deprecated
     public LoggerContext getContext(
             final String fqcn,
             final ClassLoader loader,
diff --git 
a/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
 
b/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
index 22dcfe4dba..caf72b8a77 100644
--- 
a/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
+++ 
b/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
@@ -30,9 +30,14 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 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 = namedContextSelector.locateContext(name, null, 
configLocation);
+                context.putObject(SERVLET_CONTEXT_KEY, servletContext);
                 ContextAnchor.THREAD_CONTEXT.set(context);
                 if (context.isInitialized()) {
                     context.start();
@@ -168,13 +175,48 @@ 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();
+                loggerContext =
+                        
selector.getContext(Log4jWebInitializerImpl.class.getName(), getClassLoader(), 
false, null);
+                if (loggerContext != null) {
+                    loggerContext.putObject(SERVLET_CONTEXT_KEY, 
servletContext);
+                    if (name != null) {
+                        loggerContext.setName(name);
+                    }
+                    if (loggerContext.getState() == 
LifeCycle.State.INITIALIZED) {
+                        ContextAnchor.THREAD_CONTEXT.set(loggerContext);
+                        try {
+                            final List<AbstractConfiguration> configurations = 
new ArrayList<>(uris.size());
+                            for (final URI configLocation : uris) {
+                                final Configuration config = 
ConfigurationFactory.getInstance()
+                                        .getConfiguration(loggerContext, name, 
configLocation);
+                                if (config instanceof AbstractConfiguration) {
+                                    configurations.add((AbstractConfiguration) 
config);
+                                }
+                            }
+                            if (configurations.size() == 1) {
+                                loggerContext.start(configurations.get(0));
+                            } else if (configurations.size() > 1) {
+                                loggerContext.start(new 
CompositeConfiguration(configurations));
+                            } else {
+                                loggerContext.start();
+                            }
+                        } finally {
+                            ContextAnchor.THREAD_CONTEXT.remove();
+                        }
+                    }
+                }
+            } else {
+                loggerContext = Configurator.initialize(name, 
getClassLoader(), uris, servletContext);
+                if (loggerContext != null) {
+                    loggerContext.putObject(SERVLET_CONTEXT_KEY, 
servletContext);
+                }
+            }
             return;
         }
 
@@ -275,7 +317,7 @@ final class Log4jWebInitializerImpl extends 
AbstractLifeCycle implements Log4jWe
                     this.namedContextSelector.removeContext(this.name);
                 }
                 this.loggerContext.stop(timeout, timeUnit);
-                this.loggerContext.setExternalContext(null);
+                loggerContext.removeObject(SERVLET_CONTEXT_KEY);
                 this.loggerContext = null;
             }
             this.setStopped();
diff --git 
a/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/WebLoggerContextUtils.java
 
b/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/WebLoggerContextUtils.java
index de22870634..77471e8956 100644
--- 
a/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/WebLoggerContextUtils.java
+++ 
b/log4j-jakarta-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-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 92874baa5e..e93d4ea129 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,6 +16,20 @@
  */
 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.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.Test;
+
 public class WebLookupTest {
 
     // TODO: re-enable when 
https://github.com/spring-projects/spring-framework/issues/25354 is fixed
@@ -95,4 +109,51 @@ public class WebLookupTest {
     //        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/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..68393b58e1 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,13 +175,49 @@ 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;
         }
 
@@ -275,7 +318,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..4b8e068631 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,28 @@ public final class WebLoggerContextUtils {
         };
     }
 
+    /**
+     * @deprecated Use {@link #setServletContext(LoggerContext, 
ServletContext)} instead.
+     */
+    @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 +141,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..4c5ef6a030
--- /dev/null
+++ 
b/src/changelog/.2.x.x/deprecate_external_context_and_migrate_to_map_get_put_object.xml
@@ -0,0 +1,14 @@
+<?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="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 fixes the servlet context resolution issue in composite 
configurations.
+    </description>
+</entry>

Reply via email to