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>