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..f30eb0d 100644 --- a/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java +++ b/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java @@ -5,15 +5,25 @@ 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; 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 +45,7 @@ public void trackError(final String message, final boolean handled) { @Override public void trackError(final Throwable error, final boolean handled) { try { + 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) -> { @@ -45,6 +56,39 @@ 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); + 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