Skip to content

Commit 87b5ed0

Browse files
committed
Add error tracking system
1 parent 62496cd commit 87b5ed0

23 files changed

+1305
-9
lines changed

bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
import dev.faststats.bukkit.BukkitMetrics;
44
import dev.faststats.core.Metrics;
55
import dev.faststats.core.chart.Chart;
6+
import dev.faststats.errors.ErrorTracker;
67
import org.bukkit.plugin.java.JavaPlugin;
78

89
import java.net.URI;
910

1011
public class ExamplePlugin extends JavaPlugin {
12+
// context-aware error tracker, automatically tracks errors in the same class loader
13+
public static final ErrorTracker ERROR_TRACKER = ErrorTracker.contextAware();
14+
15+
// context-unaware error tracker, does not automatically track errors
16+
public static final ErrorTracker CONTEXT_UNAWARE_ERROR_TRACKER = ErrorTracker.contextUnaware();
17+
1118
private final Metrics metrics = BukkitMetrics.factory()
1219
.url(URI.create("https://metrics.example.com/v1/collect")) // For self-hosted metrics servers only
1320

@@ -20,6 +27,10 @@ public class ExamplePlugin extends JavaPlugin {
2027
.addChart(Chart.numberArray("example_number_array", () -> new Number[]{1, 2, 3}))
2128
.addChart(Chart.booleanArray("example_boolean_array", () -> new Boolean[]{true, false}))
2229

30+
// Attach an error tracker
31+
// This must be enabled in the project settings
32+
.errorTracker(ERROR_TRACKER)
33+
2334
.debug(true) // Enable debug mode for development and testing
2435

2536
.token("YOUR_TOKEN_HERE") // required -> token can be found in the settings of your project
@@ -29,4 +40,13 @@ public class ExamplePlugin extends JavaPlugin {
2940
public void onDisable() {
3041
metrics.shutdown();
3142
}
43+
44+
public void doSomethingWrong() {
45+
try {
46+
// Do something that might throw an error
47+
throw new RuntimeException("Something went wrong!");
48+
} catch (Exception e) {
49+
CONTEXT_UNAWARE_ERROR_TRACKER.trackError(e);
50+
}
51+
}
3252
}

core/build.gradle.kts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
dependencies {
2-
compileOnlyApi("com.google.code.gson:gson:2.13.2")
3-
compileOnlyApi("org.jetbrains:annotations:26.0.2-1")
4-
compileOnlyApi("org.jspecify:jspecify:1.0.0")
2+
api(project(":error-tracker"))
53

64
testImplementation("com.google.code.gson:gson:2.13.2")
75
testImplementation("org.junit.jupiter:junit-jupiter")

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

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

33
import dev.faststats.core.chart.Chart;
4+
import dev.faststats.errors.ErrorTracker;
45
import org.jetbrains.annotations.Async;
56
import org.jetbrains.annotations.Contract;
67

78
import java.net.URI;
9+
import java.util.Optional;
810
import java.util.UUID;
911

1012
/**
@@ -23,6 +25,15 @@ public interface Metrics {
2325
@Contract(pure = true)
2426
String getToken();
2527

28+
/**
29+
* Get the error tracker for this metrics instance.
30+
*
31+
* @return the error tracker
32+
* @since 0.10.0
33+
*/
34+
@Contract(pure = true)
35+
Optional<ErrorTracker> getErrorTracker();
36+
2637
/**
2738
* Get the metrics configuration.
2839
*
@@ -60,6 +71,16 @@ interface Factory<T> {
6071
@Contract(mutates = "this")
6172
Factory<T> addChart(Chart<?> chart) throws IllegalArgumentException;
6273

74+
/**
75+
* Sets the error tracker for this metrics instance.
76+
*
77+
* @param tracker the error tracker
78+
* @return the metrics factory
79+
* @since 0.10.0
80+
*/
81+
@Contract(mutates = "this")
82+
Factory<T> errorTracker(ErrorTracker tracker);
83+
6384
/**
6485
* Enables or disabled debug mode for this metrics instance.
6586
* <p>

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.gson.JsonObject;
44
import dev.faststats.core.chart.Chart;
5+
import dev.faststats.errors.ErrorTracker;
56
import org.jetbrains.annotations.Async;
67
import org.jetbrains.annotations.Contract;
78
import org.jetbrains.annotations.MustBeInvokedByOverriders;
@@ -52,6 +53,7 @@ public abstract class SimpleMetrics implements Metrics {
5253
private final Set<Chart<?>> charts;
5354
private final Config config;
5455
private final @Token String token;
56+
private final @Nullable ErrorTracker tracker;
5557
private final URI url;
5658
private final boolean debug;
5759

@@ -64,11 +66,12 @@ protected SimpleMetrics(SimpleMetrics.Factory<?> factory, Path config) throws Il
6466
this.config = new Config(config);
6567
this.debug = factory.debug || Boolean.getBoolean("faststats.debug") || this.config.debug();
6668
this.token = factory.token;
69+
this.tracker = factory.tracker;
6770
this.url = factory.url;
6871
}
6972

7073
@VisibleForTesting
71-
protected SimpleMetrics(Config config, Set<Chart<?>> charts, @Token String token, URI url, boolean debug) {
74+
protected SimpleMetrics(Config config, Set<Chart<?>> charts, @Token String token, @Nullable ErrorTracker tracker, URI url, boolean debug) {
7275
if (!token.matches(Token.PATTERN)) {
7376
throw new IllegalArgumentException("Invalid token '" + token + "', must match '" + Token.PATTERN + "'");
7477
}
@@ -77,6 +80,7 @@ protected SimpleMetrics(Config config, Set<Chart<?>> charts, @Token String token
7780
this.config = config;
7881
this.debug = debug;
7982
this.token = token;
83+
this.tracker = tracker;
8084
this.url = url;
8185
}
8286

@@ -174,6 +178,7 @@ protected CompletableFuture<Boolean> submitAsync() throws IOException {
174178

175179
if (statusCode >= 200 && statusCode < 300) {
176180
info("Metrics submitted with status code: " + statusCode + " (" + body + ")");
181+
getErrorTracker().ifPresent(ErrorTracker::clear);
177182
return true;
178183
} else if (statusCode >= 300 && statusCode < 400) {
179184
warn("Received redirect response from metrics server: " + statusCode + " (" + body + ")");
@@ -219,8 +224,10 @@ protected JsonObject createData() {
219224

220225
appendDefaultData(charts);
221226

222-
data.addProperty("server_id", config.serverId().toString());
227+
data.addProperty("identifier", config.serverId().toString());
223228
data.add("data", charts);
229+
230+
getErrorTracker().flatMap(ErrorTracker::getData).ifPresent(errors -> data.add("errors", errors));
224231
return data;
225232
}
226233

@@ -229,6 +236,11 @@ protected JsonObject createData() {
229236
return token;
230237
}
231238

239+
@Override
240+
public Optional<ErrorTracker> getErrorTracker() {
241+
return Optional.ofNullable(tracker);
242+
}
243+
232244
@Override
233245
public Metrics.Config getConfig() {
234246
return config;
@@ -270,6 +282,7 @@ public void shutdown() {
270282
public abstract static class Factory<T> implements Metrics.Factory<T> {
271283
private final Set<Chart<?>> charts = new HashSet<>(0);
272284
private URI url = URI.create("https://metrics.faststats.dev/v1/collect");
285+
private @Nullable ErrorTracker tracker;
273286
private @Nullable String token;
274287
private boolean debug = false;
275288

@@ -279,6 +292,12 @@ public Metrics.Factory<T> addChart(Chart<?> chart) throws IllegalArgumentExcepti
279292
return this;
280293
}
281294

295+
@Override
296+
public Metrics.Factory<T> errorTracker(ErrorTracker tracker) {
297+
this.tracker = tracker;
298+
return this;
299+
}
300+
282301
@Override
283302
public Metrics.Factory<T> debug(boolean enabled) {
284303
this.debug = enabled;

core/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
exports dev.faststats.core;
77

88
requires com.google.gson;
9+
requires dev.faststats.errors;
910
requires java.net.http;
1011

1112
requires static org.jetbrains.annotations;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
public class MetricsTest {
1111
@Test
1212
public void testCreateData() throws IOException {
13-
var mock = new MockMetrics(UUID.randomUUID(), "24f9fc423ed06194065a42d00995c600", true);
13+
var mock = new MockMetrics(UUID.randomUUID(), "24f9fc423ed06194065a42d00995c600", null, true);
1414
assumeTrue(mock.submitAsync().join(), "For this test to run, the server must be running");
1515
}
1616
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.gson.JsonObject;
44
import dev.faststats.core.SimpleMetrics;
55
import dev.faststats.core.Token;
6+
import dev.faststats.errors.ErrorTracker;
67
import org.jspecify.annotations.NullMarked;
78
import org.jspecify.annotations.Nullable;
89

@@ -14,8 +15,8 @@
1415

1516
@NullMarked
1617
public class MockMetrics extends SimpleMetrics {
17-
public MockMetrics(UUID serverId, @Token String token, boolean debug) {
18-
super(new SimpleMetrics.Config(serverId, true, debug), Set.of(), token, URI.create("http://localhost:5000/v1/collect"), debug);
18+
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);
1920
}
2021

2122
@Override

error-tracker/build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
dependencies {
2+
compileOnlyApi("com.google.code.gson:gson:2.13.2")
3+
compileOnlyApi("org.jetbrains:annotations:26.0.2-1")
4+
compileOnlyApi("org.jspecify:jspecify:1.0.0")
5+
6+
testImplementation("com.google.code.gson:gson:2.13.2")
7+
testImplementation("org.junit.jupiter:junit-jupiter")
8+
testImplementation(platform("org.junit:junit-bom:6.1.0-M1"))
9+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
10+
}

0 commit comments

Comments
 (0)