Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions core/src/main/java/dev/faststats/core/ErrorTracker.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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.
* <p>
* 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<? extends Throwable> type);

/**
* Adds a pattern that will be matched against all error messages.
* <p>
* If an error's message matches the given pattern, it will not be reported to FastStats.
* <pre>{@code
* // Exact match
* tracker.ignoreError(Pattern.compile("No space left on device"));
*
* // Regex match
* tracker.ignoreError(Pattern.compile("No serializer for: class .*"));
* }</pre>
*
* @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.
* <p>
* 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.
* <p>
* An error is ignored only if its class matches the given type exactly and its message matches the given pattern.
* <pre>{@code
* tracker.ignoreError(IOException.class, Pattern.compile("No space left on device"));
* }</pre>
*
* @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<? extends Throwable> type, Pattern pattern);

/**
* Adds an error type combined with a message pattern that will not be reported to FastStats.
* <p>
* 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<? extends Throwable> type, @RegExp final String pattern) {
return ignoreError(type, Pattern.compile(pattern));
}

/**
* Attaches an error context to the tracker.
* <p>
Expand Down
44 changes: 44 additions & 0 deletions core/src/main/java/dev/faststats/core/SimpleErrorTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Integer> collected = new ConcurrentHashMap<>();
private final Map<String, JsonObject> reports = new ConcurrentHashMap<>();

private final Map<Class<? extends Throwable>, Set<Pattern>> ignoredTypedPatterns = new ConcurrentHashMap<>();
private final Set<Class<? extends Throwable>> ignoredTypes = new CopyOnWriteArraySet<>();
private final Set<Pattern> ignoredPatterns = new CopyOnWriteArraySet<>();

private volatile @Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent = null;
private volatile @Nullable UncaughtExceptionHandler originalHandler = null;

Expand All @@ -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) -> {
Expand All @@ -45,6 +56,39 @@ public void trackError(final Throwable error, final boolean handled) {
}
}

private boolean isIgnored(@Nullable final Throwable error, final Set<Throwable> 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<? extends Throwable> type) {
ignoredTypes.add(type);
return this;
}

@Override
public ErrorTracker ignoreError(final Pattern pattern) {
ignoredPatterns.add(pattern);
return this;
}

@Override
public ErrorTracker ignoreError(final Class<? extends Throwable> 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());

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.20.1
version=0.21.0