From 945572e6c45df6352b6dbe8d0677927b9f7b1d11 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 22 Jan 2026 21:54:38 +0100 Subject: [PATCH 1/2] Implement FabricMetrics class and example mod setup --- fabric/example-mod/build.gradle.kts | 19 ++++ .../src/main/java/com/example/ExampleMod.java | 49 +++++++++++ .../src/main/resources/fabric.mod.json | 20 +++++ .../dev/faststats/fabric/FabricMetrics.java | 42 +++++++++ .../faststats/fabric/FabricMetricsImpl.java | 87 +++++++++++++++++++ fabric/src/main/java/module-info.java | 14 +++ settings.gradle.kts | 7 ++ 7 files changed, 238 insertions(+) create mode 100644 fabric/example-mod/build.gradle.kts create mode 100644 fabric/example-mod/src/main/java/com/example/ExampleMod.java create mode 100644 fabric/example-mod/src/main/resources/fabric.mod.json create mode 100644 fabric/src/main/java/dev/faststats/fabric/FabricMetrics.java create mode 100644 fabric/src/main/java/dev/faststats/fabric/FabricMetricsImpl.java create mode 100644 fabric/src/main/java/module-info.java diff --git a/fabric/example-mod/build.gradle.kts b/fabric/example-mod/build.gradle.kts new file mode 100644 index 0000000..c345301 --- /dev/null +++ b/fabric/example-mod/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("net.fabricmc.fabric-loom-remap") version "1.14-SNAPSHOT" +} + +loom { + splitEnvironmentSourceSets() +} + +dependencies { + implementation(project(":fabric")) + mappings(loom.officialMojangMappings()) + minecraft("com.mojang:minecraft:1.21.11") + modImplementation("net.fabricmc:fabric-loader:0.18.4") +} + +tasks.jar { + from(project(":fabric").tasks.jar) + from(project(":core").tasks.jar) +} \ No newline at end of file diff --git a/fabric/example-mod/src/main/java/com/example/ExampleMod.java b/fabric/example-mod/src/main/java/com/example/ExampleMod.java new file mode 100644 index 0000000..3c9c531 --- /dev/null +++ b/fabric/example-mod/src/main/java/com/example/ExampleMod.java @@ -0,0 +1,49 @@ +package com.example; + +import dev.faststats.core.ErrorTracker; +import dev.faststats.core.Metrics; +import dev.faststats.core.chart.Chart; +import dev.faststats.fabric.FabricMetrics; +import net.fabricmc.api.ModInitializer; + +public class ExampleMod implements ModInitializer { + // context-aware error tracker, automatically tracks errors in the same class loader + public static final ErrorTracker ERROR_TRACKER = ErrorTracker.contextAware(); + + // context-unaware error tracker, does not automatically track errors + public static final ErrorTracker CONTEXT_UNAWARE_ERROR_TRACKER = ErrorTracker.contextUnaware(); + + private final Metrics metrics = FabricMetrics.factory() + //.url(URI.create("https://metrics.example.com/v1/collect")) // For self-hosted metrics servers only + + // Custom example charts + // For this to work you have to create a corresponding data source in your project settings first + .addChart(Chart.number("example_chart", () -> 42)) + .addChart(Chart.string("example_string", () -> "Hello, World!")) + .addChart(Chart.bool("example_boolean", () -> true)) + .addChart(Chart.stringArray("example_string_array", () -> new String[]{"Option 1", "Option 2"})) + .addChart(Chart.numberArray("example_number_array", () -> new Number[]{1, 2, 3})) + .addChart(Chart.booleanArray("example_boolean_array", () -> new Boolean[]{true, false})) + + // Attach an error tracker + // This must be enabled in the project settings + .errorTracker(ERROR_TRACKER) + + .debug(true) // Enable debug mode for development and testing + + .token("sadlskmsldmkfglsdkmfgksjdfhngkjd") // required -> token can be found in the settings of your project + .create("example-mod"); // your mod id as defined in fabric.mod.json + + public void doSomethingWrong() { + try { + // Do something that might throw an error + throw new RuntimeException("Something went wrong!"); + } catch (Exception e) { + CONTEXT_UNAWARE_ERROR_TRACKER.trackError(e); + } + } + + @Override + public void onInitialize() { + } +} diff --git a/fabric/example-mod/src/main/resources/fabric.mod.json b/fabric/example-mod/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..cdc4816 --- /dev/null +++ b/fabric/example-mod/src/main/resources/fabric.mod.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": 1, + "id": "example-mod", + "version": "1.0.0", + "name": "Example Mod", + "authors": [ + "Your Name" + ], + "environment": "server", + "entrypoints": { + "main": [ + "com.example.ExampleMod" + ] + }, + "depends": { + "fabricloader": ">=0.18.4", + "minecraft": "1.21.11", + "fabric-api": ">=0.139.4" + } +} diff --git a/fabric/src/main/java/dev/faststats/fabric/FabricMetrics.java b/fabric/src/main/java/dev/faststats/fabric/FabricMetrics.java new file mode 100644 index 0000000..f832b18 --- /dev/null +++ b/fabric/src/main/java/dev/faststats/fabric/FabricMetrics.java @@ -0,0 +1,42 @@ +package dev.faststats.fabric; + +import dev.faststats.core.Metrics; +import org.jetbrains.annotations.Async; +import org.jetbrains.annotations.Contract; + +/** + * Fabric metrics implementation. + * + * @since 0.13.0 + */ +public sealed interface FabricMetrics extends Metrics permits FabricMetricsImpl { + /** + * Creates a new metrics factory for Fabric. + * + * @return the metrics factory + * @since 0.13.0 + */ + @Contract(pure = true) + static Factory factory() { + return new FabricMetricsImpl.Factory(); + } + + interface Factory extends Metrics.Factory { + /** + * Creates a new metrics instance. + *

+ * Metrics submission will start automatically. + * + * @param modId the mod id + * @return the metrics instance + * @throws IllegalStateException if the token is not specified + * @throws IllegalArgumentException if the mod is not found + * @see #token(String) + * @since 0.13.0 + */ + @Override + @Async.Schedule + @Contract(value = "_ -> new", mutates = "io") + Metrics create(String modId) throws IllegalStateException, IllegalArgumentException; + } +} diff --git a/fabric/src/main/java/dev/faststats/fabric/FabricMetricsImpl.java b/fabric/src/main/java/dev/faststats/fabric/FabricMetricsImpl.java new file mode 100644 index 0000000..c5b54d7 --- /dev/null +++ b/fabric/src/main/java/dev/faststats/fabric/FabricMetricsImpl.java @@ -0,0 +1,87 @@ +package dev.faststats.fabric; + +import com.google.gson.JsonObject; +import dev.faststats.core.Metrics; +import dev.faststats.core.SimpleMetrics; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.server.MinecraftServer; +import org.jetbrains.annotations.Async; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Supplier; + +final class FabricMetricsImpl extends SimpleMetrics implements FabricMetrics { + private final Logger logger = LoggerFactory.getLogger("FastStats"); + private final ModContainer mod; + + private @Nullable MinecraftServer server; + + @Async.Schedule + @Contract(mutates = "io") + private FabricMetricsImpl(Factory factory, ModContainer mod, Path config) throws IllegalStateException { + super(factory, config); + + this.mod = mod; + + ServerLifecycleEvents.SERVER_STARTED.register(server -> { + this.server = server; + startSubmitting(); + }); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> shutdown()); + } + + @Override + protected void appendDefaultData(JsonObject charts) { + assert server != null : "Server not initialized"; + charts.addProperty("minecraft_version", server.getServerVersion()); + charts.addProperty("online_mode", server.usesAuthentication()); + charts.addProperty("player_count", server.getPlayerCount()); + charts.addProperty("plugin_version", mod.getMetadata().getVersion().getFriendlyString()); + charts.addProperty("server_type", "Fabric"); + } + + @Override + protected void printError(String message, @Nullable Throwable throwable) { + logger.error(message, throwable); + } + + @Override + protected void printInfo(String message) { + logger.info(message); + } + + @Override + protected void printWarning(String message) { + logger.warn(message); + } + + private Optional tryOrEmpty(Supplier supplier) { + try { + return Optional.of(supplier.get()); + } catch (NoSuchMethodError | Exception e) { + return Optional.empty(); + } + } + + static final class Factory extends SimpleMetrics.Factory implements FabricMetrics.Factory { + @Override + public Metrics create(String modId) throws IllegalStateException, IllegalArgumentException { + var fabric = FabricLoader.getInstance(); + var mod = fabric.getModContainer(modId).orElseThrow(() -> { + return new IllegalArgumentException("Mod not found: " + modId); + }); + + var dataFolder = fabric.getConfigDir().resolve("faststats"); + var config = dataFolder.resolve("config.properties"); + + return new FabricMetricsImpl(this, mod, config); + } + } +} diff --git a/fabric/src/main/java/module-info.java b/fabric/src/main/java/module-info.java new file mode 100644 index 0000000..c6601e4 --- /dev/null +++ b/fabric/src/main/java/module-info.java @@ -0,0 +1,14 @@ +import org.jspecify.annotations.NullMarked; + +@NullMarked +module dev.faststats.fabric { + exports dev.faststats.fabric; + + requires com.google.gson; + requires dev.faststats.core; + requires net.fabricmc.loader; + requires org.slf4j; + + requires static org.jetbrains.annotations; + requires static org.jspecify; +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a61ffb2..9a9c353 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,8 @@ +pluginManagement.repositories { + maven("https://maven.fabricmc.net/") + gradlePluginPortal() +} + plugins { id("org.gradle.toolchains.foojay-resolver-convention").version("1.0.0") } @@ -7,6 +12,8 @@ include("bukkit") include("bukkit:example-plugin") include("bungeecord") include("core") +include("fabric") +include("fabric:example-mod") include("hytale") include("hytale:example-plugin") include("minestom") From 52597c3d941538ef94b61c43367482051d6b7281 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 22 Jan 2026 22:37:39 +0100 Subject: [PATCH 2/2] Update build script and fix token in ExampleMod --- fabric/build.gradle.kts | 13 +++++++++++++ fabric/example-mod/build.gradle.kts | 13 +++++-------- .../src/main/java/com/example/ExampleMod.java | 6 ++++-- 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 fabric/build.gradle.kts diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts new file mode 100644 index 0000000..9c2b9f0 --- /dev/null +++ b/fabric/build.gradle.kts @@ -0,0 +1,13 @@ +val moduleName by extra("dev.faststats.fabric") + +plugins { + id("fabric-loom") version ("1.15-SNAPSHOT") +} + +dependencies { + api(project(":core")) + mappings(loom.officialMojangMappings()) + minecraft("com.mojang:minecraft:1.21.11") + modCompileOnly("net.fabricmc.fabric-api:fabric-api:0.139.4+1.21.11") + modImplementation("net.fabricmc:fabric-loader:0.18.4") +} diff --git a/fabric/example-mod/build.gradle.kts b/fabric/example-mod/build.gradle.kts index c345301..d34ea24 100644 --- a/fabric/example-mod/build.gradle.kts +++ b/fabric/example-mod/build.gradle.kts @@ -1,19 +1,16 @@ plugins { - id("net.fabricmc.fabric-loom-remap") version "1.14-SNAPSHOT" -} - -loom { - splitEnvironmentSourceSets() + id("fabric-loom") } dependencies { implementation(project(":fabric")) mappings(loom.officialMojangMappings()) minecraft("com.mojang:minecraft:1.21.11") - modImplementation("net.fabricmc:fabric-loader:0.18.4") + modCompileOnly("net.fabricmc:fabric-loader:0.18.4") } tasks.jar { - from(project(":fabric").tasks.jar) - from(project(":core").tasks.jar) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(project(":fabric").sourceSets["main"].output) + from(project(":core").sourceSets["main"].output) } \ No newline at end of file diff --git a/fabric/example-mod/src/main/java/com/example/ExampleMod.java b/fabric/example-mod/src/main/java/com/example/ExampleMod.java index 3c9c531..9279241 100644 --- a/fabric/example-mod/src/main/java/com/example/ExampleMod.java +++ b/fabric/example-mod/src/main/java/com/example/ExampleMod.java @@ -6,6 +6,8 @@ import dev.faststats.fabric.FabricMetrics; import net.fabricmc.api.ModInitializer; +import java.net.URI; + public class ExampleMod implements ModInitializer { // context-aware error tracker, automatically tracks errors in the same class loader public static final ErrorTracker ERROR_TRACKER = ErrorTracker.contextAware(); @@ -14,7 +16,7 @@ public class ExampleMod implements ModInitializer { public static final ErrorTracker CONTEXT_UNAWARE_ERROR_TRACKER = ErrorTracker.contextUnaware(); private final Metrics metrics = FabricMetrics.factory() - //.url(URI.create("https://metrics.example.com/v1/collect")) // For self-hosted metrics servers only + .url(URI.create("https://metrics.example.com/v1/collect")) // For self-hosted metrics servers only // Custom example charts // For this to work you have to create a corresponding data source in your project settings first @@ -31,7 +33,7 @@ public class ExampleMod implements ModInitializer { .debug(true) // Enable debug mode for development and testing - .token("sadlskmsldmkfglsdkmfgksjdfhngkjd") // required -> token can be found in the settings of your project + .token("YOUR_TOKEN_HERE") // required -> token can be found in the settings of your project .create("example-mod"); // your mod id as defined in fabric.mod.json public void doSomethingWrong() {