This is an automated email from the ASF dual-hosted git repository.

shuber pushed a commit to branch unomi-3-dev
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/unomi-3-dev by this push:
     new c7f09ee71 Refactor logging in tests to reduce noise and improve clarity
c7f09ee71 is described below

commit c7f09ee7145178b34c5a6c13b1113986ae5fd375
Author: Serge Huber <[email protected]>
AuthorDate: Tue Dec 9 10:16:34 2025 +0100

    Refactor logging in tests to reduce noise and improve clarity
    
    - Removed console logging from BaseIT and LogChecker to prevent log capture 
issues.
    - Enhanced ProgressListener to display search engine information and 
improved test start/end logging format.
    - Introduced optimizations in LogChecker for handling ignored patterns, 
including support for literal string patterns and regex optimizations.
    - Added a system property to control stack trace logging in 
IPValidationUtils, allowing suppression of stack traces for cleaner test output.
---
 .../test/java/org/apache/unomi/itests/BaseIT.java  |  18 ++-
 .../org/apache/unomi/itests/ProgressListener.java  |  86 ++++++++++++-
 .../org/apache/unomi/itests/tools/LogChecker.java  | 133 ++++++++++++++++++---
 .../common/security/IPValidationUtils.java         |  18 ++-
 4 files changed, 232 insertions(+), 23 deletions(-)

diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java 
b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index 98bdbfd60..234ccace6 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -209,8 +209,6 @@ public abstract class BaseIT extends KarafTestSupport {
      */
     protected void checkSearchEngine() {
         searchEngine = System.getProperty(SEARCH_ENGINE_PROPERTY, 
SEARCH_ENGINE_ELASTICSEARCH);
-        System.out.println("Check search engine: " + searchEngine);
-        
         // Fix elasticsearch-maven-plugin default_template issue before any 
test setup
         // The plugin creates a default_template with very high priority that 
overrides all user templates
         // This must be done very early, before Unomi starts or any migration 
runs
@@ -390,20 +388,21 @@ public abstract class BaseIT extends KarafTestSupport {
                 String summary = result.getSummary();
                 String testInfo = currentTestName != null ? "Test: " + 
currentTestName + "\n" : "";
                 
-                // Log to console and logger
+                // Use System.err/out to avoid creating logs that would be 
captured by InMemoryLogAppender
+                // This prevents a feedback loop where log checking creates 
more logs to check
                 System.err.println("\n=== UNEXPECTED LOG ISSUES DETECTED ===");
                 System.err.println(testInfo + summary);
                 
System.err.println("=======================================\n");
                 
-                LOGGER.warn("Unexpected log issues detected in test {}:\n{}", 
currentTestName, summary);
-                
                 // Add to JUnit test output by printing to System.out 
(captured by JUnit)
                 System.out.println("\n=== SERVER-SIDE LOG ISSUES ===");
                 System.out.println(testInfo + summary);
                 System.out.println("===============================\n");
             }
         } catch (Exception e) {
-            LOGGER.error("Error checking logs", e);
+            // Use System.err to avoid creating logs that would be captured by 
InMemoryLogAppender
+            System.err.println("LogChecker: Error checking logs: " + 
e.getMessage());
+            e.printStackTrace(System.err);
         }
     }
     
@@ -640,6 +639,13 @@ public abstract class BaseIT extends KarafTestSupport {
             
karafOptions.add(editConfigurationFilePut("etc/org.ops4j.pax.logging.cfg", 
"log4j2.logger.customLogging.level", customLoggingParts[1]));
         }
 
+        // Suppress DEBUG logs from PaxExam framework (reduce noise in test 
output)
+        // These logs appear during test setup and are not useful for most 
debugging
+        
karafOptions.add(editConfigurationFilePut("etc/org.ops4j.pax.logging.cfg", 
"log4j2.logger.paxExam.name", "org.ops4j.pax.exam"));
+        
karafOptions.add(editConfigurationFilePut("etc/org.ops4j.pax.logging.cfg", 
"log4j2.logger.paxExam.level", "WARN"));
+        
karafOptions.add(editConfigurationFilePut("etc/org.ops4j.pax.logging.cfg", 
"log4j2.logger.paxStore.name", "org.ops4j.store"));
+        
karafOptions.add(editConfigurationFilePut("etc/org.ops4j.pax.logging.cfg", 
"log4j2.logger.paxStore.level", "WARN"));
+
         // Enable debug logging for Karaf Resolver to diagnose bundle refresh 
