This is an automated email from the ASF dual-hosted git repository. dsoumis pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 78fd89cb6bb5e140129781d15734e6ec46a5f594 Author: Dimitris Soumis <[email protected]> AuthorDate: Fri Aug 29 23:10:37 2025 +0300 Add WebappLogCapture to capture webapp-scoped logs at CONFIGURE_START --- .../apache/catalina/startup/TomcatBaseTest.java | 119 +++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/test/org/apache/catalina/startup/TomcatBaseTest.java b/test/org/apache/catalina/startup/TomcatBaseTest.java index 683938714d..7bc8cb087c 100644 --- a/test/org/apache/catalina/startup/TomcatBaseTest.java +++ b/test/org/apache/catalina/startup/TomcatBaseTest.java @@ -30,10 +30,18 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -48,7 +56,10 @@ import org.junit.Before; import org.apache.catalina.Container; import org.apache.catalina.Context; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleListener; import org.apache.catalina.LifecycleState; import org.apache.catalina.Manager; import org.apache.catalina.Server; @@ -934,4 +945,112 @@ public abstract class TomcatBaseTest extends LoggingBaseTest { session.setMaxInactiveInterval(newIntervalSecs); } } + + /** + * Captures webapp-scoped logs (e.g. ContextConfig/Digester) during the + * CONFIGURE_START phase of a {@link Context}. + */ + public static class WebappLogCapture implements LifecycleListener, AutoCloseable { + private final Level level; + private final String[] loggerNames; + private final List<LogRecord> logRecords = Collections.synchronizedList(new ArrayList<>()); + private final Map<Logger, Level> previousLevelsOfLoggersMap = new IdentityHashMap<>(); + private final List<Logger> attachedLoggers = new CopyOnWriteArrayList<>(); + private volatile boolean installed = false; + + private final Handler handler = new Handler() { + @Override + public void publish(LogRecord record) { + logRecords.add(record); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + logRecords.clear(); + } + }; + public WebappLogCapture(Level level, String... loggerNames) { + this.level = level == null ? Level.ALL : level; + this.loggerNames = loggerNames; + } + + @Override + public void close() throws Exception { + for (Logger l : attachedLoggers) { + try { + l.removeHandler(handler); + } catch (Throwable ignore) { + } + try { + l.setLevel(previousLevelsOfLoggersMap.get(l)); + } catch (Throwable ignore) { + } + } + attachedLoggers.clear(); + previousLevelsOfLoggersMap.clear(); + } + + @Override + public void lifecycleEvent(LifecycleEvent event) { + if (Lifecycle.CONFIGURE_START_EVENT.equals(event.getType()) && !installed) { + installed = true; + for (String name : loggerNames) { + Logger logger = Logger.getLogger(name); + previousLevelsOfLoggersMap.put(logger, logger.getLevel()); + logger.addHandler(handler); + logger.setLevel(level); + attachedLoggers.add(logger); + } + } + } + + public boolean containsText(CharSequence s) { + for (LogRecord record : logRecords) { + if (record.getMessage().contains(s)) { + return true; + } + } + return false; + } + public boolean hasException(Class<? extends Throwable> type) { + for (LogRecord record : logRecords) { + Throwable t = record.getThrown(); + while (t != null) { + if (type.isInstance(t)) {return true;} + t = t.getCause(); + } + } + return false; + } + } + + /** + * Installs a {@link WebappLogCapture} on the given {@link Context} so it runs + * before {@link ContextConfig} during CONFIGURE_START. + * @param ctx the webapp context + * @param level level for loggers (e.g. {@code Level.ALL}) + * @param loggerNames fully-qualified logger names + * @return the active capture + */ + public static WebappLogCapture attachWebappLogCapture(Context ctx, Level level, String... loggerNames) { + List<LifecycleListener> lifecycleListenersToReAdd = new ArrayList<>(); + for (LifecycleListener l : ctx.findLifecycleListeners()) { + if (l instanceof ContextConfig) { + lifecycleListenersToReAdd.add(l); + } + } + for (LifecycleListener l : lifecycleListenersToReAdd) { + ctx.removeLifecycleListener(l); + } + WebappLogCapture webappLogCapture = new WebappLogCapture(level, loggerNames); + ctx.addLifecycleListener(webappLogCapture); + for (LifecycleListener l : lifecycleListenersToReAdd) { + ctx.addLifecycleListener(l); + } + return webappLogCapture; + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
