Skip to content

Commit a133d40

Browse files
authored
Merge pull request #63 from faststats-dev/feat/more-configs
Add error tracking and additional metrics submission config options
2 parents a615318 + 9834041 commit a133d40

File tree

6 files changed

+117
-23
lines changed

6 files changed

+117
-23
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package dev.faststats.core;
22

3-
import dev.faststats.core.concurrent.TrackingExecutors;
43
import dev.faststats.core.concurrent.TrackingBase;
4+
import dev.faststats.core.concurrent.TrackingExecutors;
55
import dev.faststats.core.concurrent.TrackingThreadFactory;
66
import dev.faststats.core.concurrent.TrackingThreadPoolExecutor;
77
import org.jetbrains.annotations.Contract;
88
import org.jspecify.annotations.Nullable;
99

10+
import java.util.Optional;
11+
import java.util.function.BiConsumer;
12+
1013
/**
1114
* An error tracker.
1215
*
@@ -72,12 +75,34 @@ static ErrorTracker contextUnaware() {
7275

7376
/**
7477
* Attaches an error context to the tracker.
78+
* <p>
79+
* If the class loader is {@code null}, the tracker will track all errors.
7580
*
7681
* @param loader the class loader
7782
* @since 0.10.0
7883
*/
7984
void attachErrorContext(@Nullable ClassLoader loader);
8085

86+
/**
87+
* Sets the error event handler which will be called when an error is tracked automatically.
88+
* <p>
89+
* The purpose of this handler is to allow custom error handling like logging.
90+
*
91+
* @param errorEvent the error event handler
92+
* @since 0.11.0
93+
*/
94+
@Contract(mutates = "this")
95+
void setContextErrorHandler(@Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent);
96+
97+
/**
98+
* Returns the error event handler which will be called when an error is tracked automatically.
99+
*
100+
* @return the error event handler
101+
* @since 0.11.0
102+
*/
103+
@Contract(pure = true)
104+
Optional<BiConsumer<@Nullable ClassLoader, Throwable>> getContextErrorHandler();
105+
81106
/**
82107
* Returns the tracking base.
83108
*

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public interface Metrics {
6161
interface Factory<T> {
6262
/**
6363
* Adds a chart to the metrics submission.
64+
* <p>
65+
* If {@link Config#additionalMetrics()} is disabled, the chart will not be submitted.
6466
*
6567
* @param chart the chart to add
6668
* @return the metrics factory
@@ -72,6 +74,8 @@ interface Factory<T> {
7274

7375
/**
7476
* Sets the error tracker for this metrics instance.
77+
* <p>
78+
* If {@link Config#errorTracking()} is disabled, no errors will be submitted.
7579
*
7680
* @param tracker the error tracker
7781
* @return the metrics factory
@@ -158,16 +162,34 @@ interface Config {
158162
* <b>Bypassing this setting may get your project banned from FastStats.</b><br>
159163
* <b>Users have to be able to opt out from metrics submission.</b>
160164
*
161-
* @return true if metrics submission is enabled, false otherwise
165+
* @return {@code true} if metrics submission is enabled, {@code false} otherwise
162166
* @since 0.1.0
163167
*/
164168
@Contract(pure = true)
165169
boolean enabled();
166170

171+
/**
172+
* Whether error tracking is enabled across all metrics instances.
173+
*
174+
* @return {@code true} if error tracking is enabled, {@code false} otherwise
175+
* @since 0.11.0
176+
*/
177+
@Contract(pure = true)
178+
boolean errorTracking();
179+
180+
/**
181+
* Whether additional metrics are enabled across all metrics instances.
182+
*
183+
* @return {@code true} if additional metrics are enabled, {@code false} otherwise
184+
* @since 0.11.0
185+
*/
186+
@Contract(pure = true)
187+
boolean additionalMetrics();
188+
167189
/**
168190
* Whether debug logging is enabled across all metrics instances.
169191
*
170-
* @return true if debug logging is enabled, false otherwise
192+
* @return {@code true} if debug logging is enabled, {@code false} otherwise
171193
* @since 0.1.0
172194
*/
173195
@Contract(pure = true)

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import java.util.Arrays;
1313
import java.util.List;
1414
import java.util.Map;
15+
import java.util.Optional;
1516
import java.util.concurrent.ConcurrentHashMap;
17+
import java.util.function.BiConsumer;
1618

