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]

Reply via email to