diff --git a/core/src/main/java/dev/faststats/core/ErrorTracker.java b/core/src/main/java/dev/faststats/core/ErrorTracker.java
index 4cd62c2..06bea1c 100644
--- a/core/src/main/java/dev/faststats/core/ErrorTracker.java
+++ b/core/src/main/java/dev/faststats/core/ErrorTracker.java
@@ -1,12 +1,15 @@
package dev.faststats.core;
-import dev.faststats.core.concurrent.TrackingExecutors;
import dev.faststats.core.concurrent.TrackingBase;
+import dev.faststats.core.concurrent.TrackingExecutors;
import dev.faststats.core.concurrent.TrackingThreadFactory;
import dev.faststats.core.concurrent.TrackingThreadPoolExecutor;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+
/**
* An error tracker.
*
@@ -72,12 +75,34 @@ static ErrorTracker contextUnaware() {
/**
* Attaches an error context to the tracker.
+ *
+ * If the class loader is {@code null}, the tracker will track all errors.
*
* @param loader the class loader
* @since 0.10.0
*/
void attachErrorContext(@Nullable ClassLoader loader);
+ /**
+ * Sets the error event handler which will be called when an error is tracked automatically.
+ *
+ * The purpose of this handler is to allow custom error handling like logging.
+ *
+ * @param errorEvent the error event handler
+ * @since 0.11.0
+ */
+ @Contract(mutates = "this")
+ void setContextErrorHandler(@Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent);
+
+ /**
+ * Returns the error event handler which will be called when an error is tracked automatically.
+ *
+ * @return the error event handler
+ * @since 0.11.0
+ */
+ @Contract(pure = true)
+ Optional> getContextErrorHandler();
+
/**
* Returns the tracking base.
*
diff --git a/core/src/main/java/dev/faststats/core/Metrics.java b/core/src/main/java/dev/faststats/core/Metrics.java
index e0eb365..01eb2d6 100644
--- a/core/src/main/java/dev/faststats/core/Metrics.java
+++ b/core/src/main/java/dev/faststats/core/Metrics.java
@@ -61,6 +61,8 @@ public interface Metrics {
interface Factory {
/**
* Adds a chart to the metrics submission.
+ *
+ * If {@link Config#additionalMetrics()} is disabled, the chart will not be submitted.
*
* @param chart the chart to add
* @return the metrics factory
@@ -72,6 +74,8 @@ interface Factory {
/**
* Sets the error tracker for this metrics instance.
+ *
+ * If {@link Config#errorTracking()} is disabled, no errors will be submitted.
*
* @param tracker the error tracker
* @return the metrics factory
@@ -158,16 +162,34 @@ interface Config {
* Bypassing this setting may get your project banned from FastStats.
* Users have to be able to opt out from metrics submission.
*
- * @return true if metrics submission is enabled, false otherwise
+ * @return {@code true} if metrics submission is enabled, {@code false} otherwise
* @since 0.1.0
*/
@Contract(pure = true)
boolean enabled();
+ /**
+ * Whether error tracking is enabled across all metrics instances.
+ *
+ * @return {@code true} if error tracking is enabled, {@code false} otherwise
+ * @since 0.11.0
+ */
+ @Contract(pure = true)
+ boolean errorTracking();
+
+ /**
+ * Whether additional metrics are enabled across all metrics instances.
+ *
+ * @return {@code true} if additional metrics are enabled, {@code false} otherwise
+ * @since 0.11.0
+ */
+ @Contract(pure = true)
+ boolean additionalMetrics();
+
/**
* Whether debug logging is enabled across all metrics instances.
*
- * @return true if debug logging is enabled, false otherwise
+ * @return {@code true} if debug logging is enabled, {@code false} otherwise
* @since 0.1.0
*/
@Contract(pure = true)
diff --git a/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java b/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java
index bf4c437..f60e3e1 100644
--- a/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java
+++ b/core/src/main/java/dev/faststats/core/SimpleErrorTracker.java
@@ -12,7 +12,9 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
final class SimpleErrorTracker implements ErrorTracker {
private final int stackTraceLimit = Math.min(50, Integer.getInteger("faststats.stack-trace-limit", 15));
@@ -24,6 +26,8 @@ final class SimpleErrorTracker implements ErrorTracker {
private final TrackingThreadFactory threadFactory = new SimpleTrackingThreadFactory(this);
private final TrackingThreadPoolExecutor threadPoolExecutor = new SimpleTrackingThreadPoolExecutor(this);
+ private @Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent = null;
+
@Override
public void trackError(String message) {
trackError(new RuntimeException(message));
@@ -143,10 +147,21 @@ public void attachErrorContext(@Nullable ClassLoader loader) {
Thread.setDefaultUncaughtExceptionHandler((thread, error) -> {
if (handler != null) handler.uncaughtException(thread, error);
if (loader != null && !isSameLoader(loader, error)) return;
+ if (errorEvent != null) errorEvent.accept(loader, error);
trackError(error);
});
}
+ @Override
+ public void setContextErrorHandler(@Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent) {
+ this.errorEvent = errorEvent;
+ }
+
+ @Override
+ public Optional> getContextErrorHandler() {
+ return Optional.ofNullable(errorEvent);
+ }
+
@Override
public TrackingBase base() {
return base;
diff --git a/core/src/main/java/dev/faststats/core/SimpleMetrics.java b/core/src/main/java/dev/faststats/core/SimpleMetrics.java
index a713133..55762a7 100644
--- a/core/src/main/java/dev/faststats/core/SimpleMetrics.java
+++ b/core/src/main/java/dev/faststats/core/SimpleMetrics.java
@@ -30,6 +30,7 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiPredicate;
import java.util.zip.GZIPOutputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -62,11 +63,11 @@ public abstract class SimpleMetrics implements Metrics {
protected SimpleMetrics(Factory> factory, Path config) throws IllegalStateException {
if (factory.token == null) throw new IllegalStateException("Token must be specified");
- this.charts = Set.copyOf(factory.charts);
this.config = new Config(config);
+ this.charts = this.config.additionalMetrics ? Set.copyOf(factory.charts) : Set.of();
this.debug = factory.debug || Boolean.getBoolean("faststats.debug") || this.config.debug();
this.token = factory.token;
- this.tracker = factory.tracker;
+ this.tracker = this.config.errorTracking ? factory.tracker : null;
this.url = factory.url;
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
@@ -85,7 +86,7 @@ protected SimpleMetrics(Config config, Set> charts, @Token String token
throw new IllegalArgumentException("Invalid token '" + token + "', must match '" + Token.PATTERN + "'");
}
- this.charts = Set.copyOf(charts);
+ this.charts = config.additionalMetrics ? Set.copyOf(charts) : Set.of();
this.config = config;
this.debug = debug;
this.token = token;
@@ -339,8 +340,10 @@ public Metrics.Factory url(URI url) {
protected static final class Config implements Metrics.Config {
private final UUID serverId;
+ private final boolean additionalMetrics;
private final boolean debug;
private final boolean enabled;
+ private final boolean errorTracking;
private final boolean firstRun;
@Contract(mutates = "io")
@@ -359,23 +362,37 @@ protected Config(Path file) {
saveConfig.set(true);
return UUID.randomUUID();
}
- }).orElseGet(UUID::randomUUID);
+ }).orElseGet(() -> {
+ saveConfig.set(true);
+ return UUID.randomUUID();
+ });
+
+ BiPredicate predicate = (key, defaultValue) -> {
+ return properties.map(object -> object.getProperty(key)).map(Boolean::parseBoolean).orElseGet(() -> {
+ saveConfig.set(true);
+ return defaultValue;
+ });
+ };
- this.enabled = properties.map(object -> object.getProperty("enabled")).map(Boolean::parseBoolean).orElse(true);
- this.debug = properties.map(object -> object.getProperty("debug")).map(Boolean::parseBoolean).orElse(false);
+ this.enabled = predicate.test("enabled", true);
+ this.errorTracking = predicate.test("submitErrors", true);
+ this.additionalMetrics = predicate.test("submitAdditionalMetrics", true);
+ this.debug = predicate.test("debug", false);
if (saveConfig.get()) try {
- save(file, serverId, enabled, debug);
+ save(file, serverId, enabled, errorTracking, additionalMetrics, debug);
} catch (IOException e) {
throw new RuntimeException("Failed to save metrics config", e);
}
}
@VisibleForTesting
- public Config(UUID serverId, boolean enabled, boolean debug) {
+ public Config(UUID serverId, boolean enabled, boolean errorTracking, boolean additionalMetrics, boolean debug) {
this.serverId = serverId;
this.enabled = enabled;
this.debug = debug;
+ this.errorTracking = errorTracking;
+ this.additionalMetrics = additionalMetrics;
this.firstRun = false;
}
@@ -389,6 +406,16 @@ public boolean enabled() {
return enabled;
}
+ @Override
+ public boolean errorTracking() {
+ return errorTracking;
+ }
+
+ @Override
+ public boolean additionalMetrics() {
+ return additionalMetrics;
+ }
+
@Override
public boolean debug() {
return debug;
@@ -405,7 +432,7 @@ private static Optional readOrEmpty(Path file) {
}
}
- private static void save(Path file, UUID serverId, boolean enabled, boolean debug) throws IOException {
+ private static void save(Path file, UUID serverId, boolean enabled, boolean errorTracking, boolean additionalMetrics, boolean debug) throws IOException {
Files.createDirectories(file.getParent());
try (var out = Files.newOutputStream(file);
var writer = new OutputStreamWriter(out, UTF_8)) {
@@ -413,19 +440,24 @@ private static void save(Path file, UUID serverId, boolean enabled, boolean debu
properties.setProperty("serverId", serverId.toString());
properties.setProperty("enabled", Boolean.toString(enabled));
+ properties.setProperty("submitErrors", Boolean.toString(errorTracking));
+ properties.setProperty("submitAdditionalMetrics", Boolean.toString(additionalMetrics));
properties.setProperty("debug", Boolean.toString(debug));
var comment = """
- FastStats (https://faststats.dev) gathers basic information for plugin developers,
- # such as the number of users and total player count.
- # Keeping metrics enabled is recommended, but you can disable them if you prefer.
- # Enabling metrics does not affect performance,
- # and all data sent to FastStats is completely anonymous.
-
+ FastStats (https://faststats.dev) collects anonymous usage statistics for plugin developers.
+ # This helps developers understand how their projects are used in the real world.
+ #
+ # No IP addresses, player data, or personal information is collected.
+ # The server ID below is randomly generated and can be regenerated at any time.
+ #
+ # Enabling metrics has no noticeable performance impact.
+ # Keeping metrics enabled is recommended, but you can disable them by setting 'enabled=false'.
+ #
# If you suspect a plugin is collecting personal data or bypassing the "enabled" option,
- # please report it to the FastStats team (https://faststats.dev/abuse).
-
- # For more information, visit https://faststats.dev/info
+ # please report it at: https://faststats.dev/abuse
+ #
+ # For more information, visit: https://faststats.dev/info
""";
properties.store(writer, comment);
}
diff --git a/core/src/test/java/dev/faststats/MockMetrics.java b/core/src/test/java/dev/faststats/MockMetrics.java
index 377b43e..174b9d6 100644
--- a/core/src/test/java/dev/faststats/MockMetrics.java
+++ b/core/src/test/java/dev/faststats/MockMetrics.java
@@ -16,7 +16,7 @@
@NullMarked
public class MockMetrics extends SimpleMetrics {
public MockMetrics(UUID serverId, @Token String token, @Nullable ErrorTracker tracker, boolean debug) {
- super(new SimpleMetrics.Config(serverId, true, debug), Set.of(), token, tracker, URI.create("http://localhost:5000/v1/collect"), debug);
+ super(new SimpleMetrics.Config(serverId, true, true, true, debug), Set.of(), token, tracker, URI.create("http://localhost:5000/v1/collect"), debug);
}
@Override
diff --git a/gradle.properties b/gradle.properties
index c1bf17f..c57f47b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1 @@
-version=0.10.1
+version=0.11.0