Skip to content

Commit f7d9ddd

Browse files
authored
Merge pull request #132 from faststats-dev/feat/error-ignoring
Added error ignoring
2 parents af88ff2 + fd698bc commit f7d9ddd

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed

core/src/main/java/dev/faststats/core/ErrorTracker.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package dev.faststats.core;
22

3+
import org.intellij.lang.annotations.RegExp;
34
import org.jetbrains.annotations.Contract;
45
import org.jspecify.annotations.Nullable;
56

67
import java.util.Optional;
78
import java.util.function.BiConsumer;
9+
import java.util.regex.Pattern;
810

911
/**
1012
* An error tracker.
@@ -96,6 +98,85 @@ static ErrorTracker contextUnaware() {
9698
@Contract(mutates = "this")
9799
void trackError(Throwable error, boolean handled);
98100

101+
/**
102+
* Adds an error type that will not be reported to FastStats.
103+
* <p>
104+
* Matching is done exactly. If, for example {@link LinkageError} was ignored,
105+
* {@link NoClassDefFoundError} would still be reported, even though it extends {@link LinkageError}
106+
*
107+
* @param type the error type
108+
* @return the error tracker
109+
* @since 0.21.0
110+
*/
111+
@Contract(value = "_ -> this", mutates = "this")
112+
ErrorTracker ignoreErrorType(Class<? extends Throwable> type);
113+
114+
/**
115+
* Adds a pattern that will be matched against all error messages.
116+
* <p>
117+
* If an error's message matches the given pattern, it will not be reported to FastStats.
118+
* <pre>{@code
119+
* // Exact match
120+
* tracker.ignoreError(Pattern.compile("No space left on device"));
121+
*
122+
* // Regex match
123+
* tracker.ignoreError(Pattern.compile("No serializer for: class .*"));
124+
* }</pre>
125+
*
126+
* @param pattern the regex pattern to match against error messages
127+
* @return the error tracker
128+
* @since 0.21.0
129+
*/
130+
@Contract(value = "_ -> this", mutates = "this")
131+
ErrorTracker ignoreError(Pattern pattern);
132+
133+
/**
134+
* Adds a pattern that will be matched against all error messages.
135+
* <p>
136+
* If an error's message matches the given pattern, it will not be reported to FastStats.
137+
*
138+
* @param pattern the regex pattern string to match against error messages
139+
* @return the error tracker
140+
* @see #ignoreError(Pattern)
141+
* @since 0.21.0
142+
*/
143+
@Contract(value = "_ -> this", mutates = "this")
144+
default ErrorTracker ignoreError(@RegExp final String pattern) {
145+
return ignoreError(Pattern.compile(pattern));
146+
}
147+
148+
/**
149+
* Adds an error type combined with a message pattern that will not be reported to FastStats.
150+
* <p>
151+
* An error is ignored only if its class matches the given type exactly and its message matches the given pattern.
152+
* <pre>{@code
153+
* tracker.ignoreError(IOException.class, Pattern.compile("No space left on device"));
154+
* }</pre>
155+
*
156+
* @param type the error type
157+
* @param pattern the regex pattern to match against error messages
158+
* @return the error tracker
159+
* @since 0.21.0
160+
*/
161+
@Contract(value = "_, _ -> this", mutates = "this")
162+
ErrorTracker ignoreError(Class<? extends Throwable> type, Pattern pattern);
163+
164+
/**
165+
* Adds an error type combined with a message pattern that will not be reported to FastStats.
166+
* <p>
167+
* An error is ignored only if its class matches the given type exactly and its message matches the given pattern.
168+
*
169+
* @param type the error type
170+
* @param pattern the regex pattern string to match against error messages
171+
* @return the error tracker
172+
* @see #ignoreError(Class, Pattern)
173+
* @since 0.21.0
174+
*/
175+
@Contract(value = "_, _ -> this", mutates = "this")
176+
default ErrorTracker ignoreError(final Class<? extends Throwable> type, @RegExp final String pattern) {
177+
return ignoreError(type, Pattern.compile(pattern));
178+
}
179+
99180
/**
100181
* Attaches an error context to the tracker.
101182
* <p>

core/src/main/java/dev/faststats/core/SimpleErrorTracker.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,25 @@
55
import org.jspecify.annotations.Nullable;
66

77
import java.lang.Thread.UncaughtExceptionHandler;
8+
import java.util.Collections;
9+
import java.util.IdentityHashMap;
810
import java.util.Map;
911
import java.util.Optional;
12+
import java.util.Set;
1013
import java.util.concurrent.ConcurrentHashMap;
14+
import java.util.concurrent.CopyOnWriteArraySet;
1115
import java.util.function.BiConsumer;
16+
import java.util.regex.Matcher;
17+
import java.util.regex.Pattern;
1218

1319
final class SimpleErrorTracker implements ErrorTracker {
1420
private final Map<String, Integer> collected = new ConcurrentHashMap<>();
1521
private final Map<String, JsonObject> reports = new ConcurrentHashMap<>();
1622

23+
private final Map<Class<? extends Throwable>, Set<Pattern>> ignoredTypedPatterns = new ConcurrentHashMap<>();
24+
private final Set<Class<? extends Throwable>> ignoredTypes = new CopyOnWriteArraySet<>();
25+
private final Set<Pattern> ignoredPatterns = new CopyOnWriteArraySet<>();
26+
1727
private volatile @Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent = null;
1828
private volatile @Nullable UncaughtExceptionHandler originalHandler = null;
1929

@@ -35,6 +45,7 @@ public void trackError(final String message, final boolean handled) {
3545
@Override
3646
public void trackError(final Throwable error, final boolean handled) {
3747
try {
48+
if (isIgnored(error, Collections.newSetFromMap(new IdentityHashMap<>()))) return;
3849
final var compiled = ErrorHelper.compile(error, null, handled);
3950
final var hashed = MurmurHash3.hash(compiled);
4051
if (collected.compute(hashed, (k, v) -> {
@@ -45,6 +56,39 @@ public void trackError(final Throwable error, final boolean handled) {
4556
}
4657
}
4758

59+
private boolean isIgnored(@Nullable final Throwable error, final Set<Throwable> visited) {
60+
if (error == null || !visited.add(error)) return false;
61+
62+
if (ignoredTypes.contains(error.getClass())) return true;
63+
64+
final var message = error.getMessage() != null ? error.getMessage() : "";
65+
if (ignoredPatterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find)) return true;
66+
67+
final var patterns = ignoredTypedPatterns.get(error.getClass());
68+
if (patterns != null && patterns.stream().map(pattern -> pattern.matcher(message)).anyMatch(Matcher::find))
69+
return true;
70+
71+
return isIgnored(error.getCause(), visited);
72+
}
73+
74+
@Override
75+
public ErrorTracker ignoreErrorType(final Class<? extends Throwable> type) {
76+
ignoredTypes.add(type);
77+
return this;
78+
}
79+
80+
@Override
81+
public ErrorTracker ignoreError(final Pattern pattern) {
82+
ignoredPatterns.add(pattern);
83+
return this;
84+
}
85+
86+
@Override
87+
public ErrorTracker ignoreError(final Class<? extends Throwable> type, final Pattern pattern) {
88+
ignoredTypedPatterns.computeIfAbsent(type, k -> new CopyOnWriteArraySet<>()).add(pattern);
89+
return this;
90+
}
91+
4892
public JsonArray getData(final String buildId) {
4993
final var report = new JsonArray(reports.size());
5094

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=0.20.1
1+
version=0.21.0

0 commit comments

Comments
 (0)