1719
final class SimpleErrorTracker implements ErrorTracker {
1820
private final int stackTraceLimit = Math.min(50, Integer.getInteger("faststats.stack-trace-limit", 15));
@@ -24,6 +26,8 @@ final class SimpleErrorTracker implements ErrorTracker {
2426
private final TrackingThreadFactory threadFactory = new SimpleTrackingThreadFactory(this);
2527
private final TrackingThreadPoolExecutor threadPoolExecutor = new SimpleTrackingThreadPoolExecutor(this);
2628

29+
private @Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent = null;
30+
2731
@Override
2832
public void trackError(String message) {
2933
trackError(new RuntimeException(message));
@@ -143,10 +147,21 @@ public void attachErrorContext(@Nullable ClassLoader loader) {
143147
Thread.setDefaultUncaughtExceptionHandler((thread, error) -> {
144148
if (handler != null) handler.uncaughtException(thread, error);
145149
if (loader != null && !isSameLoader(loader, error)) return;
150+
if (errorEvent != null) errorEvent.accept(loader, error);
146151
trackError(error);
147152
});
148153
}
149154

155+
@Override
156+
public void setContextErrorHandler(@Nullable BiConsumer<@Nullable ClassLoader, Throwable> errorEvent) {
157+
this.errorEvent = errorEvent;
158+
}
159+
160+
@Override
161+
public Optional<BiConsumer<@Nullable ClassLoader, Throwable>> getContextErrorHandler() {
162+
return Optional.ofNullable(errorEvent);
163+
}
164+
150165
@Override
151166
public TrackingBase base() {
152167
return base;

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

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.concurrent.ScheduledExecutorService;
3131
import java.util.concurrent.TimeUnit;
3232
import java.util.concurrent.atomic.AtomicBoolean;
33+
import java.util.function.BiPredicate;
3334
import java.util.zip.GZIPOutputStream;
3435

3536
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -62,11 +63,11 @@ public abstract class SimpleMetrics implements Metrics {
6263
protected SimpleMetrics(Factory<?> factory, Path config) throws IllegalStateException {
6364
if (factory.token == null) throw new IllegalStateException("Token must be specified");
6465

65-
this.charts = Set.copyOf(factory.charts);
6666
this.config = new Config(config);
67+
this.charts = this.config.additionalMetrics ? Set.copyOf(factory.charts) : Set.of();
6768
this.debug = factory.debug || Boolean.getBoolean("faststats.debug") || this.config.debug();
6869
this.token = factory.token;
69-
this.tracker = factory.tracker;
70+
this.tracker = this.config.errorTracking ? factory.tracker : null;
7071
this.url = factory.url;
7172

7273
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
@@ -85,7 +86,7 @@ protected SimpleMetrics(Config config, Set<Chart<?>> charts, @Token String token
8586
throw new IllegalArgumentException("Invalid token '" + token + "', must match '" + Token.PATTERN + "'");
8687
}
8788

88-
this.charts = Set.copyOf(charts);
89+
this.charts = config.additionalMetrics ? Set.copyOf(charts) : Set.of();
8990
this.config = config;
9091
this.debug = debug;
9192
this.token = token;
@@ -339,8 +340,10 @@ public Metrics.Factory<T> url(URI url) {
339340

340341
protected static final class Config implements Metrics.Config {
341342
private final UUID serverId;
343+
private final boolean additionalMetrics;
342344
private final boolean debug;
343345
private final boolean enabled;
346+
private final boolean errorTracking;
344347
private final boolean firstRun;
345348

346349
@Contract(mutates = "io")
@@ -359,23 +362,37 @@ protected Config(Path file) {
359362
saveConfig.set(true);
360363
return UUID.randomUUID();
361364
}
362-
}).orElseGet(UUID::randomUUID);
365+
}).orElseGet(() -> {
366+
saveConfig.set(true);
367+
return UUID.randomUUID();
368+
});
369+
370+
BiPredicate<String, Boolean> predicate = (key, defaultValue) -> {
371+
return properties.map(object -> object.getProperty(key)).map(Boolean::parseBoolean).orElseGet(() -> {
372+
saveConfig.set(true);
373+
return defaultValue;
374+
});
375+
};
363376

364-
this.enabled = properties.map(object -> object.getProperty("enabled")).map(Boolean::parseBoolean).orElse(true);
365-
this.debug = properties.map(object -> object.getProperty("debug")).map(Boolean::parseBoolean).orElse(false);
377+
this.enabled = predicate.test("enabled", true);
378+
this.errorTracking = predicate.test("submitErrors", true);
379+
this.additionalMetrics = predicate.test("submitAdditionalMetrics", true);
380+
this.debug = predicate.test("debug", false);
366381

367382
if (saveConfig.get()) try {
368-
save(file, serverId, enabled, debug);
383+
save(file, serverId, enabled, errorTracking, additionalMetrics, debug);
369384
} catch (IOException e) {
370385
throw new RuntimeException("Failed to save metrics config", e);
371386
}
372387
}
373388

374389
@VisibleForTesting
375-
public Config(UUID serverId, boolean enabled, boolean debug) {
390+
public Config(UUID serverId, boolean enabled, boolean errorTracking, boolean additionalMetrics, boolean debug) {
376391
this.serverId = serverId;
377392
this.enabled = enabled;
378393
this.debug = debug;
394+
this.errorTracking = errorTracking;
395+
this.additionalMetrics = additionalMetrics;
379396
this.firstRun = false;
380397
}
381398

@@ -389,6 +406,16 @@ public boolean enabled() {
389406
return enabled;
390407
}
391408

409+
@Override
410+
public boolean errorTracking() {
411+
return errorTracking;
412+
}
413+
414+
@Override
415+
public boolean additionalMetrics() {
416+
return additionalMetrics;
417+
}
418+
392419
@Override
393420
public boolean debug() {
394421
return debug;
@@ -405,27 +432,32 @@ private static Optional<Properties> readOrEmpty(Path file) {
405432
}
406433
}
407434

408-
private static void save(Path file, UUID serverId, boolean enabled, boolean debug) throws IOException {
435+
private static void save(Path file, UUID serverId, boolean enabled, boolean errorTracking, boolean additionalMetrics, boolean debug) throws IOException {
409436
Files.createDirectories(file.getParent());
410437
try (var out = Files.newOutputStream(file);
411438
var writer = new OutputStreamWriter(out, UTF_8)) {
412439
var properties = new Properties();
413440

414441
properties.setProperty("serverId", serverId.toString());
415442
properties.setProperty("enabled", Boolean.toString(enabled));
443+
properties.setProperty("submitErrors", Boolean.toString(errorTracking));
444+
properties.setProperty("submitAdditionalMetrics", Boolean.toString(additionalMetrics));
416445
properties.setProperty("debug", Boolean.toString(debug));
417446

418447
var comment = """
419-
FastStats (https://faststats.dev) gathers basic information for plugin developers,
420-
# such as the number of users and total player count.
421-
# Keeping metrics enabled is recommended, but you can disable them if you prefer.
422-
# Enabling metrics does not affect performance,
423-
# and all data sent to FastStats is completely anonymous.
424-
448+
FastStats (https://faststats.dev) collects anonymous usage statistics for plugin developers.
449+
# This helps developers understand how their projects are used in the real world.
450+
#
451+
# No IP addresses, player data, or personal information is collected.
452+
# The server ID below is randomly generated and can be regenerated at any time.
453+
#
454+
# Enabling metrics has no noticeable performance impact.
455+
# Keeping metrics enabled is recommended, but you can disable them by setting 'enabled=false'.
456+
#
425457
# If you suspect a plugin is collecting personal data or bypassing the "enabled" option,
426-
# please report it to the FastStats team (https://faststats.dev/abuse).
427-
428-
# For more information, visit https://faststats.dev/info
458+
# please report it at: https://faststats.dev/abuse
459+
#
460+
# For more information, visit: https://faststats.dev/info
429461
""";
430462
properties.store(writer, comment);
431463
}

core/src/test/java/dev/faststats/MockMetrics.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
@NullMarked
1717
public class MockMetrics extends SimpleMetrics {
1818
public MockMetrics(UUID serverId, @Token String token, @Nullable ErrorTracker tracker, boolean debug) {
19-
super(new SimpleMetrics.Config(serverId, true, debug), Set.of(), token, tracker, URI.create("http://localhost:5000/v1/collect"), debug);
19+
super(new SimpleMetrics.Config(serverId, true, true, true, debug), Set.of(), token, tracker, URI.create("http://localhost:5000/v1/collect"), debug);
2020
}
2121

2222
@Override

gradle.properties

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

0 commit comments

Comments
 (0)