From b0a6188dfdc58292e0928b03a08f603a3bca7cfe Mon Sep 17 00:00:00 2001 From: david Date: Mon, 22 Jun 2026 22:47:32 +0200 Subject: [PATCH 01/10] refactor logger to use platform specific logging --- .../dev/faststats/bukkit/BukkitContext.java | 13 +++-- .../dev/faststats/bungee/BungeeContext.java | 15 ++++-- .../dev/faststats/config/SimpleConfig.java | 38 ++++++------- .../java/dev/faststats/SimpleContext.java | 16 ++++-- .../faststats/SimpleErrorTrackerService.java | 6 +-- .../java/dev/faststats/SubmissionService.java | 5 +- .../java/dev/faststats/internal/Logger.java | 40 ++++++++++---- .../dev/faststats/internal/LoggerFactory.java | 25 ++++----- .../internal/PlatformLoggerFactory.java | 49 +++++++++++++++++ .../dev/faststats/internal/SimpleLogger.java | 46 ---------------- .../internal/SimpleLoggerFactory.java | 8 --- core/src/main/java/module-info.java | 1 - .../test/java/dev/faststats/MockContext.java | 15 ++++-- .../dev/faststats/fabric/FabricContext.java | 29 ++++++++-- fabric/src/main/java/module-info.java | 1 + .../dev/faststats/hytale/HytaleContext.java | 21 ++++++-- .../faststats/hytale/logger/HytaleLogger.java | 54 ------------------- .../hytale/logger/HytaleLoggerFactory.java | 11 ---- hytale/src/main/java/module-info.java | 2 - .../dev.faststats.core.internal.LoggerFactory | 1 - .../faststats/minestom/MinestomContext.java | 27 ++++++++-- .../faststats/neoforge/NeoForgeContext.java | 20 +++++-- neoforge/src/main/java/module-info.java | 1 + .../dev/faststats/nukkit/NukkitContext.java | 18 +++++-- .../dev/faststats/sponge/SpongeConfig.java | 35 ++++++------ .../dev/faststats/sponge/SpongeContext.java | 30 +++++++++-- .../faststats/velocity/VelocityContext.java | 29 ++++++++-- 27 files changed, 317 insertions(+), 239 deletions(-) create mode 100644 core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java delete mode 100644 core/src/main/java/dev/faststats/internal/SimpleLogger.java delete mode 100644 core/src/main/java/dev/faststats/internal/SimpleLoggerFactory.java delete mode 100644 hytale/src/main/java/dev/faststats/hytale/logger/HytaleLogger.java delete mode 100644 hytale/src/main/java/dev/faststats/hytale/logger/HytaleLoggerFactory.java delete mode 100644 hytale/src/main/resources/META-INF/services/dev.faststats.core.internal.LoggerFactory diff --git a/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java b/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java index 6d3be80c..9a8ccd3e 100644 --- a/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java +++ b/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java @@ -3,6 +3,8 @@ import dev.faststats.SimpleContext; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.Contract; @@ -20,8 +22,8 @@ public final class BukkitContext extends SimpleContext { private final Set cancellations = new CopyOnWriteArraySet<>(); private final Plugin plugin; - private BukkitContext(final Factory factory, final Plugin plugin, @Token final String token) { - super(factory, SimpleConfig.read(getConfigPath(plugin)), "bukkit", token); + private BukkitContext(final Factory factory, final LoggerFactory loggerFactory, final Plugin plugin, @Token final String token) { + super(factory, loggerFactory, SimpleConfig.read(getConfigPath(plugin), loggerFactory), "bukkit", token); this.plugin = plugin; initializeServices(factory); } @@ -58,7 +60,7 @@ private static Path getPluginsFolder(final Plugin plugin) { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -99,7 +101,10 @@ public Factory(final Plugin plugin, @Token final String token) { @Override public BukkitContext create() { - return new BukkitContext(this, plugin, token); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + plugin.getLogger().log(level.getLevel(), message, throwable); + }); + return new BukkitContext(this, loggerFactory, plugin, token); } } diff --git a/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java b/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java index 30a83dda..99e26210 100644 --- a/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java +++ b/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java @@ -5,6 +5,8 @@ import dev.faststats.SimpleMetrics; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.scheduler.ScheduledTask; import org.jetbrains.annotations.Contract; @@ -22,8 +24,10 @@ public final class BungeeContext extends SimpleContext { private final Set tasks = new CopyOnWriteArraySet<>(); private final Plugin plugin; - private BungeeContext(final Factory factory, final Plugin plugin, @Token final String token) { - super(factory, SimpleConfig.read(plugin.getProxy().getPluginsFolder().toPath().resolve("faststats").resolve("config.properties")), "bungeecord", token); + private BungeeContext(final Factory factory, final LoggerFactory loggerFactory, final Plugin plugin, @Token final String token) { + super(factory, loggerFactory, SimpleConfig.read(plugin.getProxy().getPluginsFolder().toPath() + .resolve("faststats").resolve("config.properties"), loggerFactory + ), "bungeecord", token); this.plugin = plugin; initializeServices(factory); } @@ -41,7 +45,7 @@ public Metrics create() throws IllegalStateException { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -72,7 +76,10 @@ public Factory(final Plugin plugin, @Token final String token) { @Override public BungeeContext create() { - return new BungeeContext(this, plugin, token); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + plugin.getLogger().log(level.getLevel(), message, throwable); + }); + return new BungeeContext(this, loggerFactory, plugin, token); } } } diff --git a/config/src/main/java/dev/faststats/config/SimpleConfig.java b/config/src/main/java/dev/faststats/config/SimpleConfig.java index 370cdb92..70c581a5 100644 --- a/config/src/main/java/dev/faststats/config/SimpleConfig.java +++ b/config/src/main/java/dev/faststats/config/SimpleConfig.java @@ -1,6 +1,7 @@ package dev.faststats.config; import dev.faststats.Config; +import dev.faststats.SimpleContext; import dev.faststats.internal.Logger; import dev.faststats.internal.LoggerFactory; import org.jetbrains.annotations.ApiStatus; @@ -29,7 +30,6 @@ public record SimpleConfig( boolean errorTracking, boolean firstRun ) implements Config { - private static final Logger logger = LoggerFactory.factory().getLogger(SimpleConfig.class); private static final int CONFIG_VERSION = 2; private static final String COMMENT = """ @@ -61,7 +61,9 @@ public record SimpleConfig( until you restart the server to allow you to opt out if you prefer."""; @Contract(mutates = "io") - public static SimpleConfig read(final Path file) throws RuntimeException { + public static SimpleConfig read(final Path file, final LoggerFactory factory) throws RuntimeException { + final var logger = factory.getLogger(SimpleConfig.class); + final var debugFlag = Boolean.getBoolean("faststats.debug"); final var enabledFlag = Boolean.parseBoolean(System.getProperty("faststats.enabled", "true")); @@ -74,15 +76,13 @@ public static SimpleConfig read(final Path file) throws RuntimeException { final var uuid = UUID.fromString(corrected); if (!value.equals(uuid.toString())) saveConfig.set(true); return uuid; - }); - final var configVersion = parse(properties, saveConfig, "configVersion", null, Integer::parseInt); - final boolean enabled = parse(properties, saveConfig, "enabled", () -> true, Boolean::parseBoolean); - final boolean submitMetrics = parse(properties, saveConfig, "submitMetrics", () -> true, Boolean::parseBoolean); - final boolean errorTracking = parse(properties, saveConfig, "submitErrors", () -> true, Boolean::parseBoolean); - final boolean additionalMetrics = parse(properties, saveConfig, "submitAdditionalMetrics", () -> true, Boolean::parseBoolean); - final boolean debug = parse(properties, saveConfig, "debug", () -> false, Boolean::parseBoolean); - - logger.setFilter(level -> debug || debugFlag); + }, logger); + final var configVersion = parse(properties, saveConfig, "configVersion", null, Integer::parseInt, logger); + final boolean enabled = parse(properties, saveConfig, "enabled", () -> true, Boolean::parseBoolean, logger); + final boolean submitMetrics = parse(properties, saveConfig, "submitMetrics", () -> true, Boolean::parseBoolean, logger); + final boolean errorTracking = parse(properties, saveConfig, "submitErrors", () -> true, Boolean::parseBoolean, logger); + final boolean additionalMetrics = parse(properties, saveConfig, "submitAdditionalMetrics", () -> true, Boolean::parseBoolean, logger); + final boolean debug = parse(properties, saveConfig, "debug", () -> false, Boolean::parseBoolean, logger); if (configVersion == null || configVersion < CONFIG_VERSION) saveConfig.set(true); else if (configVersion > CONFIG_VERSION) saveConfig.set(false); @@ -123,13 +123,14 @@ public static SimpleConfig read(final Path file) throws RuntimeException { } // fixme: this code sucks ass - @Contract(value = "_, _, _, !null, _ -> !null") + @Contract(value = "_, _, _, !null, _, _-> !null") private static @Nullable T parse( @Nullable final Properties properties, final AtomicBoolean saveConfig, final String key, @Nullable final Supplier defaultValue, - final Function parser + final Function parser, + final Logger logger ) { if (properties == null) { saveConfig.set(true); @@ -161,8 +162,7 @@ public static SimpleConfig read(final Path file) throws RuntimeException { } } - @SuppressWarnings("PatternValidation") - public boolean preSubmissionStart(final String name) { + public boolean preSubmissionStart(final SimpleContext context) { if (Boolean.getBoolean("faststats.first-run")) return false; if (firstRun()) { @@ -170,10 +170,10 @@ public boolean preSubmissionStart(final String name) { final var split = ONBOARDING_MESSAGE.split("\n"); for (final var s : split) if (s.length() > separatorLength) separatorLength = s.length(); - final var logger = LoggerFactory.factory().getLogger(name); - logger.info("-".repeat(separatorLength)); - for (final var s : split) logger.info(s); - logger.info("-".repeat(separatorLength)); + final var logger = context.getLoggerFactory().getLogger(getClass()); + logger.print(Logger.LogLevel.INFO, null, "-".repeat(separatorLength)); + for (final var s : split) logger.print(Logger.LogLevel.INFO, null, s); + logger.print(Logger.LogLevel.INFO, null, "-".repeat(separatorLength)); System.setProperty("faststats.first-run", "true"); return false; diff --git a/core/src/main/java/dev/faststats/SimpleContext.java b/core/src/main/java/dev/faststats/SimpleContext.java index 79a394d5..6d5a0de3 100644 --- a/core/src/main/java/dev/faststats/SimpleContext.java +++ b/core/src/main/java/dev/faststats/SimpleContext.java @@ -16,12 +16,13 @@ import java.util.function.Function; public non-sealed abstract class SimpleContext implements FastStatsContext { - private final Logger logger = LoggerFactory.factory().getLogger(getClass()); - private final @Token String token; private final Config config; private final SdkInfo sdkInfo; + private final LoggerFactory loggerFactory; + private final Logger logger; + protected volatile boolean ready = false; private @Nullable Metrics metrics; @@ -40,11 +41,14 @@ public non-sealed abstract class SimpleContext implements FastStatsContext { * @throws UncheckedIOException if an IO error occurs * @since 0.24.0 */ - protected SimpleContext(final Factory factory, final Config config, final String name, @Token final String token) throws IllegalArgumentException { + protected SimpleContext(final Factory factory, final LoggerFactory loggerFactory, final Config config, final String name, @Token final String token) throws IllegalArgumentException { if (!token.matches(Token.PATTERN)) throw new IllegalArgumentException("Invalid token '" + token + "', must match '" + Token.PATTERN + "'"); - logger.setFilter(level -> config.debug()); + this.loggerFactory = loggerFactory; + loggerFactory.setDebug(config.debug()); + this.logger = loggerFactory.getLogger(getClass()); + this.sdkInfo = constructSdkInfo(name); this.config = config; this.token = token; @@ -90,6 +94,10 @@ private SdkInfo constructSdkInfo(final String name) throws UncheckedIOException, } } + public LoggerFactory getLoggerFactory() { + return loggerFactory; + } + protected abstract boolean preSubmissionStart(); @Contract(pure = true) diff --git a/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java b/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java index c1ec0432..3ac1386d 100644 --- a/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java +++ b/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java @@ -2,8 +2,6 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import dev.faststats.internal.Logger; -import dev.faststats.internal.LoggerFactory; import org.jetbrains.annotations.VisibleForTesting; import org.jspecify.annotations.Nullable; @@ -14,7 +12,6 @@ final class SimpleErrorTrackerService extends SubmissionService implements ErrorTrackerService { private static Thread.@Nullable UncaughtExceptionHandler originalHandler; - private static final Logger logger = LoggerFactory.factory().getLogger(SimpleErrorTrackerService.class); private static final Set DISPATCHER_TRACKERS = new CopyOnWriteArraySet<>(); private final Set errorTrackers = new CopyOnWriteArraySet<>(); @@ -27,7 +24,6 @@ final class SimpleErrorTrackerService extends SubmissionService implements Error SimpleErrorTrackerService(final SimpleContext context, final ErrorTracker globalErrorTracker) { super(context); - logger.setFilter(level -> context.getConfig().debug()); // fixme: awful practice this.globalErrorTracker = ((SimpleErrorTracker) globalErrorTracker); } @@ -66,7 +62,7 @@ private static void handleUncaughtException(final Thread thread, final Throwable tracker.trackError(error).handled(false); tracker.getContextErrorHandler().ifPresent(handler -> handler.accept(loader, error)); } catch (final Throwable t) { - logger.error("Failed to dispatch uncaught error to tracker", t); + // logger.error("Failed to dispatch uncaught error to tracker", t); // fixme } } diff --git a/core/src/main/java/dev/faststats/SubmissionService.java b/core/src/main/java/dev/faststats/SubmissionService.java index ba83df4d..dded6cf6 100644 --- a/core/src/main/java/dev/faststats/SubmissionService.java +++ b/core/src/main/java/dev/faststats/SubmissionService.java @@ -2,7 +2,6 @@ import com.google.gson.JsonElement; import dev.faststats.internal.Logger; -import dev.faststats.internal.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -25,12 +24,12 @@ abstract class SubmissionService { .version(HttpClient.Version.HTTP_1_1) .build(); - protected final Logger logger = LoggerFactory.factory().getLogger(getClass()); + protected final Logger logger; protected final SimpleContext context; SubmissionService(final SimpleContext context) { this.context = context; - logger.setFilter(level -> context.getConfig().debug()); + this.logger = context.getLoggerFactory().getLogger(getClass()); } protected abstract String serverType(); diff --git a/core/src/main/java/dev/faststats/internal/Logger.java b/core/src/main/java/dev/faststats/internal/Logger.java index 3f5f232e..7ee7eb07 100644 --- a/core/src/main/java/dev/faststats/internal/Logger.java +++ b/core/src/main/java/dev/faststats/internal/Logger.java @@ -3,25 +3,43 @@ import org.intellij.lang.annotations.PrintFormat; import org.jspecify.annotations.Nullable; -import java.util.function.Predicate; import java.util.logging.Level; public interface Logger { - void setLevel(Level level); + default void error(@PrintFormat final String message, @Nullable final Throwable t, final Object... args) { + debug(LogLevel.ERROR, message, t, args); + } - boolean isLoggable(Level level); + default void info(@PrintFormat final String message, final Object... args) { + debug(LogLevel.INFO, message, null, args); + } - void setFilter(@Nullable Predicate filter); + default void warn(@PrintFormat final String message, final Object... args) { + debug(LogLevel.WARN, message, null, args); + } - void error(@PrintFormat final String message, @Nullable final Throwable throwable, @Nullable final Object... args); + private void debug(final LogLevel level, @PrintFormat final String message, @Nullable final Throwable t, final Object... args) { + if (factory().isDebug()) print(level, t, "[" + caller() + "] " + message.formatted(args)); + } - void log(final Level level, @PrintFormat final String message, @Nullable final Object... args); + String caller(); + + LoggerFactory factory(); - default void info(@PrintFormat final String message, @Nullable final Object... args) { - log(Level.INFO, message, args); - } + void print(LogLevel level, @Nullable Throwable t, String message); + + enum LogLevel { + ERROR(Level.SEVERE), + INFO(Level.INFO), + WARN(Level.WARNING); + private final Level level; + + LogLevel(final Level level) { + this.level = level; + } - default void warn(@PrintFormat final String message, @Nullable final Object... args) { - log(Level.WARNING, message, args); + public Level getLevel() { + return level; + } } } diff --git a/core/src/main/java/dev/faststats/internal/LoggerFactory.java b/core/src/main/java/dev/faststats/internal/LoggerFactory.java index eb927e03..d12904ba 100644 --- a/core/src/main/java/dev/faststats/internal/LoggerFactory.java +++ b/core/src/main/java/dev/faststats/internal/LoggerFactory.java @@ -2,24 +2,17 @@ import org.jetbrains.annotations.Contract; -import java.util.ServiceLoader; - -public interface LoggerFactory { - @Contract(pure = true) - static LoggerFactory factory() { - final class Holder { - private static final LoggerFactory INSTANCE = ServiceLoader.load(LoggerFactory.class) - .findFirst() - .orElseGet(SimpleLoggerFactory::new); - } - return Holder.INSTANCE; - } +public abstract class LoggerFactory { + private volatile boolean debug; @Contract(value = "_ -> new", pure = true) - default Logger getLogger(final Class clazz) { - return getLogger(clazz.getName()); + public abstract Logger getLogger(Class clazz); + + public boolean isDebug() { + return debug; } - @Contract(value = "_ -> new", pure = true) - Logger getLogger(String name); + public void setDebug(final boolean debug) { + this.debug = debug; + } } diff --git a/core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java b/core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java new file mode 100644 index 00000000..adefc957 --- /dev/null +++ b/core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java @@ -0,0 +1,49 @@ +package dev.faststats.internal; + +import org.jspecify.annotations.Nullable; + +public abstract class PlatformLoggerFactory extends LoggerFactory { + @FunctionalInterface + public interface Printer { + void print(Logger.LogLevel level, @Nullable Throwable throwable, String message); + } + + public static PlatformLoggerFactory create(final Printer printer) { + return new PlatformLoggerFactory() { + @Override + public Logger getLogger(final Class clazz) { + return new PlatformLogger(clazz.getName()); + } + + @Override + protected void print(final Logger.LogLevel level, @Nullable final Throwable throwable, final String message) { + printer.print(level, throwable, message); + } + }; + } + + protected abstract void print(Logger.LogLevel level, @Nullable Throwable throwable, String message); + + private final class PlatformLogger implements Logger { + private final String caller; + + private PlatformLogger(final String caller) { + this.caller = caller; + } + + @Override + public String caller() { + return caller; + } + + @Override + public LoggerFactory factory() { + return PlatformLoggerFactory.this; + } + + @Override + public void print(final LogLevel level, @Nullable final Throwable throwable, final String message) { + PlatformLoggerFactory.this.print(level, throwable, message); + } + } +} diff --git a/core/src/main/java/dev/faststats/internal/SimpleLogger.java b/core/src/main/java/dev/faststats/internal/SimpleLogger.java deleted file mode 100644 index 147b410d..00000000 --- a/core/src/main/java/dev/faststats/internal/SimpleLogger.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.faststats.internal; - -import org.jspecify.annotations.Nullable; - -import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -class SimpleLogger implements Logger { - private final java.util.logging.Logger logger; - - public SimpleLogger(final String name) { - this.logger = java.util.logging.Logger.getLogger(name); - } - - @Override - public void setLevel(final Level level) { - logger.setLevel(level); - } - - @Override - public boolean isLoggable(final Level level) { - return logger.isLoggable(level); - } - - @Override - public void setFilter(@Nullable final Predicate filter) { - logger.setFilter(filter != null ? record -> filter.test(record.getLevel()) : null); - } - - @Override - public void error(final String message, @Nullable final Throwable throwable, @Nullable final Object... args) { - if (throwable != null) { - if (!logger.isLoggable(Level.SEVERE)) return; - final var logRecord = new LogRecord(Level.SEVERE, message.formatted(args)); - logRecord.setLoggerName(logger.getName()); - logRecord.setThrown(throwable); - logger.log(logRecord); - } else log(Level.SEVERE, message, args); - } - - @Override - public void log(final Level level, final String message, @Nullable final Object... args) { - logger.log(level, () -> message.formatted(args)); - } -} diff --git a/core/src/main/java/dev/faststats/internal/SimpleLoggerFactory.java b/core/src/main/java/dev/faststats/internal/SimpleLoggerFactory.java deleted file mode 100644 index dcb8b9cf..00000000 --- a/core/src/main/java/dev/faststats/internal/SimpleLoggerFactory.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.faststats.internal; - -final class SimpleLoggerFactory implements LoggerFactory { - @Override - public Logger getLogger(final String name) { - return new SimpleLogger(name); - } -} diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 34bcc42a..4b9f260c 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -14,5 +14,4 @@ requires static org.jspecify; uses dev.faststats.SdkInfo.UserAgentProvider; - uses dev.faststats.internal.LoggerFactory; } diff --git a/core/src/test/java/dev/faststats/MockContext.java b/core/src/test/java/dev/faststats/MockContext.java index 5a077091..a88c3b54 100644 --- a/core/src/test/java/dev/faststats/MockContext.java +++ b/core/src/test/java/dev/faststats/MockContext.java @@ -1,5 +1,9 @@ package dev.faststats; +import dev.faststats.internal.Logger; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; + import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; @@ -17,8 +21,8 @@ public final class MockContext extends SimpleContext { }); private final Set> tasks = new CopyOnWriteArraySet<>(); - private MockContext(final Factory factory) throws IllegalArgumentException { - super(factory, factory.config(), "test", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + private MockContext(final Factory factory, final LoggerFactory loggerFactory) throws IllegalArgumentException { + super(factory, loggerFactory, factory.config(), "test", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); this.factory = factory; initializeServices(factory); } @@ -106,7 +110,12 @@ public Factory firstRun() { @Override public MockContext create() { - return new MockContext(this); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var output = level == Logger.LogLevel.ERROR ? System.err : System.out; + output.println("[" + level.name() + "] " + message); + if (throwable != null) throwable.printStackTrace(output); + }); + return new MockContext(this, loggerFactory); } private Config config() { diff --git a/fabric/src/main/java/dev/faststats/fabric/FabricContext.java b/fabric/src/main/java/dev/faststats/fabric/FabricContext.java index 369f3b53..79575a3b 100644 --- a/fabric/src/main/java/dev/faststats/fabric/FabricContext.java +++ b/fabric/src/main/java/dev/faststats/fabric/FabricContext.java @@ -5,11 +5,13 @@ import dev.faststats.SimpleMetrics; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.PlatformLoggerFactory; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import org.jetbrains.annotations.Contract; +import org.slf4j.LoggerFactory; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -32,8 +34,10 @@ public final class FabricContext extends SimpleContext { private final Set> tasks = new CopyOnWriteArraySet<>(); private final ModContainer mod; - private FabricContext(final Factory factory, final String modId, @Token final String token) { - super(factory, SimpleConfig.read(FabricLoader.getInstance().getConfigDir().resolve("faststats").resolve("config.properties")), "fabric", token); + private FabricContext(final Factory factory, final dev.faststats.internal.LoggerFactory loggerFactory, final String modId, @Token final String token) { + super(factory, loggerFactory, SimpleConfig.read(FabricLoader.getInstance().getConfigDir() + .resolve("faststats").resolve("config.properties"), loggerFactory + ), "fabric", token); this.mod = FabricLoader.getInstance().getModContainer(modId).orElseThrow(() -> { return new IllegalArgumentException("Mod not found: " + modId); }); @@ -66,7 +70,7 @@ public Metrics create() throws IllegalStateException { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -98,7 +102,24 @@ public Factory(final String modId, @Token final String token) { @Override public FabricContext create() { - return new FabricContext(this, modId, token); + final var logger = LoggerFactory.getLogger(modId); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + switch (level) { + case INFO -> { + if (throwable == null) logger.info(message); + else logger.info(message, throwable); + } + case ERROR -> { + if (throwable == null) logger.error(message); + else logger.error(message, throwable); + } + case WARN -> { + if (throwable == null) logger.warn(message); + else logger.warn(message, throwable); + } + } + }); + return new FabricContext(this, loggerFactory, modId, token); } } } diff --git a/fabric/src/main/java/module-info.java b/fabric/src/main/java/module-info.java index 2ca33731..4f28b6c9 100644 --- a/fabric/src/main/java/module-info.java +++ b/fabric/src/main/java/module-info.java @@ -7,6 +7,7 @@ requires com.google.gson; requires dev.faststats.config; requires dev.faststats; + requires java.logging; requires net.fabricmc.loader; requires org.slf4j; diff --git a/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java b/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java index 88efd840..4c6d5b07 100644 --- a/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java +++ b/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java @@ -8,6 +8,8 @@ import dev.faststats.SimpleMetrics; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; import org.jetbrains.annotations.Contract; import java.util.Set; @@ -24,8 +26,10 @@ public final class HytaleContext extends SimpleContext { private final Set registrations = new CopyOnWriteArraySet<>(); private final JavaPlugin plugin; - private HytaleContext(final Factory factory, final JavaPlugin plugin, @Token final String token) { - super(factory, SimpleConfig.read(plugin.getDataDirectory().toAbsolutePath().getParent().resolve("faststats").resolve("config.properties")), "hytale", token); + private HytaleContext(final Factory factory, final LoggerFactory loggerFactory, final JavaPlugin plugin, @Token final String token) { + super(factory, loggerFactory, SimpleConfig.read(plugin.getDataDirectory().toAbsolutePath().getParent() + .resolve("faststats").resolve("config.properties"), loggerFactory + ), "hytale", token); this.plugin = plugin; initializeServices(factory); } @@ -44,7 +48,7 @@ public Metrics create() throws IllegalStateException { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -76,7 +80,16 @@ public Factory(final JavaPlugin plugin, @Token final String token) { @Override public HytaleContext create() { - return new HytaleContext(this, plugin, token); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var api = switch (level) { + case INFO -> plugin.getLogger().atFine(); + case ERROR -> plugin.getLogger().atSevere(); + case WARN -> plugin.getLogger().atWarning(); + }; + if (throwable == null) api.log(message); + else api.log(message, throwable); + }); + return new HytaleContext(this, loggerFactory, plugin, token); } } } diff --git a/hytale/src/main/java/dev/faststats/hytale/logger/HytaleLogger.java b/hytale/src/main/java/dev/faststats/hytale/logger/HytaleLogger.java deleted file mode 100644 index 6213d4e6..00000000 --- a/hytale/src/main/java/dev/faststats/hytale/logger/HytaleLogger.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.faststats.hytale.logger; - -import dev.faststats.internal.Logger; -import org.intellij.lang.annotations.PrintFormat; -import org.jspecify.annotations.Nullable; - -import java.util.function.Predicate; -import java.util.logging.Level; - -final class HytaleLogger implements Logger { - private final com.hypixel.hytale.logger.HytaleLogger logger; - private volatile @Nullable Predicate filter; - - HytaleLogger(final String name) { - this.logger = com.hypixel.hytale.logger.HytaleLogger.get(name); - } - - @Override - public void setLevel(final Level level) { - logger.setLevel(level); - } - - @Override - public boolean isLoggable(final Level level) { - final var loggerLevel = logger.getLevel(); - if (level.intValue() < loggerLevel.intValue()) return false; - - final var currentFilter = filter; - return currentFilter != null && currentFilter.test(level); - } - - @Override - public void setFilter(@Nullable final Predicate filter) { - this.filter = filter; - } - - @Override - public void error(@PrintFormat final String message, @Nullable final Throwable throwable, @Nullable final Object... args) { - if (!isLoggable(Level.SEVERE)) return; - - final var api = logger.atSevere(); - if (throwable != null) { - api.withCause(throwable).logVarargs(message, args); - return; - } - api.logVarargs(message, args); - } - - @Override - public void log(final Level level, @PrintFormat final String message, @Nullable final Object... args) { - if (!isLoggable(level)) return; - logger.at(level).logVarargs(message, args); - } -} diff --git a/hytale/src/main/java/dev/faststats/hytale/logger/HytaleLoggerFactory.java b/hytale/src/main/java/dev/faststats/hytale/logger/HytaleLoggerFactory.java deleted file mode 100644 index 5af5e688..00000000 --- a/hytale/src/main/java/dev/faststats/hytale/logger/HytaleLoggerFactory.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.faststats.hytale.logger; - -import dev.faststats.internal.Logger; -import dev.faststats.internal.LoggerFactory; - -public final class HytaleLoggerFactory implements LoggerFactory { - @Override - public Logger getLogger(final String name) { - return new HytaleLogger(name); - } -} diff --git a/hytale/src/main/java/module-info.java b/hytale/src/main/java/module-info.java index a9372166..28671a57 100644 --- a/hytale/src/main/java/module-info.java +++ b/hytale/src/main/java/module-info.java @@ -11,6 +11,4 @@ requires static org.jetbrains.annotations; requires static org.jspecify; - - provides dev.faststats.internal.LoggerFactory with dev.faststats.hytale.logger.HytaleLoggerFactory; } diff --git a/hytale/src/main/resources/META-INF/services/dev.faststats.core.internal.LoggerFactory b/hytale/src/main/resources/META-INF/services/dev.faststats.core.internal.LoggerFactory deleted file mode 100644 index 9affb6ba..00000000 --- a/hytale/src/main/resources/META-INF/services/dev.faststats.core.internal.LoggerFactory +++ /dev/null @@ -1 +0,0 @@ -dev.faststats.hytale.logger.HytaleLoggerFactory diff --git a/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java b/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java index 7b278de9..bbc1fdce 100644 --- a/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java +++ b/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java @@ -5,10 +5,12 @@ import dev.faststats.SimpleContext; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.PlatformLoggerFactory; import net.minestom.server.MinecraftServer; import net.minestom.server.timer.Task; import net.minestom.server.timer.TaskSchedule; import org.jetbrains.annotations.Contract; +import org.slf4j.LoggerFactory; import java.nio.file.Path; import java.util.Set; @@ -23,8 +25,8 @@ public final class MinestomContext extends SimpleContext { private final Set tasks = new CopyOnWriteArraySet<>(); - MinestomContext(final Factory factory, @Token final String token) { - super(factory, SimpleConfig.read(Path.of("faststats", "config.properties")), "minestom", token); + MinestomContext(final Factory factory, final dev.faststats.internal.LoggerFactory loggerFactory, @Token final String token) { + super(factory, loggerFactory, SimpleConfig.read(Path.of("faststats", "config.properties"), loggerFactory), "minestom", token); initializeServices(factory); } @@ -49,7 +51,7 @@ protected MinestomMetrics.Factory metricsFactory() { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -83,7 +85,24 @@ public Factory(@Token final String token) { @Override public MinestomContext create() { - return new MinestomContext(this, token); + final var logger = LoggerFactory.getLogger("FastStats"); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + switch (level) { + case INFO -> { + if (throwable == null) logger.info(message); + else logger.info(message, throwable); + } + case ERROR -> { + if (throwable == null) logger.error(message); + else logger.error(message, throwable); + } + case WARN -> { + if (throwable == null) logger.warn(message); + else logger.warn(message, throwable); + } + } + }); + return new MinestomContext(this, loggerFactory, token); } } } diff --git a/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java b/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java index 6e185e66..9b6272b8 100644 --- a/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java +++ b/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java @@ -5,6 +5,8 @@ import dev.faststats.SimpleMetrics; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModList; import net.neoforged.fml.loading.FMLEnvironment; @@ -36,8 +38,10 @@ public final class NeoForgeContext extends SimpleContext { private final Set> tasks = new CopyOnWriteArraySet<>(); private final IModInfo mod; - private NeoForgeContext(final Factory factory, final String modId, @Token final String token) { - super(factory, SimpleConfig.read(FMLPaths.CONFIGDIR.get().resolve("faststats").resolve("config.properties")), "neoforge", token); + private NeoForgeContext(final Factory factory, final LoggerFactory loggerFactory, final String modId, @Token final String token) { + super(factory, loggerFactory, SimpleConfig.read(FMLPaths.CONFIGDIR.get() + .resolve("faststats").resolve("config.properties"), loggerFactory + ), "neoforge", token); this.mod = ModList.get().getModContainerById(modId).map(ModContainer::getModInfo).orElseThrow(() -> { return new IllegalArgumentException("Mod not found: " + modId); }); @@ -67,7 +71,7 @@ public Metrics create() throws IllegalStateException { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -99,7 +103,15 @@ public Factory(final String modId, @Token final String token) { @Override public NeoForgeContext create() { - return new NeoForgeContext(this, modId, token); + final var logger = org.slf4j.LoggerFactory.getLogger(modId); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + switch (level) { + case INFO -> logger.info(message, throwable); + case ERROR -> logger.error(message, throwable); + case WARN -> logger.warn(message, throwable); + } + }); + return new NeoForgeContext(this, loggerFactory, modId, token); } } } diff --git a/neoforge/src/main/java/module-info.java b/neoforge/src/main/java/module-info.java index 96f97049..0835ea36 100644 --- a/neoforge/src/main/java/module-info.java +++ b/neoforge/src/main/java/module-info.java @@ -9,6 +9,7 @@ requires dev.faststats; requires fml_loader; requires net.neoforged.bus; + requires org.slf4j; requires static org.jetbrains.annotations; requires static org.jspecify; diff --git a/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java b/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java index 38ff91b5..14d6a896 100644 --- a/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java +++ b/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java @@ -7,6 +7,8 @@ import dev.faststats.SimpleMetrics; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; import org.jetbrains.annotations.Contract; import java.nio.file.Path; @@ -23,8 +25,9 @@ public final class NukkitContext extends SimpleContext { private final Set tasks = new CopyOnWriteArraySet<>(); final PluginBase plugin; - private NukkitContext(final Factory factory, final PluginBase plugin, @Token final String token) { - super(factory, SimpleConfig.read(Path.of(plugin.getServer().getPluginPath(), "faststats", "config.properties")), "nukkit", token); + private NukkitContext(final Factory factory, final LoggerFactory loggerFactory, final PluginBase plugin, @Token final String token) { + super(factory, loggerFactory, SimpleConfig.read(Path.of(plugin.getServer().getPluginPath()) + .resolve("faststats").resolve("config.properties"), loggerFactory), "nukkit", token); this.plugin = plugin; initializeServices(factory); } @@ -42,7 +45,7 @@ public Metrics create() throws IllegalStateException { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -76,7 +79,14 @@ public Factory(final PluginBase plugin, @Token final String token) { @Override public NukkitContext create() { - return new NukkitContext(this, plugin, token); + final var loggerFactory = PlatformLoggerFactory.create((level, t, message) -> { + switch (level) { + case INFO -> plugin.getLogger().info(message, t); + case ERROR -> plugin.getLogger().error(message, t); + case WARN -> plugin.getLogger().warning(message, t); + } + }); + return new NukkitContext(this, loggerFactory, plugin, token); } } diff --git a/sponge/src/main/java/dev/faststats/sponge/SpongeConfig.java b/sponge/src/main/java/dev/faststats/sponge/SpongeConfig.java index 05ffe4b3..c8e8766b 100644 --- a/sponge/src/main/java/dev/faststats/sponge/SpongeConfig.java +++ b/sponge/src/main/java/dev/faststats/sponge/SpongeConfig.java @@ -31,7 +31,7 @@ public record SpongeConfig( boolean errorTracking, boolean firstRun ) implements Config { - private static final Logger logger = LoggerFactory.factory().getLogger(SpongeConfig.class); + private static volatile boolean loggingEnabled; private static final int CONFIG_VERSION = 2; private static final String COMMENT = """ @@ -64,7 +64,8 @@ public record SpongeConfig( """; @Contract(mutates = "io") - public static SpongeConfig read(final PluginContainer plugin, final Path file) throws RuntimeException { + public static SpongeConfig read(final PluginContainer plugin, final Path file, final LoggerFactory loggerFactory) throws RuntimeException { + final var logger = loggerFactory.getLogger(SpongeConfig.class); final var debugFlag = Boolean.getBoolean("faststats.debug"); final var properties = readOrEmpty(file); @@ -76,14 +77,12 @@ public static SpongeConfig read(final PluginContainer plugin, final Path file) t final var uuid = UUID.fromString(corrected); if (!value.equals(uuid.toString())) saveConfig.set(true); return uuid; - }); - final var configVersion = parse(properties, saveConfig, "configVersion", null, Integer::parseInt); - final boolean submitMetrics = parse(properties, saveConfig, "submitMetrics", () -> true, Boolean::parseBoolean); - final boolean errorTracking = parse(properties, saveConfig, "submitErrors", () -> true, Boolean::parseBoolean); - final boolean additionalMetrics = parse(properties, saveConfig, "submitAdditionalMetrics", () -> true, Boolean::parseBoolean); - final boolean debug = parse(properties, saveConfig, "debug", () -> true, Boolean::parseBoolean); - - logger.setFilter(level -> debug || debugFlag); + }, logger); + final var configVersion = parse(properties, saveConfig, "configVersion", null, Integer::parseInt, logger); + final boolean submitMetrics = parse(properties, saveConfig, "submitMetrics", () -> true, Boolean::parseBoolean, logger); + final boolean errorTracking = parse(properties, saveConfig, "submitErrors", () -> true, Boolean::parseBoolean, logger); + final boolean additionalMetrics = parse(properties, saveConfig, "submitAdditionalMetrics", () -> true, Boolean::parseBoolean, logger); + final boolean debug = parse(properties, saveConfig, "debug", () -> true, Boolean::parseBoolean, logger); if (configVersion == null || configVersion < CONFIG_VERSION) saveConfig.set(true); else if (configVersion > CONFIG_VERSION) saveConfig.set(false); @@ -123,13 +122,14 @@ public static SpongeConfig read(final PluginContainer plugin, final Path file) t ); } - @Contract(value = "_, _, _, !null, _ -> !null") + @Contract(value = "_, _, _, !null, _, _ -> !null") private static @Nullable T parse( @Nullable final Properties properties, final AtomicBoolean saveConfig, final String key, @Nullable final Supplier defaultValue, - final Function parser + final Function parser, + final Logger logger ) { if (properties == null) { saveConfig.set(true); @@ -161,8 +161,7 @@ public static SpongeConfig read(final PluginContainer plugin, final Path file) t } } - @SuppressWarnings("PatternValidation") - public boolean preSubmissionStart() { + public boolean preSubmissionStart(final SpongeContext context) { if (Boolean.getBoolean("faststats.first-run")) return false; if (firstRun()) { @@ -170,10 +169,10 @@ public boolean preSubmissionStart() { final var split = ONBOARDING_MESSAGE.split("\n"); for (final var s : split) if (s.length() > separatorLength) separatorLength = s.length(); - final var logger = LoggerFactory.factory().getLogger(getClass()); - logger.info("-".repeat(separatorLength)); - for (final var s : split) logger.info(s); - logger.info("-".repeat(separatorLength)); + final var logger = context.getLoggerFactory().getLogger(getClass()); + logger.print(Logger.LogLevel.INFO, null, "-".repeat(separatorLength)); + for (final var s : split) logger.print(Logger.LogLevel.INFO, null, s); + logger.print(Logger.LogLevel.INFO, null, "-".repeat(separatorLength)); System.setProperty("faststats.first-run", "true"); return false; diff --git a/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java b/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java index e95b4758..b44e68cb 100644 --- a/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java +++ b/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java @@ -5,6 +5,8 @@ import dev.faststats.SimpleContext; import dev.faststats.SimpleMetrics; import dev.faststats.Token; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; import org.spongepowered.api.Sponge; @@ -28,11 +30,15 @@ public final class SpongeContext extends SimpleContext { final PluginContainer plugin; private SpongeContext( - final Factory factory, final PluginContainer plugin, + final Factory factory, + final LoggerFactory loggerFactory, + final PluginContainer plugin, @ConfigDir(sharedRoot = true) final Path dataDirectory, @Token final String token ) { - super(factory, SpongeConfig.read(plugin, dataDirectory.resolve("faststats").resolve("config.properties")), "sponge", token); + super(factory, loggerFactory, SpongeConfig.read(plugin, dataDirectory + .resolve("faststats").resolve("config.properties"), loggerFactory + ), "sponge", token); this.plugin = plugin; initializeServices(factory); } @@ -50,7 +56,7 @@ public Metrics create() throws IllegalStateException { @Override protected boolean preSubmissionStart() { - return ((SpongeConfig) getConfig()).preSubmissionStart(); + return ((SpongeConfig) getConfig()).preSubmissionStart(this); } @Override @@ -119,7 +125,23 @@ public SpongeContext.Factory token(@Token final String token) throws IllegalArgu @Override public SpongeContext create() { if (token == null) throw new IllegalStateException("Token not configured"); - return new SpongeContext(this, plugin, dataDirectory, token); + final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + switch (level) { + case INFO -> { + if (throwable == null) plugin.logger().info(message); + else plugin.logger().info(message, throwable); + } + case ERROR -> { + if (throwable == null) plugin.logger().error(message); + else plugin.logger().error(message, throwable); + } + case WARN -> { + if (throwable == null) plugin.logger().warn(message); + else plugin.logger().warn(message, throwable); + } + } + }); + return new SpongeContext(this, loggerFactory, plugin, dataDirectory, token); } } diff --git a/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java b/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java index 660c27ee..417b5acb 100644 --- a/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java +++ b/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java @@ -10,8 +10,11 @@ import dev.faststats.SimpleMetrics; import dev.faststats.Token; import dev.faststats.config.SimpleConfig; +import dev.faststats.internal.LoggerFactory; +import dev.faststats.internal.PlatformLoggerFactory; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; import java.nio.file.Path; import java.time.Duration; @@ -30,12 +33,16 @@ public final class VelocityContext extends SimpleContext { final ProxyServer server; private VelocityContext( - final Factory factory, final PluginContainer plugin, + final Factory factory, + final LoggerFactory loggerFactory, + final PluginContainer plugin, final ProxyServer server, @DataDirectory final Path dataDirectory, @Token final String token ) { - super(factory, SimpleConfig.read(dataDirectory.resolveSibling("faststats").resolve("config.properties")), "velocity", token); + super(factory, loggerFactory, SimpleConfig.read(dataDirectory.resolveSibling("faststats") + .resolve("config.properties"), loggerFactory + ), "velocity", token); this.plugin = plugin; this.server = server; initializeServices(factory); @@ -54,7 +61,7 @@ public Metrics create() throws IllegalStateException { @Override protected boolean preSubmissionStart() { - return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName()); + return ((SimpleConfig) getConfig()).preSubmissionStart(this); } @Override @@ -88,6 +95,7 @@ public static class Factory extends SimpleContext.Factory { + switch (level) { + case INFO -> logger.info(message, t); + case ERROR -> logger.error(message, t); + case WARN -> logger.warn(message, t); + } + }); + return new VelocityContext(this, loggerFactory, plugin, server, dataDirectory, token); } } @@ -140,9 +158,10 @@ public static final class Builder extends Factory { public Builder( final PluginContainer plugin, final ProxyServer server, + final Logger logger, @DataDirectory final Path dataDirectory ) { - super(plugin, server, dataDirectory); + super(plugin, server, logger, dataDirectory); } } } From fb81ee2755c25084d22642697e9e7ea58fe6c68a Mon Sep 17 00:00:00 2001 From: david Date: Tue, 23 Jun 2026 15:05:53 +0200 Subject: [PATCH 02/10] null mark internal package --- core/src/main/java/dev/faststats/internal/package-info.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/dev/faststats/internal/package-info.java b/core/src/main/java/dev/faststats/internal/package-info.java index dfe3b560..3cd0be50 100644 --- a/core/src/main/java/dev/faststats/internal/package-info.java +++ b/core/src/main/java/dev/faststats/internal/package-info.java @@ -1,4 +1,6 @@ @ApiStatus.Internal +@NullMarked package dev.faststats.internal; -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; \ No newline at end of file From c24c058afe90db823d2638260a8a9c7dbb5c3dd7 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 23 Jun 2026 15:06:04 +0200 Subject: [PATCH 03/10] make debug method public --- core/src/main/java/dev/faststats/internal/Logger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/dev/faststats/internal/Logger.java b/core/src/main/java/dev/faststats/internal/Logger.java index 7ee7eb07..8caa085d 100644 --- a/core/src/main/java/dev/faststats/internal/Logger.java +++ b/core/src/main/java/dev/faststats/internal/Logger.java @@ -18,7 +18,7 @@ default void warn(@PrintFormat final String message, final Object... args) { debug(LogLevel.WARN, message, null, args); } - private void debug(final LogLevel level, @PrintFormat final String message, @Nullable final Throwable t, final Object... args) { + default void debug(final LogLevel level, @PrintFormat final String message, @Nullable final Throwable t, final Object... args) { if (factory().isDebug()) print(level, t, "[" + caller() + "] " + message.formatted(args)); } From aab40047aba7f9f38f9066d3650336ca416e1623 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 23 Jun 2026 15:06:37 +0200 Subject: [PATCH 04/10] print success at warning level if warnings exist --- .../main/java/dev/faststats/SubmissionService.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/dev/faststats/SubmissionService.java b/core/src/main/java/dev/faststats/SubmissionService.java index dded6cf6..ef84cf15 100644 --- a/core/src/main/java/dev/faststats/SubmissionService.java +++ b/core/src/main/java/dev/faststats/SubmissionService.java @@ -1,6 +1,7 @@ package dev.faststats; import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import dev.faststats.internal.Logger; import java.io.ByteArrayOutputStream; @@ -63,7 +64,9 @@ protected boolean submit( ); if (isSuccessful(response)) { - logger.info("%s submitted with status code: %s (%s)", + final var warnings = hasWarnings(response.body()); + final var level = warnings ? Logger.LogLevel.WARN : Logger.LogLevel.INFO; + logger.debug(level, "%s submitted with status code: %s (%s)", null, capitalize(submissionName), response.statusCode(), response.body()); return true; } @@ -78,6 +81,15 @@ protected boolean submit( return false; } + private boolean hasWarnings(final String body) { + try { + final var json = JsonParser.parseString(body); + return json.isJsonObject() && json.getAsJsonObject().has("warnings"); + } catch (final Throwable ignored) { + return false; + } + } + private static String capitalize(final String value) { if (value.isEmpty()) return value; return Character.toUpperCase(value.charAt(0)) + value.substring(1); From 5c0933bf0a806b75f29c1fbf5883d7e7fd663b9a Mon Sep 17 00:00:00 2001 From: david Date: Tue, 23 Jun 2026 15:06:56 +0200 Subject: [PATCH 05/10] bump version to 0.27.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3b94202e..96eec9a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=0.27.0 +version=0.27.1 org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m From c73d9f2437f1dcb8bd071c139b15c4a5974fc738 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 23 Jun 2026 15:44:12 +0200 Subject: [PATCH 06/10] Simplify logger construction --- .../dev/faststats/bukkit/BukkitContext.java | 2 +- .../dev/faststats/bungee/BungeeContext.java | 2 +- .../java/dev/faststats/internal/Logger.java | 2 +- .../dev/faststats/internal/LoggerFactory.java | 2 +- .../internal/PlatformLoggerFactory.java | 29 ++++++++----------- .../test/java/dev/faststats/MockContext.java | 2 +- .../dev/faststats/fabric/FabricContext.java | 2 +- .../dev/faststats/hytale/HytaleContext.java | 2 +- .../faststats/minestom/MinestomContext.java | 2 +- .../faststats/neoforge/NeoForgeContext.java | 2 +- .../dev/faststats/nukkit/NukkitContext.java | 2 +- .../dev/faststats/sponge/SpongeContext.java | 2 +- .../faststats/velocity/VelocityContext.java | 2 +- 13 files changed, 24 insertions(+), 29 deletions(-) diff --git a/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java b/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java index 9a8ccd3e..3a87a069 100644 --- a/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java +++ b/bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java @@ -101,7 +101,7 @@ public Factory(final Plugin plugin, @Token final String token) { @Override public BukkitContext create() { - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { plugin.getLogger().log(level.getLevel(), message, throwable); }); return new BukkitContext(this, loggerFactory, plugin, token); diff --git a/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java b/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java index 99e26210..bd41cb8e 100644 --- a/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java +++ b/bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java @@ -76,7 +76,7 @@ public Factory(final Plugin plugin, @Token final String token) { @Override public BungeeContext create() { - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { plugin.getLogger().log(level.getLevel(), message, throwable); }); return new BungeeContext(this, loggerFactory, plugin, token); diff --git a/core/src/main/java/dev/faststats/internal/Logger.java b/core/src/main/java/dev/faststats/internal/Logger.java index 8caa085d..be4bcf74 100644 --- a/core/src/main/java/dev/faststats/internal/Logger.java +++ b/core/src/main/java/dev/faststats/internal/Logger.java @@ -5,7 +5,7 @@ import java.util.logging.Level; -public interface Logger { +public sealed interface Logger permits PlatformLoggerFactory.PlatformLogger { default void error(@PrintFormat final String message, @Nullable final Throwable t, final Object... args) { debug(LogLevel.ERROR, message, t, args); } diff --git a/core/src/main/java/dev/faststats/internal/LoggerFactory.java b/core/src/main/java/dev/faststats/internal/LoggerFactory.java index d12904ba..8d6c02c2 100644 --- a/core/src/main/java/dev/faststats/internal/LoggerFactory.java +++ b/core/src/main/java/dev/faststats/internal/LoggerFactory.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.Contract; -public abstract class LoggerFactory { +public sealed abstract class LoggerFactory permits PlatformLoggerFactory { private volatile boolean debug; @Contract(value = "_ -> new", pure = true) diff --git a/core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java b/core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java index adefc957..5ea8a50c 100644 --- a/core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java +++ b/core/src/main/java/dev/faststats/internal/PlatformLoggerFactory.java @@ -2,29 +2,24 @@ import org.jspecify.annotations.Nullable; -public abstract class PlatformLoggerFactory extends LoggerFactory { +public final class PlatformLoggerFactory extends LoggerFactory { + @Override + public Logger getLogger(final Class clazz) { + return new PlatformLogger(clazz.getName()); + } + @FunctionalInterface public interface Printer { void print(Logger.LogLevel level, @Nullable Throwable throwable, String message); } - public static PlatformLoggerFactory create(final Printer printer) { - return new PlatformLoggerFactory() { - @Override - public Logger getLogger(final Class clazz) { - return new PlatformLogger(clazz.getName()); - } - - @Override - protected void print(final Logger.LogLevel level, @Nullable final Throwable throwable, final String message) { - printer.print(level, throwable, message); - } - }; - } + private final Printer printer; - protected abstract void print(Logger.LogLevel level, @Nullable Throwable throwable, String message); + public PlatformLoggerFactory(final Printer printer) { + this.printer = printer; + } - private final class PlatformLogger implements Logger { + final class PlatformLogger implements Logger { private final String caller; private PlatformLogger(final String caller) { @@ -43,7 +38,7 @@ public LoggerFactory factory() { @Override public void print(final LogLevel level, @Nullable final Throwable throwable, final String message) { - PlatformLoggerFactory.this.print(level, throwable, message); + printer.print(level, throwable, message); } } } diff --git a/core/src/test/java/dev/faststats/MockContext.java b/core/src/test/java/dev/faststats/MockContext.java index a88c3b54..6a240747 100644 --- a/core/src/test/java/dev/faststats/MockContext.java +++ b/core/src/test/java/dev/faststats/MockContext.java @@ -110,7 +110,7 @@ public Factory firstRun() { @Override public MockContext create() { - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { final var output = level == Logger.LogLevel.ERROR ? System.err : System.out; output.println("[" + level.name() + "] " + message); if (throwable != null) throwable.printStackTrace(output); diff --git a/fabric/src/main/java/dev/faststats/fabric/FabricContext.java b/fabric/src/main/java/dev/faststats/fabric/FabricContext.java index 79575a3b..27505a59 100644 --- a/fabric/src/main/java/dev/faststats/fabric/FabricContext.java +++ b/fabric/src/main/java/dev/faststats/fabric/FabricContext.java @@ -103,7 +103,7 @@ public Factory(final String modId, @Token final String token) { @Override public FabricContext create() { final var logger = LoggerFactory.getLogger(modId); - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { switch (level) { case INFO -> { if (throwable == null) logger.info(message); diff --git a/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java b/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java index 4c6d5b07..3b920dbd 100644 --- a/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java +++ b/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java @@ -80,7 +80,7 @@ public Factory(final JavaPlugin plugin, @Token final String token) { @Override public HytaleContext create() { - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { final var api = switch (level) { case INFO -> plugin.getLogger().atFine(); case ERROR -> plugin.getLogger().atSevere(); diff --git a/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java b/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java index bbc1fdce..eec01aa1 100644 --- a/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java +++ b/minestom/src/main/java/dev/faststats/minestom/MinestomContext.java @@ -86,7 +86,7 @@ public Factory(@Token final String token) { @Override public MinestomContext create() { final var logger = LoggerFactory.getLogger("FastStats"); - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { switch (level) { case INFO -> { if (throwable == null) logger.info(message); diff --git a/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java b/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java index 9b6272b8..b4fa6cde 100644 --- a/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java +++ b/neoforge/src/main/java/dev/faststats/neoforge/NeoForgeContext.java @@ -104,7 +104,7 @@ public Factory(final String modId, @Token final String token) { @Override public NeoForgeContext create() { final var logger = org.slf4j.LoggerFactory.getLogger(modId); - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { switch (level) { case INFO -> logger.info(message, throwable); case ERROR -> logger.error(message, throwable); diff --git a/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java b/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java index 14d6a896..1fb324fb 100644 --- a/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java +++ b/nukkit/src/main/java/dev/faststats/nukkit/NukkitContext.java @@ -79,7 +79,7 @@ public Factory(final PluginBase plugin, @Token final String token) { @Override public NukkitContext create() { - final var loggerFactory = PlatformLoggerFactory.create((level, t, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, t, message) -> { switch (level) { case INFO -> plugin.getLogger().info(message, t); case ERROR -> plugin.getLogger().error(message, t); diff --git a/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java b/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java index b44e68cb..28cf347a 100644 --- a/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java +++ b/sponge/src/main/java/dev/faststats/sponge/SpongeContext.java @@ -125,7 +125,7 @@ public SpongeContext.Factory token(@Token final String token) throws IllegalArgu @Override public SpongeContext create() { if (token == null) throw new IllegalStateException("Token not configured"); - final var loggerFactory = PlatformLoggerFactory.create((level, throwable, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { switch (level) { case INFO -> { if (throwable == null) plugin.logger().info(message); diff --git a/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java b/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java index 417b5acb..dfeac39f 100644 --- a/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java +++ b/velocity/src/main/java/dev/faststats/velocity/VelocityContext.java @@ -137,7 +137,7 @@ public Factory token(@Token final String token) { @Override public VelocityContext create() { if (token == null) throw new IllegalStateException("Token not configured"); - final var loggerFactory = PlatformLoggerFactory.create((level, t, message) -> { + final var loggerFactory = new PlatformLoggerFactory((level, t, message) -> { switch (level) { case INFO -> logger.info(message, t); case ERROR -> logger.error(message, t); From cc7968e34c95b27bc404c351a40f5d26c0026913 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 24 Jun 2026 15:27:29 +0200 Subject: [PATCH 07/10] At least we have some sort of logging here :/ --- .../main/java/dev/faststats/SimpleErrorTrackerService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java b/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java index 3ac1386d..250ecfce 100644 --- a/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java +++ b/core/src/main/java/dev/faststats/SimpleErrorTrackerService.java @@ -62,7 +62,9 @@ private static void handleUncaughtException(final Thread thread, final Throwable tracker.trackError(error).handled(false); tracker.getContextErrorHandler().ifPresent(handler -> handler.accept(loader, error)); } catch (final Throwable t) { - // logger.error("Failed to dispatch uncaught error to tracker", t); // fixme + // todo: replace with better solution + System.err.println("Failed to dispatch uncaught error to tracker"); + t.printStackTrace(System.err); } } From 139c8054b4d343c1d183dbc77cd0750ba7009010 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 24 Jun 2026 16:28:24 +0200 Subject: [PATCH 08/10] Use inline import --- fabric/src/main/java/dev/faststats/fabric/FabricContext.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fabric/src/main/java/dev/faststats/fabric/FabricContext.java b/fabric/src/main/java/dev/faststats/fabric/FabricContext.java index 27505a59..adc2ffa5 100644 --- a/fabric/src/main/java/dev/faststats/fabric/FabricContext.java +++ b/fabric/src/main/java/dev/faststats/fabric/FabricContext.java @@ -11,7 +11,6 @@ import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import org.jetbrains.annotations.Contract; -import org.slf4j.LoggerFactory; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -102,7 +101,7 @@ public Factory(final String modId, @Token final String token) { @Override public FabricContext create() { - final var logger = LoggerFactory.getLogger(modId); + final var logger = org.slf4j.LoggerFactory.getLogger(modId); final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { switch (level) { case INFO -> { From b22ca8b692af6a37ad0ac66d375cf94edcf7f16d Mon Sep 17 00:00:00 2001 From: david Date: Wed, 24 Jun 2026 17:17:59 +0200 Subject: [PATCH 09/10] Fix hytale info log level --- hytale/src/main/java/dev/faststats/hytale/HytaleContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java b/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java index 3b920dbd..2689ee58 100644 --- a/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java +++ b/hytale/src/main/java/dev/faststats/hytale/HytaleContext.java @@ -82,7 +82,7 @@ public Factory(final JavaPlugin plugin, @Token final String token) { public HytaleContext create() { final var loggerFactory = new PlatformLoggerFactory((level, throwable, message) -> { final var api = switch (level) { - case INFO -> plugin.getLogger().atFine(); + case INFO -> plugin.getLogger().atInfo(); case ERROR -> plugin.getLogger().atSevere(); case WARN -> plugin.getLogger().atWarning(); }; From 5ed6fd6af555559815427341e951794f3adb4cba Mon Sep 17 00:00:00 2001 From: david Date: Wed, 24 Jun 2026 17:46:30 +0200 Subject: [PATCH 10/10] Improve submission logging --- core/src/main/java/dev/faststats/SubmissionService.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/dev/faststats/SubmissionService.java b/core/src/main/java/dev/faststats/SubmissionService.java index ef84cf15..c7567ee1 100644 --- a/core/src/main/java/dev/faststats/SubmissionService.java +++ b/core/src/main/java/dev/faststats/SubmissionService.java @@ -45,18 +45,14 @@ protected URI getServerUrl(final String propertyName, final String defaultUrl) { return URI.create(defaultUrl); } - // todo: i still don't like the logging :| protected boolean submit( final URI url, final JsonElement data, final String submissionName ) { - logger.info("Uncompressed data: %s", data); - try { final var compressed = compress(data.toString()); - logger.info("Compressed size: %s bytes", compressed.length); - logger.info("Sending %s to: %s", submissionName, url); + logger.info("Sending %s to: %s (%s bytes)\n%s", submissionName, url, compressed.length, data); final var response = HTTP_CLIENT.send( createSubmissionRequest(url, compressed), @@ -66,7 +62,7 @@ protected boolean submit( if (isSuccessful(response)) { final var warnings = hasWarnings(response.body()); final var level = warnings ? Logger.LogLevel.WARN : Logger.LogLevel.INFO; - logger.debug(level, "%s submitted with status code: %s (%s)", null, + logger.debug(level, "%s submitted successfully with status code: %s (%s)", null, capitalize(submissionName), response.statusCode(), response.body()); return true; }