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;
}
}