From def6aa04423a8e571388ab1be2720a24b7bae2c1 Mon Sep 17 00:00:00 2001 From: Hunter S Date: Sat, 20 Jun 2026 21:24:10 -0700 Subject: [PATCH 1/3] docs(examples): add Kotlin examples for all platforms - added [platform]\example-plugin\src\main\java\com\example\ExamplePlugin.kt - added [platform]\example-mod\src\main\java\com\example\ExampleMod.kt --- .../main/java/com/example/ExamplePlugin.kt | 44 ++++++++++++++ .../main/java/com/example/ExamplePlugin.kt | 44 ++++++++++++++ .../src/main/java/com/example/ExampleMod.kt | 43 ++++++++++++++ .../main/java/com/example/ExamplePlugin.kt | 45 ++++++++++++++ .../main/java/com/example/ExampleServer.kt | 47 +++++++++++++++ .../src/main/java/com/example/ExampleMod.kt | 34 +++++++++++ .../main/java/com/example/ExamplePlugin.kt | 44 ++++++++++++++ .../main/java/com/example/ExamplePlugin.kt | 57 ++++++++++++++++++ .../main/java/com/example/ExamplePlugin.kt | 58 +++++++++++++++++++ 9 files changed, 416 insertions(+) create mode 100644 bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt create mode 100644 bungeecord/example-plugin/src/main/java/com/example/ExamplePlugin.kt create mode 100644 fabric/example-mod/src/main/java/com/example/ExampleMod.kt create mode 100644 hytale/example-plugin/src/main/java/com/example/ExamplePlugin.kt create mode 100644 minestom/example-server/src/main/java/com/example/ExampleServer.kt create mode 100644 neoforge/example-mod/src/main/java/com/example/ExampleMod.kt create mode 100644 nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt create mode 100644 sponge/example-plugin/src/main/java/com/example/ExamplePlugin.kt create mode 100644 velocity/example-plugin/src/main/java/com/example/ExamplePlugin.kt diff --git a/bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt new file mode 100644 index 00000000..ac6ab52e --- /dev/null +++ b/bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt @@ -0,0 +1,44 @@ +package com.example + +import dev.faststats.ErrorTracker +import dev.faststats.bukkit.BukkitContext +import dev.faststats.data.Metric +import org.bukkit.plugin.java.JavaPlugin +import java.util.concurrent.atomic.AtomicInteger + +class ExamplePlugin : JavaPlugin() { + private val gameCount = AtomicInteger() + + private val context = BukkitContext.Factory(this, "YOUR_TOKEN_HERE") + .errorTrackerService(ERROR_TRACKER) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .create() + + override fun onEnable() { + context.ready() // start metrics and errors submission + } + + override fun onDisable() { + context.shutdown() // safely shut down configured services + } + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} diff --git a/bungeecord/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/bungeecord/example-plugin/src/main/java/com/example/ExamplePlugin.kt new file mode 100644 index 00000000..5325aa88 --- /dev/null +++ b/bungeecord/example-plugin/src/main/java/com/example/ExamplePlugin.kt @@ -0,0 +1,44 @@ +package com.example + +import dev.faststats.ErrorTracker +import dev.faststats.bungee.BungeeContext +import dev.faststats.data.Metric +import net.md_5.bungee.api.plugin.Plugin +import java.util.concurrent.atomic.AtomicInteger + +class ExamplePlugin : Plugin() { + private val gameCount = AtomicInteger() + + private val context = BungeeContext.Factory(this, "YOUR_TOKEN_HERE") + .errorTrackerService(ERROR_TRACKER) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .create() + + override fun onEnable() { + context.ready() // start metrics and errors submission + } + + override fun onDisable() { + context.shutdown() // safely shut down configured services + } + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} diff --git a/fabric/example-mod/src/main/java/com/example/ExampleMod.kt b/fabric/example-mod/src/main/java/com/example/ExampleMod.kt new file mode 100644 index 00000000..62caee46 --- /dev/null +++ b/fabric/example-mod/src/main/java/com/example/ExampleMod.kt @@ -0,0 +1,43 @@ +package com.example + +import dev.faststats.ErrorTracker +import dev.faststats.data.Metric +import dev.faststats.fabric.FabricContext +import net.fabricmc.api.ModInitializer +import java.util.concurrent.atomic.AtomicInteger + +class ExampleMod : ModInitializer { + private val gameCount = AtomicInteger() + + private val context = FabricContext.Factory( + "example-mod", // your mod id as defined in fabric.mod.json + "YOUR_TOKEN_HERE", + ) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .errorTrackerService(ERROR_TRACKER) + .create() + + override fun onInitialize() { + // your actual logic + } + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} diff --git a/hytale/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/hytale/example-plugin/src/main/java/com/example/ExamplePlugin.kt new file mode 100644 index 00000000..27799c5d --- /dev/null +++ b/hytale/example-plugin/src/main/java/com/example/ExamplePlugin.kt @@ -0,0 +1,45 @@ +package com.example + +import com.hypixel.hytale.server.core.plugin.JavaPlugin +import com.hypixel.hytale.server.core.plugin.JavaPluginInit +import dev.faststats.ErrorTracker +import dev.faststats.data.Metric +import dev.faststats.hytale.HytaleContext +import java.util.concurrent.atomic.AtomicInteger + +class ExamplePlugin(init: JavaPluginInit) : JavaPlugin(init) { + private val gameCount = AtomicInteger() + + private val context = HytaleContext.Factory(this, "YOUR_TOKEN_HERE") + .errorTrackerService(ERROR_TRACKER) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .create() + + override fun setup() { + context.ready() // start metrics and errors submission + } + + override fun shutdown() { + context.shutdown() // safely shut down configured services + } + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} diff --git a/minestom/example-server/src/main/java/com/example/ExampleServer.kt b/minestom/example-server/src/main/java/com/example/ExampleServer.kt new file mode 100644 index 00000000..b253d1dc --- /dev/null +++ b/minestom/example-server/src/main/java/com/example/ExampleServer.kt @@ -0,0 +1,47 @@ +package com.example + +import dev.faststats.ErrorTracker +import dev.faststats.data.Metric +import dev.faststats.minestom.MinestomContext +import net.minestom.server.MinecraftServer +import java.util.concurrent.atomic.AtomicInteger + +object ExampleServer { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + private val gameCount = AtomicInteger() + + private val context = MinestomContext.Factory("YOUR_TOKEN_HERE") + .errorTrackerService(ERROR_TRACKER) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .create() + + @JvmStatic + fun main(args: Array) { + val server = MinecraftServer.init() + + server.start("0.0.0.0", 25565) + MinecraftServer.getSchedulerManager().buildShutdownTask { shutdown() } + + context.ready() // start metrics and errors submission + } + + fun shutdown() { + context.shutdown() // safely shut down configured services + } + + fun startGame() { + gameCount.incrementAndGet() + } +} diff --git a/neoforge/example-mod/src/main/java/com/example/ExampleMod.kt b/neoforge/example-mod/src/main/java/com/example/ExampleMod.kt new file mode 100644 index 00000000..5ab7c555 --- /dev/null +++ b/neoforge/example-mod/src/main/java/com/example/ExampleMod.kt @@ -0,0 +1,34 @@ +package com.example + +import dev.faststats.ErrorTracker +import dev.faststats.data.Metric +import dev.faststats.neoforge.NeoForgeContext +import net.neoforged.fml.common.Mod +import java.util.concurrent.atomic.AtomicInteger + +@Mod("example_mod") +class ExampleMod { + private val gameCount = AtomicInteger() + + private val context = NeoForgeContext.Factory( + "example_mod", + "YOUR_TOKEN_HERE", + ) + .metrics { factory -> + factory + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + .onFlush { gameCount.set(0) } + .create() + } + .errorTrackerService(ERROR_TRACKER) + .create() + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} diff --git a/nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt new file mode 100644 index 00000000..4a3dd8c4 --- /dev/null +++ b/nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt @@ -0,0 +1,44 @@ +package com.example + +import cn.nukkit.plugin.PluginBase +import dev.faststats.ErrorTracker +import dev.faststats.data.Metric +import dev.faststats.nukkit.NukkitContext +import java.util.concurrent.atomic.AtomicInteger + +class ExamplePlugin : PluginBase() { + private val gameCount = AtomicInteger() + + private val context = NukkitContext.Factory(this, "YOUR_TOKEN_HERE") + .errorTrackerService(ERROR_TRACKER) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .create() + + override fun onEnable() { + context.ready() // start metrics and errors submission + } + + override fun onDisable() { + context.shutdown() // safely shut down configured services + } + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} diff --git a/sponge/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/sponge/example-plugin/src/main/java/com/example/ExamplePlugin.kt new file mode 100644 index 00000000..8fc9720c --- /dev/null +++ b/sponge/example-plugin/src/main/java/com/example/ExamplePlugin.kt @@ -0,0 +1,57 @@ +package com.example + +import com.google.inject.Inject +import dev.faststats.ErrorTracker +import dev.faststats.data.Metric +import dev.faststats.sponge.SpongeContext +import org.spongepowered.api.Server +import org.spongepowered.api.event.Listener +import org.spongepowered.api.event.lifecycle.StartedEngineEvent +import org.spongepowered.api.event.lifecycle.StoppingEngineEvent +import org.spongepowered.plugin.builtin.jvm.Plugin +import java.util.concurrent.atomic.AtomicInteger + +@Plugin("example") +class ExamplePlugin { + @Inject + private lateinit var contextBuilder: SpongeContext.Builder + + private val gameCount = AtomicInteger() + private var context: SpongeContext? = null + + @Listener + fun onServerStart(event: StartedEngineEvent) { + val context = contextBuilder + .token("YOUR_TOKEN_HERE") + .errorTrackerService(ERROR_TRACKER) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .create() + this.context = context + context.ready() // start metrics and errors submission + } + + @Listener + fun onServerStop(event: StoppingEngineEvent) { + context?.shutdown() // safely shut down configured services + } + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} diff --git a/velocity/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/velocity/example-plugin/src/main/java/com/example/ExamplePlugin.kt new file mode 100644 index 00000000..4e78935f --- /dev/null +++ b/velocity/example-plugin/src/main/java/com/example/ExamplePlugin.kt @@ -0,0 +1,58 @@ +package com.example + +import com.google.inject.Inject +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent +import com.velocitypowered.api.plugin.Plugin +import dev.faststats.ErrorTracker +import dev.faststats.data.Metric +import dev.faststats.velocity.VelocityContext +import java.util.concurrent.atomic.AtomicInteger + +@Plugin( + id = "example", + name = "Example Plugin", + version = "1.0.0", + url = "https://example.com", + authors = ["Your Name"], +) +class ExamplePlugin @Inject constructor(contextBuilder: VelocityContext.Builder) { + private val gameCount = AtomicInteger() + + private val context = contextBuilder + .token("YOUR_TOKEN_HERE") + .errorTrackerService(ERROR_TRACKER) + // .metrics(Metrics.Factory::create) // Define a minimal metrics instance without any custom metrics + .metrics { factory -> + factory + // Custom metrics require a corresponding data source in your project settings + .addMetric(Metric.number("game_count") { gameCount.get() }) + .addMetric(Metric.string("server_version") { "1.0.0" }) + + // #onFlush is invoked after successful metrics submission + // This is useful for cleaning up cached data + .onFlush { gameCount.set(0) } // reset game count on flush + + .create() + } + .create() + + @Subscribe + fun onProxyInitialize(event: ProxyInitializeEvent) { + context.ready() // start metrics and errors submission + } + + @Subscribe + fun onProxyStop(event: ProxyShutdownEvent) { + context.shutdown() // safely shut down configured services + } + + fun startGame() { + gameCount.incrementAndGet() + } + + companion object { + val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() + } +} From 36975d531c6a7289602b11bea7aabdbb1f157758 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 21 Jun 2026 09:08:05 +0200 Subject: [PATCH 2/3] Move examples to kotlin asset folder; add kotlin toolchain --- build.gradle.kts | 2 ++ .../com/example/KotlinExamplePlugin.kt} | 2 +- .../com/example/KotlinExamplePlugin.kt} | 2 +- .../ExampleMod.kt => kotlin/com/example/KotlinExampleMod.kt} | 2 +- .../com/example/KotlinExamplePlugin.kt} | 2 +- .../com/example/KotlinExampleServer.kt} | 2 +- .../ExampleMod.kt => kotlin/com/example/KotlinExampleMod.kt} | 2 +- .../example-plugin/src/main/java/com/example/ExamplePlugin.kt | 2 +- .../com/example/KotlinExamplePlugin.kt} | 2 +- .../com/example/KotlinExamplePlugin.kt} | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) rename bukkit/example-plugin/src/main/{java/com/example/ExamplePlugin.kt => kotlin/com/example/KotlinExamplePlugin.kt} (97%) rename bungeecord/example-plugin/src/main/{java/com/example/ExamplePlugin.kt => kotlin/com/example/KotlinExamplePlugin.kt} (97%) rename fabric/example-mod/src/main/{java/com/example/ExampleMod.kt => kotlin/com/example/KotlinExampleMod.kt} (97%) rename hytale/example-plugin/src/main/{java/com/example/ExamplePlugin.kt => kotlin/com/example/KotlinExamplePlugin.kt} (95%) rename minestom/example-server/src/main/{java/com/example/ExampleServer.kt => kotlin/com/example/KotlinExampleServer.kt} (98%) rename neoforge/example-mod/src/main/{java/com/example/ExampleMod.kt => kotlin/com/example/KotlinExampleMod.kt} (97%) rename sponge/example-plugin/src/main/{java/com/example/ExamplePlugin.kt => kotlin/com/example/KotlinExamplePlugin.kt} (98%) rename velocity/example-plugin/src/main/{java/com/example/ExamplePlugin.kt => kotlin/com/example/KotlinExamplePlugin.kt} (95%) diff --git a/build.gradle.kts b/build.gradle.kts index cd70a13d..9fcd9098 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("java") id("com.gradleup.shadow") version "9.4.2" apply false + kotlin("jvm") version "2.3.20" apply false } val javaVersionsOverride = mapOf( @@ -27,6 +28,7 @@ subprojects { val example = project.name.startsWith("example") if (example) { + apply { plugin("org.jetbrains.kotlin.jvm") } if (project.path != ":fabric:example-mod" && project.path != ":neoforge:example-mod") { apply { plugin("com.gradleup.shadow") } } diff --git a/bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/bukkit/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt similarity index 97% rename from bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt rename to bukkit/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt index ac6ab52e..e790510b 100644 --- a/bukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt +++ b/bukkit/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt @@ -6,7 +6,7 @@ import dev.faststats.data.Metric import org.bukkit.plugin.java.JavaPlugin import java.util.concurrent.atomic.AtomicInteger -class ExamplePlugin : JavaPlugin() { +class KotlinExamplePlugin : JavaPlugin() { private val gameCount = AtomicInteger() private val context = BukkitContext.Factory(this, "YOUR_TOKEN_HERE") diff --git a/bungeecord/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/bungeecord/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt similarity index 97% rename from bungeecord/example-plugin/src/main/java/com/example/ExamplePlugin.kt rename to bungeecord/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt index 5325aa88..339dd09e 100644 --- a/bungeecord/example-plugin/src/main/java/com/example/ExamplePlugin.kt +++ b/bungeecord/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt @@ -6,7 +6,7 @@ import dev.faststats.data.Metric import net.md_5.bungee.api.plugin.Plugin import java.util.concurrent.atomic.AtomicInteger -class ExamplePlugin : Plugin() { +class KotlinExamplePlugin : Plugin() { private val gameCount = AtomicInteger() private val context = BungeeContext.Factory(this, "YOUR_TOKEN_HERE") diff --git a/fabric/example-mod/src/main/java/com/example/ExampleMod.kt b/fabric/example-mod/src/main/kotlin/com/example/KotlinExampleMod.kt similarity index 97% rename from fabric/example-mod/src/main/java/com/example/ExampleMod.kt rename to fabric/example-mod/src/main/kotlin/com/example/KotlinExampleMod.kt index 62caee46..38e25d12 100644 --- a/fabric/example-mod/src/main/java/com/example/ExampleMod.kt +++ b/fabric/example-mod/src/main/kotlin/com/example/KotlinExampleMod.kt @@ -6,7 +6,7 @@ import dev.faststats.fabric.FabricContext import net.fabricmc.api.ModInitializer import java.util.concurrent.atomic.AtomicInteger -class ExampleMod : ModInitializer { +class KotlinExampleMod : ModInitializer { private val gameCount = AtomicInteger() private val context = FabricContext.Factory( diff --git a/hytale/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/hytale/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt similarity index 95% rename from hytale/example-plugin/src/main/java/com/example/ExamplePlugin.kt rename to hytale/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt index 27799c5d..38872487 100644 --- a/hytale/example-plugin/src/main/java/com/example/ExamplePlugin.kt +++ b/hytale/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt @@ -7,7 +7,7 @@ import dev.faststats.data.Metric import dev.faststats.hytale.HytaleContext import java.util.concurrent.atomic.AtomicInteger -class ExamplePlugin(init: JavaPluginInit) : JavaPlugin(init) { +class KotlinExamplePlugin(init: JavaPluginInit) : JavaPlugin(init) { private val gameCount = AtomicInteger() private val context = HytaleContext.Factory(this, "YOUR_TOKEN_HERE") diff --git a/minestom/example-server/src/main/java/com/example/ExampleServer.kt b/minestom/example-server/src/main/kotlin/com/example/KotlinExampleServer.kt similarity index 98% rename from minestom/example-server/src/main/java/com/example/ExampleServer.kt rename to minestom/example-server/src/main/kotlin/com/example/KotlinExampleServer.kt index b253d1dc..ff153e1a 100644 --- a/minestom/example-server/src/main/java/com/example/ExampleServer.kt +++ b/minestom/example-server/src/main/kotlin/com/example/KotlinExampleServer.kt @@ -6,7 +6,7 @@ import dev.faststats.minestom.MinestomContext import net.minestom.server.MinecraftServer import java.util.concurrent.atomic.AtomicInteger -object ExampleServer { +object KotlinExampleServer { val ERROR_TRACKER: ErrorTracker = ErrorTracker.contextAware() private val gameCount = AtomicInteger() diff --git a/neoforge/example-mod/src/main/java/com/example/ExampleMod.kt b/neoforge/example-mod/src/main/kotlin/com/example/KotlinExampleMod.kt similarity index 97% rename from neoforge/example-mod/src/main/java/com/example/ExampleMod.kt rename to neoforge/example-mod/src/main/kotlin/com/example/KotlinExampleMod.kt index 5ab7c555..b66c5691 100644 --- a/neoforge/example-mod/src/main/java/com/example/ExampleMod.kt +++ b/neoforge/example-mod/src/main/kotlin/com/example/KotlinExampleMod.kt @@ -7,7 +7,7 @@ import net.neoforged.fml.common.Mod import java.util.concurrent.atomic.AtomicInteger @Mod("example_mod") -class ExampleMod { +class KotlinExampleMod { private val gameCount = AtomicInteger() private val context = NeoForgeContext.Factory( diff --git a/nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt index 4a3dd8c4..afab6fe7 100644 --- a/nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt +++ b/nukkit/example-plugin/src/main/java/com/example/ExamplePlugin.kt @@ -6,7 +6,7 @@ import dev.faststats.data.Metric import dev.faststats.nukkit.NukkitContext import java.util.concurrent.atomic.AtomicInteger -class ExamplePlugin : PluginBase() { +class KotlinExamplePlugin : PluginBase() { private val gameCount = AtomicInteger() private val context = NukkitContext.Factory(this, "YOUR_TOKEN_HERE") diff --git a/sponge/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/sponge/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt similarity index 98% rename from sponge/example-plugin/src/main/java/com/example/ExamplePlugin.kt rename to sponge/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt index 8fc9720c..e21b1d6d 100644 --- a/sponge/example-plugin/src/main/java/com/example/ExamplePlugin.kt +++ b/sponge/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt @@ -12,7 +12,7 @@ import org.spongepowered.plugin.builtin.jvm.Plugin import java.util.concurrent.atomic.AtomicInteger @Plugin("example") -class ExamplePlugin { +class KotlinExamplePlugin { @Inject private lateinit var contextBuilder: SpongeContext.Builder diff --git a/velocity/example-plugin/src/main/java/com/example/ExamplePlugin.kt b/velocity/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt similarity index 95% rename from velocity/example-plugin/src/main/java/com/example/ExamplePlugin.kt rename to velocity/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt index 4e78935f..2e10801d 100644 --- a/velocity/example-plugin/src/main/java/com/example/ExamplePlugin.kt +++ b/velocity/example-plugin/src/main/kotlin/com/example/KotlinExamplePlugin.kt @@ -17,7 +17,7 @@ import java.util.concurrent.atomic.AtomicInteger url = "https://example.com", authors = ["Your Name"], ) -class ExamplePlugin @Inject constructor(contextBuilder: VelocityContext.Builder) { +class KotlinExamplePlugin @Inject constructor(contextBuilder: VelocityContext.Builder) { private val gameCount = AtomicInteger() private val context = contextBuilder From 43a2eaf528caa6708e30aee6f6ece36d9e450143 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 21 Jun 2026 09:47:28 +0200 Subject: [PATCH 3/3] more kotlin examples --- .../example/KotlinErrorTrackerExample.kt | 60 +++++++++++++++++ .../example/KotlinFeatureFlagExample.kt | 65 +++++++++++++++++++ .../example/KotlinMetricTypesExample.kt | 18 +++++ 3 files changed, 143 insertions(+) create mode 100644 core/example/src/main/kotlin/dev/faststats/example/KotlinErrorTrackerExample.kt create mode 100644 core/example/src/main/kotlin/dev/faststats/example/KotlinFeatureFlagExample.kt create mode 100644 core/example/src/main/kotlin/dev/faststats/example/KotlinMetricTypesExample.kt diff --git a/core/example/src/main/kotlin/dev/faststats/example/KotlinErrorTrackerExample.kt b/core/example/src/main/kotlin/dev/faststats/example/KotlinErrorTrackerExample.kt new file mode 100644 index 00000000..93538761 --- /dev/null +++ b/core/example/src/main/kotlin/dev/faststats/example/KotlinErrorTrackerExample.kt @@ -0,0 +1,60 @@ +package dev.faststats.example + +import dev.faststats.Attributes +import dev.faststats.ErrorTracker +import dev.faststats.FastStatsContext +import dev.faststats.SimpleContext +import java.lang.reflect.InvocationTargetException +import java.nio.file.AccessDeniedException + +object KotlinErrorTrackerExample { + // Context-aware: automatically tracks uncaught errors from the same class loader + val CONTEXT_AWARE: ErrorTracker = ErrorTracker.contextAware() // Filter expected noise before it is submitted + .ignoreError(InvocationTargetException::class.java, "Expected .* but got .*") + .ignoreError(AccessDeniedException::class.java) + + // Context-unaware: only tracks errors passed to trackError() manually + val CONTEXT_UNAWARE: ErrorTracker = + ErrorTracker.contextUnaware() // Replace sensitive values in error messages before submission + .anonymize("^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$", "[email hidden]") + .anonymize("Bearer [A-Za-z0-9._~+/=-]+", "Bearer [token hidden]") + .anonymize("AKIA[0-9A-Z]{16}", "[aws-key hidden]") + .anonymize("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", "[uuid hidden]") + .anonymize("([?&](?:api_?key|token|secret)=)[^&\\s]+", "$1[redacted]") + + val CONTEXT: FastStatsContext = contextFactory + .errorTrackerService(CONTEXT_AWARE) // Set the global/internal error tracker + .create() + + init { + // Attributes on the global error tracker are attached to all reports + CONTEXT_AWARE.getAttributes() + .put("environment", "production") + .put("component", "global-error-handler") + + // Tracker-wide attributes are attached to every report submitted by this tracker + CONTEXT_UNAWARE.getAttributes() + .put("component", "manual-error-handler") + + // Register an additional tracker for submission + CONTEXT.errorTrackerService().orElseThrow().registerErrorTracker(CONTEXT_UNAWARE) + } + + fun manualTracking() { + try { + throw RuntimeException("Something went wrong!") + } catch (e: Exception) { + CONTEXT_UNAWARE.trackError(e) // Add additional attributes for more context + .attributes( + Attributes.empty() + .put("operation", "manualTracking") + .put("severity", "warning") + .put("retryable", false) + ) // Define whether the error was properly handled + .handled(false) + } + } + + private val contextFactory: SimpleContext.Factory<*, *> + get() = throw UnsupportedOperationException() +} diff --git a/core/example/src/main/kotlin/dev/faststats/example/KotlinFeatureFlagExample.kt b/core/example/src/main/kotlin/dev/faststats/example/KotlinFeatureFlagExample.kt new file mode 100644 index 00000000..49e2f485 --- /dev/null +++ b/core/example/src/main/kotlin/dev/faststats/example/KotlinFeatureFlagExample.kt @@ -0,0 +1,65 @@ +package dev.faststats.example + +import dev.faststats.* +import java.time.Duration +import java.util.function.Consumer + +object KotlinFeatureFlagExample { + val CONTEXT: FastStatsContext = contextFactory + // .featureFlagService(FeatureFlagService.Factory::create) // Define a feature flag service with default settings + .featureFlagService { factory: FeatureFlagService.Factory? -> + factory!! + .attributes( + Attributes.empty() // Define global attributes + .put("version", "1.2.3") + .put("java_version", System.getProperty("java.version")) + .put("java_vendor", System.getProperty("java.vendor")) + ) + .ttl(Duration.ofMinutes(10)) // Custom cache TTL for resolved flag values + .create() + }.create() + + val SERVICE: FeatureFlagService = CONTEXT.featureFlagService().orElseThrow() + + // Define flags with default values + val NEW_COMMANDS: FeatureFlag = SERVICE.define("new_commands", false) + val COMPRESSION: FeatureFlag = SERVICE.define("compression", "zstd") + + fun usage() { + // Async: waits for the server value to be fetched + NEW_COMMANDS.whenReady().thenAccept(Consumer { enabled: Boolean? -> + if (enabled == true) { + // register new commands + } + }) + + // Non-blocking: returns the cached value if present without triggering a fetch + COMPRESSION.getCached().ifPresent(Consumer { compression: String? -> + when (compression) { + "zstd" -> {} + "lz4" -> {} + else -> {} + } + }) + + // Refresh stale values explicitly when your code decides it is needed + if (COMPRESSION.isExpired()) { + COMPRESSION.fetch().thenAccept(Consumer { }) + } + + // Opt-in/out (requires allow_specific_opt_in on server) + NEW_COMMANDS.optIn().thenAccept(Consumer { updatedValue: Boolean? -> + if (updatedValue == true) { + // react to the updated server value + } + }) + NEW_COMMANDS.optOut().thenAccept(Consumer { updatedValue: Boolean? -> + if (!updatedValue!!) { + // react to the updated server value + } + }) + } + + private val contextFactory: SimpleContext.Factory<*, *> + get() = throw UnsupportedOperationException() +} diff --git a/core/example/src/main/kotlin/dev/faststats/example/KotlinMetricTypesExample.kt b/core/example/src/main/kotlin/dev/faststats/example/KotlinMetricTypesExample.kt new file mode 100644 index 00000000..45c679ae --- /dev/null +++ b/core/example/src/main/kotlin/dev/faststats/example/KotlinMetricTypesExample.kt @@ -0,0 +1,18 @@ +package dev.faststats.example + +import dev.faststats.data.Metric + +object KotlinMetricTypesExample { + // Single value metrics + val PLAYER_COUNT: Metric = Metric.number("player_count") { 42 } + val SERVER_VERSION: Metric = Metric.string("server_version") { "1.0.0" } + val MAINTENANCE_MODE: Metric = Metric.bool("maintenance_mode") { false } + + // Array metrics + val INSTALLED_PLUGINS: Metric> = Metric.stringArray("installed_plugins") { + arrayOf("WorldEdit", "Essentials") + } + val WORLDS: Metric> = Metric.stringArray("worlds") { + arrayOf("city", "farmworld", "farmworld_nether", "famrworld_end") + } +}