This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 6704676af27ca39ef84506e0b4ee11971f5fb622 Author: opencode <[email protected]> AuthorDate: Tue Oct 20 08:59:15 2026 +0200 Clear handler prefix ThreadLocal when handler creation fails In ClassLoaderLogManager.readConfiguration(InputStream, ClassLoader) the prefix of the handler being configured is held in a ThreadLocal while the handler is instantiated, so the handler reads its own prefixed properties during construction. The reset of that ThreadLocal only ran on the success path, so a handler that could not be created, for example due to a typo in the handler class name in a web application's logging.properties, left its prefix on the configuring thread. Subsequent property lookups on that thread were then resolved against the prefixed properties of the failed handler before the unprefixed ones, which could corrupt the configuration of loggers and handlers created afterwards. The reset now runs in a finally block so the prefix is cleared on every path, including when an Error escapes the handler's static initialization. The side effect is that the prefix is cleared after the handler has been added to the handlers map and to the root logger. Neither operation resolves properties, so there is no change in behaviour on the success path. Added a regression test that resolves an unprefixed property after configuring a single broken handler. --- java/org/apache/juli/ClassLoaderLogManager.java | 5 ++- .../org/apache/juli/TestClassLoaderLogManager.java | 42 ++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/java/org/apache/juli/ClassLoaderLogManager.java b/java/org/apache/juli/ClassLoaderLogManager.java index d943d6512e..4e834c3a3d 100644 --- a/java/org/apache/juli/ClassLoaderLogManager.java +++ b/java/org/apache/juli/ClassLoaderLogManager.java @@ -566,7 +566,6 @@ public class ClassLoaderLogManager extends LogManager { // The specification strongly implies all configuration should be done // during the creation of the handler object. // This includes setting level, filter, formatter and encoding. - this.prefix.set(null); info.handlers.put(handlerName, handler); if (rootHandlers == null) { localRootLogger.addHandler(handler); @@ -575,6 +574,10 @@ public class ClassLoaderLogManager extends LogManager { // Report error System.err.println("Handler error"); e.printStackTrace(); + } finally { + // Clear the prefix even when the handler could not be created, so a + // stale prefix does not affect property resolution on this thread + this.prefix.set(null); } } diff --git a/test/org/apache/juli/TestClassLoaderLogManager.java b/test/org/apache/juli/TestClassLoaderLogManager.java index b2482af790..dc05850c9f 100644 --- a/test/org/apache/juli/TestClassLoaderLogManager.java +++ b/test/org/apache/juli/TestClassLoaderLogManager.java @@ -20,6 +20,9 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Random; import java.util.logging.Level; @@ -108,6 +111,30 @@ public class TestClassLoaderLogManager { } } + /* + * The prefix of a handler must not remain set on the configuring thread after the instantiation of the handler + * fails, as subsequent property lookups on that thread would otherwise first be resolved against the prefixed + * properties of the failed handler. + */ + @Test + public void testStaleHandlerPrefixAfterHandlerFailure() throws IOException { + final String config = "handlers = 10Foo.MissingHandler" + System.lineSeparator() + + "10Foo.marker = STALE_PREFIX_VALUE" + System.lineSeparator() + + "marker = CLEAN_VALUE" + System.lineSeparator(); + final ClassLoader cl = new TestClassLoader(config.getBytes(StandardCharsets.UTF_8)); + final Thread currentThread = Thread.currentThread(); + final ClassLoader oldCL = currentThread.getContextClassLoader(); + try { + currentThread.setContextClassLoader(cl); + final ClassLoaderLogManager logManager = new ClassLoaderLogManager(); + logManager.readConfiguration(); + // The failed handler must not leave its prefix behind for this thread + Assert.assertEquals("CLEAN_VALUE", logManager.getProperty("marker")); + } finally { + currentThread.setContextClassLoader(oldCL); + } + } + private static class LoggerCreateThread extends Thread { private final LogManager logManager; @@ -161,7 +188,18 @@ public class TestClassLoaderLogManager { } } - private static class TestClassLoader extends ClassLoader implements WebappProperties { + private static class TestClassLoader extends URLClassLoader implements WebappProperties { + + private final byte[] loggingProperties; + + TestClassLoader() { + this(EMPTY_BYTES); + } + + TestClassLoader(byte[] loggingProperties) { + super(new URL[0]); + this.loggingProperties = loggingProperties; + } @Override public String getWebappName() { @@ -186,7 +224,7 @@ public class TestClassLoaderLogManager { @Override public InputStream getResourceAsStream(final String resource) { if ("logging.properties".equals(resource)) { - return new ByteArrayInputStream(EMPTY_BYTES); + return new ByteArrayInputStream(loggingProperties); } return null; } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
