From 5405b3ac9d6645417290169cf392e188a66af20b Mon Sep 17 00:00:00 2001 From: david Date: Sat, 4 Apr 2026 15:59:43 +0200 Subject: [PATCH 1/2] Added error ignoring - Added exact error type matching - Added pattern based message matching - Added combined exact error type and pattern based message matching --- .../java/dev/faststats/core/ErrorTracker.java | 81 +++++++++++++++++++ .../faststats/core/SimpleErrorTracker.java | 34 ++++++++ gradle.properties | 2 +- 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/dev/faststats/core/ErrorTracker.java b/core/src/main/java/dev/faststats/core/ErrorTracker.java index 478b4d8..d9e7280 100644 --- a/core/src/main/java/dev/faststats/core/ErrorTracker.java +++ b/core/src/main/java/dev/faststats/core/ErrorTracker.java @@ -1,10 +1,12 @@ package dev.faststats.core; +import org.intellij.lang.annotations.RegExp; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; import java.util.Optional; import java.util.function.BiConsumer; +import java.util.regex.Pattern; /** * An error tracker. @@ -96,6 +98,85 @@ static ErrorTracker contextUnaware() { @Contract(mutates = "this") void trackError(Throwable error, boolean handled); + /** + * Adds an error type that will not be reported to FastStats. + *

+ * Matching is done exactly. If, for example {@link LinkageError} was ignored, + * {@link NoClassDefFoundError} would still be reported, even though it extends {@link LinkageError} + * + * @param type the error type + * @return the error tracker + * @since 0.21.0 + */ + @Contract(value = "_ -> this", mutates = "this") + ErrorTracker ignoreErrorType(Class type); + + /** + * Adds a pattern that will be matched against all error messages. + *

+ * If an error's message matches the given pattern, it will not be reported to FastStats. + *

{@code
+     * // Exact match
+     * tracker.ignoreError(Pattern.compile("No space left on device"));
+     *
+     * // Regex match
+     * tracker.ignoreError(Pattern.compile("No serializer for: class .*"));
+     * }
+ * + * @param pattern the regex pattern to match against error messages + * @return the error tracker + * @since 0.21.0 + */ + @Contract(value = "_ -> this", mutates = "this") + ErrorTracker ignoreError(Pattern pattern); + + /** + * Adds a pattern that will be matched against all error messages. + *

+ * If an error's message matches the given pattern, it will not be reported to FastStats. + * + * @param pattern the regex pattern string to match against error messages + * @return the error tracker + * @see #ignoreError(Pattern) + * @since 0.21.0 + */ + @Contract(value = "_ -> this", mutates = "this") + default ErrorTracker ignoreError(@RegExp final String pattern) { + return ignoreError(Pattern.compile(pattern)); + } + + /** + * Adds an error type combined with a message pattern that will not be reported to FastStats. + *

+ * An error is ignored only if its class matches the given type exactly and its message matches the given pattern. + *

{@code
+     * tracker.ignoreError(IOException.class, Pattern.compile("No space left on device"));
+     * }
+ * + * @param type the error type + * @param pattern the regex pattern to match against error messages + * @return the error tracker + * @since 0.21.0 + */ + @Contract(value = "_, _ -> this", mutates = "this") + ErrorTracker ignoreError(Class type, Pattern pattern); + + /** + * Adds an error type combined with a message pattern that will not be reported to FastStats. + *

+ * An error is ignored only if its class matches the given type exactly and its message matches the given pattern. + * + * @param type the error type + * @param pattern the regex pattern string to match against error messages + * @return the error tracker + * @see #ignoreError(Class, Pattern) + * @since 0.21.0 + */ + @Contract(value = "_, _ -> this", mutates = "this") + default ErrorTracker ignoreError(final Class type, @RegExp final String pattern) { + return ignoreError(type, Pattern.compile(pattern)); + } + /** * Attaches an error context to the tracker. *

diff --git a/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java b/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java index e10165d..5bc374c 100644 --- a/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java +++ b/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java @@ -7,13 +7,21 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; final class SimpleErrorTracker implements ErrorTracker { private final Map collected = new ConcurrentHashMap<>(); private final Map reports = new ConcurrentHashMap<>(); + private final Map, Set> ignoredTypedPatterns = new ConcurrentHashMap<>(); + private final Set> ignoredTypes = new CopyOnWriteArraySet<>(); + private final Set ignoredPatterns = new CopyOnWriteArraySet<>(); + private volatile @Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent = null; private volatile @Nullable UncaughtExceptionHandler originalHandler = null; @@ -35,6 +43,14 @@ public void trackError(final String message, final boolean handled) { @Override public void trackError(final Throwable error, final boolean handled) { try { + if (ignoredTypes.contains(error.getClass())) return; + + final var message = error.getMessage() != null ? error.getMessage() : ""; + if (ignoredPatterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find)) return; + + final var typedPatterns = ignoredTypedPatterns.get(error.getClass()); + if (typedPatterns != null && typedPatterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find)) return; + final var compiled = ErrorHelper.compile(error, null, handled); final var hashed = MurmurHash3.hash(compiled); if (collected.compute(hashed, (k, v) -> { @@ -45,6 +61,24 @@ public void trackError(final Throwable error, final boolean handled) { } } + @Override + public ErrorTracker ignoreErrorType(final Class type) { + ignoredTypes.add(type); + return this; + } + + @Override + public ErrorTracker ignoreError(final Pattern pattern) { + ignoredPatterns.add(pattern); + return this; + } + + @Override + public ErrorTracker ignoreError(final Class type, final Pattern pattern) { + ignoredTypedPatterns.computeIfAbsent(type, k -> new CopyOnWriteArraySet<>()).add(pattern); + return this; + } + public JsonArray getData(final String buildId) { final var report = new JsonArray(reports.size()); diff --git a/gradle.properties b/gradle.properties index d8047b2..9af4b1f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.20.1 +version=0.21.0 From fd698bc599775eb029e6fb5deb769102a996fcd7 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 4 Apr 2026 16:17:59 +0200 Subject: [PATCH 2/2] Improve error ignored check --- .../faststats/core/SimpleErrorTracker.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java b/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java index 5bc374c..f30eb0d 100644 --- a/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java +++ b/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java @@ -5,6 +5,8 @@ import org.jspecify.annotations.Nullable; import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Collections; +import java.util.IdentityHashMap; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -43,14 +45,7 @@ public void trackError(final String message, final boolean handled) { @Override public void trackError(final Throwable error, final boolean handled) { try { - if (ignoredTypes.contains(error.getClass())) return; - - final var message = error.getMessage() != null ? error.getMessage() : ""; - if (ignoredPatterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find)) return; - - final var typedPatterns = ignoredTypedPatterns.get(error.getClass()); - if (typedPatterns != null && typedPatterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find)) return; - + if (isIgnored(error, Collections.newSetFromMap(new IdentityHashMap<>()))) return; final var compiled = ErrorHelper.compile(error, null, handled); final var hashed = MurmurHash3.hash(compiled); if (collected.compute(hashed, (k, v) -> { @@ -61,6 +56,21 @@ public void trackError(final Throwable error, final boolean handled) { } } + private boolean isIgnored(@Nullable final Throwable error, final Set visited) { + if (error == null || !visited.add(error)) return false; + + if (ignoredTypes.contains(error.getClass())) return true; + + final var message = error.getMessage() != null ? error.getMessage() : ""; + if (ignoredPatterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find)) return true; + + final var patterns = ignoredTypedPatterns.get(error.getClass()); + if (patterns != null && patterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find)) + return true; + + return isIgnored(error.getCause(), visited); + } + @Override public ErrorTracker ignoreErrorType(final Class type) { ignoredTypes.add(type);