This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 263f3fdd069fbe867f768454d05478d5a8cea7c1 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 | 33 +++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/java/org/apache/juli/ClassLoaderLogManager.java b/java/org/apache/juli/ClassLoaderLogManager.java index 77db708011..c9da071977 100644 --- a/java/org/apache/juli/ClassLoaderLogManager.java +++ b/java/org/apache/juli/ClassLoaderLogManager.java @@ -695,7 +695,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); @@ -704,6 +703,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 2e57009308..2f44fb74c3 100644 --- a/test/org/apache/juli/TestClassLoaderLogManager.java +++ b/test/org/apache/juli/TestClassLoaderLogManager.java @@ -115,6 +115,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); + } + } + /* * reconfigure() must replace the configuration and apply it to the loggers that already exist (which a container * class holds a static reference to), unlike readConfiguration() which only configures loggers created afterwards. @@ -295,8 +319,15 @@ public class TestClassLoaderLogManager { 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; } @@ -332,7 +363,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]