issues (default: disabled)
         boolean enableResolverDebug = 
Boolean.parseBoolean(System.getProperty(RESOLVER_DEBUG_PROPERTY, "false"));
         if (enableResolverDebug) {
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java 
b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
index 3e3b58d11..49987a480 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
@@ -209,6 +209,13 @@ public class ProgressListener extends RunListener {
 
         // Print the bottom border
         System.out.println(colorize(bottomBorder, CYAN));
+        
+        // Display search engine information once at the start
+        String searchEngine = System.getProperty("unomi.search.engine", 
"elasticsearch");
+        String searchEngineDisplay = capitalizeSearchEngine(searchEngine);
+        System.out.println();
+        System.out.println(colorize("Using search engine: " + 
searchEngineDisplay, CYAN));
+        System.out.println();
     }
 
     /**
@@ -219,6 +226,12 @@ public class ProgressListener extends RunListener {
     @Override
     public void testStarted(Description description) {
         startTestTime = System.currentTimeMillis();
+        // Print test start boundary with test name
+        String testName = extractTestName(description);
+        System.out.println(); // Blank line before test
+        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
+        System.out.println(colorize("▶ START: " + testName, GREEN));
+        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
     }
 
     /**
@@ -236,7 +249,15 @@ public class ProgressListener extends RunListener {
             // Remove the smallest time, keeping only the top 5 longest
             slowTests.poll();
         }
+        // Print test end boundary
+        String testName = extractTestName(description);
+        String durationStr = formatTime(testDuration);
+        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
+        System.out.println(colorize("✓ END: " + testName + " (Duration: " + 
durationStr + ")", GREEN));
+        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
+        System.out.println(); // Blank line before progress bar
         displayProgress();
+        System.out.println(); // Blank line after progress bar
     }
 
     /**
@@ -248,8 +269,13 @@ public class ProgressListener extends RunListener {
     public void testFailure(Failure failure) {
         successfulTests.decrementAndGet(); // Remove the previous success 
count for this test.
         failedTests.incrementAndGet();
-        System.out.println(colorize("Test failed: " + 
failure.getDescription(), RED));
+        String testName = extractTestName(failure.getDescription());
+        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 RED));
+        System.out.println(colorize("✗ FAILED: " + testName, RED));
+        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 RED));
+        System.out.println(); // Blank line before progress bar
         displayProgress();
+        System.out.println(); // Blank line after progress bar
     }
 
     /**
@@ -298,6 +324,56 @@ public class ProgressListener extends RunListener {
 
     }
 
+    /**
+     * Capitalizes the search engine name for display.
+     * Converts "opensearch" to "OpenSearch" and "elasticsearch" to 
"Elasticsearch".
+     *
+     * @param searchEngine the search engine name (lowercase)
+     * @return the capitalized search engine name
+     */
+    private String capitalizeSearchEngine(String searchEngine) {
+        if (searchEngine == null || searchEngine.isEmpty()) {
+            return searchEngine;
+        }
+        // Handle special case for "opensearch" -> "OpenSearch"
+        if ("opensearch".equalsIgnoreCase(searchEngine)) {
+            return "OpenSearch";
+        }
+        // Handle "elasticsearch" -> "Elasticsearch"
+        if ("elasticsearch".equalsIgnoreCase(searchEngine)) {
+            return "Elasticsearch";
+        }
+        // Default: capitalize first letter
+        return searchEngine.substring(0, 1).toUpperCase() + 
searchEngine.substring(1);
+    }
+
+    /**
+     * Extracts a clean test name from the test description.
+     * Formats it as "ClassName: methodName" for better readability.
+     *
+     * @param description the test description
+     * @return a formatted test name string
+     */
+    private String extractTestName(Description description) {
+        String displayName = description.getDisplayName();
+        // The display name is typically in format "methodName(ClassName)"
+        // Extract class name and method name
+        if (displayName.contains("(") && displayName.contains(")")) {
+            int methodEnd = displayName.indexOf('(');
+            int classStart = methodEnd + 1;
+            int classEnd = displayName.indexOf(')');
+            if (methodEnd > 0 && classEnd > classStart) {
+                String methodName = displayName.substring(0, methodEnd);
+                String className = displayName.substring(classStart, classEnd);
+                // Extract simple class name (last part after dot)
+                int lastDot = className.lastIndexOf('.');
+                String simpleClassName = (lastDot >= 0) ? 
className.substring(lastDot + 1) : className;
+                return simpleClassName + ": " + methodName;
+            }
+        }
+        return displayName;
+    }
+
     /**
      * Escapes special characters for CSV compatibility.
      *
@@ -329,9 +405,14 @@ public class ProgressListener extends RunListener {
         String progressBar = generateProgressBar(((double) completed / 
totalTests) * 100);
         String humanReadableTime = formatTime(estimatedRemainingTime);
 
-        System.out.printf("[%s] %sProgress: %s%.2f%%%s (%d/%d tests). 
Estimated time remaining: %s%s%s. " +
+        // Add visual separator and make progress bar more prominent
+        String separator = 
colorize("════════════════════════════════════════════════════════════════════════════════",
 CYAN);
+        System.out.println(separator);
+        System.out.printf("%s[%s]%s %sProgress: %s%.2f%%%s (%d/%d tests). 
Estimated time remaining: %s%s%s. " +
                         "Successful: %s%d%s, Failed: %s%d%s%n",
+                ansiSupported ? CYAN : "",
                 progressBar,
+                ansiSupported ? RESET : "",
                 ansiSupported ? BLUE : "",
                 ansiSupported ? GREEN : "",
                 ((double) completed / totalTests) * 100,
@@ -347,6 +428,7 @@ public class ProgressListener extends RunListener {
                 ansiSupported ? RED : "",
                 failedTests.get(),
                 ansiSupported ? RESET : "");
+        System.out.println(separator);
 
         if (completed % Math.max(1, totalTests / 10) == 0 && completed < 
totalTests) {
             String quote = QUOTES[completed % QUOTES.length];
diff --git a/itests/src/test/java/org/apache/unomi/itests/tools/LogChecker.java 
b/itests/src/test/java/org/apache/unomi/itests/tools/LogChecker.java
index bb3f528b2..50bdacad8 100644
--- a/itests/src/test/java/org/apache/unomi/itests/tools/LogChecker.java
+++ b/itests/src/test/java/org/apache/unomi/itests/tools/LogChecker.java
@@ -17,8 +17,6 @@
 package org.apache.unomi.itests.tools;
 
 import org.apache.unomi.extensions.log4j.InMemoryLogAppender;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.time.Instant;
 import java.time.ZoneId;
@@ -32,10 +30,10 @@ import java.util.regex.Pattern;
  */
 public class LogChecker {
     
-    private static final Logger LOGGER = 
LoggerFactory.getLogger(LogChecker.class);
-    
     private int checkpointIndex = 0;
     private final Set<Pattern> ignoredPatterns;
+    private final Map<Pattern, java.util.regex.Matcher> matcherCache;
+    private final List<String> literalPatterns; // Fast path for literal 
string patterns (case-insensitive)
     private final int errorContextLinesBefore;
     private final int errorContextLinesAfter;
     private final int warningContextLinesBefore;
@@ -219,8 +217,13 @@ public class LogChecker {
         private void appendErrorsSummary(StringBuilder sb) {
             if (!errors.isEmpty()) {
                 sb.append(String.format("Found %d error(s):", errors.size()));
-                for (LogEntry error : errors) {
-                    sb.append("\n").append(error.getFullContext());
+                // Limit to first 50 errors to avoid extremely long strings 
that slow down regex matching
+                int maxErrors = Math.min(50, errors.size());
+                for (int i = 0; i < maxErrors; i++) {
+                    sb.append("\n").append(errors.get(i).getFullContext());
+                }
+                if (errors.size() > maxErrors) {
+                    sb.append(String.format("\n... and %d more error(s) 
(truncated)", errors.size() - maxErrors));
                 }
             }
         }
@@ -228,8 +231,13 @@ public class LogChecker {
         private void appendWarningsSummary(StringBuilder sb) {
             if (!warnings.isEmpty()) {
                 sb.append(String.format("\nFound %d warning(s):", 
warnings.size()));
-                for (LogEntry warning : warnings) {
-                    sb.append("\n").append(warning.getFullContext());
+                // Limit to first 50 warnings to avoid extremely long strings 
that slow down regex matching
+                int maxWarnings = Math.min(50, warnings.size());
+                for (int i = 0; i < maxWarnings; i++) {
+                    sb.append("\n").append(warnings.get(i).getFullContext());
+                }
+                if (warnings.size() > maxWarnings) {
+                    sb.append(String.format("\n... and %d more warning(s) 
(truncated)", warnings.size() - maxWarnings));
                 }
             }
         }
@@ -254,6 +262,8 @@ public class LogChecker {
     public LogChecker(int errorContextLinesBefore, int errorContextLinesAfter, 
                       int warningContextLinesBefore, int 
warningContextLinesAfter) {
         this.ignoredPatterns = new HashSet<>();
+        this.matcherCache = new HashMap<>();
+        this.literalPatterns = new ArrayList<>();
         this.errorContextLinesBefore = errorContextLinesBefore;
         this.errorContextLinesAfter = errorContextLinesAfter;
         this.warningContextLinesBefore = warningContextLinesBefore;
@@ -263,10 +273,50 @@ public class LogChecker {
     
     /**
      * Add a pattern to ignore (expected errors/warnings)
-     * @param pattern Regex pattern to match against log messages
+     * @param pattern Regex pattern to match against log messages, or literal 
string (no regex special chars)
      */
     public void addIgnoredPattern(String pattern) {
-        ignoredPatterns.add(Pattern.compile(pattern, 
Pattern.CASE_INSENSITIVE));
+        // Check if pattern is a literal string (no regex special characters 
except . which we allow)
+        if (isLiteralPattern(pattern)) {
+            // Use fast string contains() for literal patterns
+            literalPatterns.add(pattern.toLowerCase());
+        } else {
+            // Optimize regex pattern: use possessive quantifiers to prevent 
backtracking
+            String optimizedPattern = optimizePattern(pattern);
+            Pattern compiledPattern = Pattern.compile(optimizedPattern, 
Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
+            ignoredPatterns.add(compiledPattern);
+            // Pre-create matcher for this pattern to avoid allocation during 
matching
+            matcherCache.put(compiledPattern, compiledPattern.matcher(""));
+        }
+    }
+    
+    /**
+     * Check if a pattern is a literal string (no regex special characters)
+     */
+    private boolean isLiteralPattern(String pattern) {
+        // Check for regex special characters (excluding . which is common in 
literal strings)
+        // We consider it literal if it only contains alphanumeric, spaces, 
and common punctuation
+        for (int i = 0; i < pattern.length(); i++) {
+            char c = pattern.charAt(i);
+            if (c == '*' || c == '+' || c == '?' || c == '^' || c == '$' || 
+                c == '[' || c == ']' || c == '{' || c == '}' || c == '(' || c 
== ')' ||
+                c == '|' || c == '\\') {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * Optimize regex pattern to prevent catastrophic backtracking
+     * - Replace .* with .*+ (possessive quantifier) to prevent backtracking
+     * - This makes greedy matching non-backtracking, which is safe for 
"contains" matching
+     */
+    private String optimizePattern(String pattern) {
+        // Replace .* with .*+ (possessive quantifier) to prevent backtracking
+        // This is safe because we're using find(), not matches(), so we just 
need to find the pattern anywhere
+        // Possessive quantifiers prevent backtracking which can cause 
exponential time complexity
+        return pattern.replace(".*", ".*+");
     }
     
     /**
@@ -312,7 +362,27 @@ public class LogChecker {
         // RequestValidatorInterceptor warnings (expected when testing request 
size limits)
         addIgnoredPattern("RequestValidatorInterceptor.*Incoming POST request 
blocked because exceeding maximum bytes size");
         addIgnoredPattern("RequestValidatorInterceptor.*has thrown exception, 
unwinding now");
+        addIgnoredPattern("RequestValidatorInterceptor.*Interceptor for.*has 
thrown exception, unwinding now");
+        
addIgnoredPattern("org\\.apache\\.unomi\\.rest\\.validation\\.request\\.RequestValidatorInterceptor.*has
 thrown exception");
         addIgnoredPattern("InvalidRequestException.*Incoming POST request 
blocked because exceeding maximum bytes size");
+        addIgnoredPattern(".*Incoming POST request blocked because exceeding 
maximum bytes size allowed on: /cxs/eventcollector");
+        // More general patterns that match regardless of logger name format
+        addIgnoredPattern(".*has thrown exception, unwinding now.*Incoming 
POST request blocked because exceeding maximum bytes size");
+        addIgnoredPattern(".*Interceptor for.*has thrown exception, unwinding 
now.*exceeding maximum bytes size");
+        addIgnoredPattern(".*exceeding maximum bytes size allowed on: 
/cxs/eventcollector.*limit: 200000");
+        addIgnoredPattern(".*exceeding maximum bytes size.*limit: 
200000.*request size: 210940");
+        // Match the exact error message format from the exception (with 
escaped parentheses)
+        addIgnoredPattern(".*Incoming POST request blocked because exceeding 
maximum bytes size allowed on: /cxs/eventcollector \\(limit: 200000, request 
size: 210940\\)");
+        // Very general pattern that matches the key error message parts
+        addIgnoredPattern(".*blocked because exceeding maximum bytes 
size.*/cxs/eventcollector");
+        // Simple pattern matching just the key error message
+        addIgnoredPattern(".*exceeding maximum bytes size allowed on: 
/cxs/eventcollector");
+        // Very simple patterns that match the core error message parts
+        addIgnoredPattern(".*exceeding maximum bytes 
size.*/cxs/eventcollector.*limit: 200000");
+        addIgnoredPattern(".*blocked because exceeding maximum bytes size");
+        // Match if we see both key parts anywhere in the log entry
+        addIgnoredPattern(".*has thrown exception.*exceeding maximum bytes 
size");
+        addIgnoredPattern(".*TestEndPoint.*exceeding maximum bytes size");
         
         // Test-related schema errors (expected in JSONSchemaIT and other 
tests)
         addIgnoredPattern("Schema not found for event type: dummy");
@@ -322,11 +392,16 @@ public class LogChecker {
         addIgnoredPattern("Couldn't find persona");
         addIgnoredPattern("Unable to save schema");
         addIgnoredPattern("SchemaServiceImpl.*Couldn't find schema");
+        addIgnoredPattern("JsonSchemaFactory.*Failed to load json schema");
         addIgnoredPattern("Failed to load json schema!");
         addIgnoredPattern("Couldn't find schema.*vendor.test.com");
         addIgnoredPattern("JsonSchemaException.*Couldn't find schema");
         addIgnoredPattern("InvocationTargetException.*JsonSchemaException");
+        addIgnoredPattern("InvocationTargetException.*Couldn't find schema");
+        
addIgnoredPattern(".*InvocationTargetException.*JsonSchemaException.*Couldn't 
find schema");
         addIgnoredPattern("IOException.*Couldn't find schema");
+        addIgnoredPattern("ValidatorTypeCode.*InvocationTargetException");
+        
addIgnoredPattern("ValidatorTypeCode.*Error:.*InvocationTargetException");
         // Schema validation warnings (expected during schema validation tests)
         addIgnoredPattern("SchemaServiceImpl.*Schema validation found.*errors 
while validating");
         addIgnoredPattern("SchemaServiceImpl.*Validation error.*does not match 
the regex pattern");
@@ -355,8 +430,14 @@ public class LogChecker {
         addIgnoredPattern("Error while executing in class 
loader.*scrollIdentifier=dummyScrollId");
         addIgnoredPattern("Error while executing in class loader.*Error 
loading itemType");
         addIgnoredPattern("Error while executing in class loader.*Error 
continuing scrolling query");
+        addIgnoredPattern("OpenSearchPersistenceServiceImpl.*Error while 
executing in class loader");
+        addIgnoredPattern("OpenSearchPersistenceServiceImpl\\.\\d+.*Error 
while executing in class loader");
+        addIgnoredPattern("OpenSearchPersistenceServiceImpl\\.\\d+:\\d+.*Error 
while executing in class loader");
+        addIgnoredPattern("ElasticSearchPersistenceServiceImpl.*Error while 
executing in class loader");
         addIgnoredPattern("Error continuing scrolling 
query.*scrollIdentifier=dummyScrollId");
+        addIgnoredPattern(".*Error continuing scrolling query for 
itemType=org\\.apache\\.unomi\\.api\\.Profile.*scrollIdentifier=dummyScrollId");
         addIgnoredPattern("Error continuing scrolling query for 
itemType.*scrollIdentifier=dummyScrollId");
+        addIgnoredPattern("java\\.lang\\.Exception.*Error continuing scrolling 
query.*scrollIdentifier=dummyScrollId");
         addIgnoredPattern("Cannot parse scroll id");
         addIgnoredPattern("Request failed:.*illegal_argument_exception.*Cannot 
parse scroll id");
         
@@ -408,7 +489,9 @@ public class LogChecker {
             }
             return result;
         } catch (Exception e) {
-            LOGGER.warn("Failed to get events from InMemoryLogAppender", e);
+            // Use System.err to avoid creating logs that would be captured by 
InMemoryLogAppender
+            System.err.println("LogChecker: Failed to get events from 
InMemoryLogAppender: " + e.getMessage());
+            e.printStackTrace(System.err);
             return Collections.emptyList();
         }
     }
@@ -537,7 +620,9 @@ public class LogChecker {
             
             return new EventData(timestamp, level, thread, logger, message, 
throwable);
         } catch (Exception e) {
-            LOGGER.warn("Failed to extract data from log event", e);
+            // Use System.err to avoid creating logs that would be captured by 
InMemoryLogAppender
+            System.err.println("LogChecker: Failed to extract data from log 
event: " + e.getMessage());
+            e.printStackTrace(System.err);
             return null;
         }
     }
@@ -608,11 +693,31 @@ public class LogChecker {
                 .append(' ')
                 .append(entry.getFullMessage() != null ? 
entry.getFullMessage() : "");
         String candidate = candidateBuilder.toString();
-        for (Pattern pattern : ignoredPatterns) {
-            if (pattern.matcher(candidate).find()) {
+        String candidateLower = candidate.toLowerCase(); // For 
case-insensitive literal matching
+        
+        // Fast path: check literal patterns first (much faster than regex)
+        for (String literal : literalPatterns) {
+            if (candidateLower.contains(literal)) {
                 return false;
             }
         }
+        
+        // Slower path: check regex patterns (reuse cached matchers)
+        for (Pattern pattern : ignoredPatterns) {
+            java.util.regex.Matcher matcher = matcherCache.get(pattern);
+            if (matcher != null) {
+                // Reset the matcher with the new candidate string (reuses the 
Matcher object)
+                matcher.reset(candidate);
+                if (matcher.find()) {
+                    return false;
+                }
+            } else {
+                // Fallback if matcher not in cache (shouldn't happen, but be 
safe)
+                if (pattern.matcher(candidate).find()) {
+                    return false;
+                }
+            }
+        }
         return true;
     }
     
diff --git 
a/services-common/src/main/java/org/apache/unomi/services/common/security/IPValidationUtils.java
 
b/services-common/src/main/java/org/apache/unomi/services/common/security/IPValidationUtils.java
index 9a5812741..160f058a7 100644
--- 
a/services-common/src/main/java/org/apache/unomi/services/common/security/IPValidationUtils.java
+++ 
b/services-common/src/main/java/org/apache/unomi/services/common/security/IPValidationUtils.java
@@ -34,6 +34,15 @@ public class IPValidationUtils {
     
     private static final Logger LOGGER = 
LoggerFactory.getLogger(IPValidationUtils.class);
     
+    /**
+     * System property to control stack trace logging in error messages.
+     * When set to "true", stack traces are suppressed (useful for unit tests).
+     * Default is "false" (stack traces are included).
+     */
+    private static final String SUPPRESS_STACK_TRACES_PROPERTY = 
"org.apache.unomi.ipvalidation.suppress.stacktraces";
+    private static final boolean SUPPRESS_STACK_TRACES = Boolean.parseBoolean(
+        System.getProperty(SUPPRESS_STACK_TRACES_PROPERTY, "false"));
+    
     /**
      * Check if a source IP address is authorized against a set of allowed IP 
addresses.
      * 
@@ -76,7 +85,14 @@ public class IPValidationUtils {
             }
             return false;
         } catch (Exception e) {
-            LOGGER.error("Invalid source IP address: {}", sourceIP, e);
+            // If stack trace suppression is enabled (typically for unit 
tests),
+            // log only the error message without stack trace to reduce noise.
+            // Otherwise, log with full stack trace for debugging.
+            if (SUPPRESS_STACK_TRACES) {
+                LOGGER.error("Invalid source IP address: {} - {}", sourceIP, 
e.getMessage());
+            } else {
+                LOGGER.error("Invalid source IP address: {}", sourceIP, e);
+            }
             return false;
         }
     }

Reply via email to