Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions bukkit/src/main/java/dev/faststats/bukkit/BukkitContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,8 +22,8 @@ public final class BukkitContext extends SimpleContext {
private final Set<Runnable> 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);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = new PlatformLoggerFactory((level, throwable, message) -> {
plugin.getLogger().log(level.getLevel(), message, throwable);
});
return new BukkitContext(this, loggerFactory, plugin, token);
}
}

Expand Down
15 changes: 11 additions & 4 deletions bungeecord/src/main/java/dev/faststats/bungee/BungeeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,8 +24,10 @@ public final class BungeeContext extends SimpleContext {
private final Set<ScheduledTask> 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);
}
Expand All @@ -41,7 +45,7 @@ public Metrics create() throws IllegalStateException {

@Override
protected boolean preSubmissionStart() {
return ((SimpleConfig) getConfig()).preSubmissionStart(getProjectName());
return ((SimpleConfig) getConfig()).preSubmissionStart(this);
}

@Override
Expand Down Expand Up @@ -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 = new PlatformLoggerFactory((level, throwable, message) -> {
plugin.getLogger().log(level.getLevel(), message, throwable);
});
return new BungeeContext(this, loggerFactory, plugin, token);
}
}
}
38 changes: 19 additions & 19 deletions config/src/main/java/dev/faststats/config/SimpleConfig.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 = """
Expand Down Expand Up @@ -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"));

Expand All @@ -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);
Expand Down Expand Up @@ -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 <T> @Nullable T parse(
@Nullable final Properties properties,
final AtomicBoolean saveConfig,
final String key,
@Nullable final Supplier<T> defaultValue,
final Function<String, T> parser
final Function<String, T> parser,
final Logger logger
) {
if (properties == null) {
saveConfig.set(true);
Expand Down Expand Up @@ -161,19 +162,18 @@ 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()) {
var separatorLength = 0;
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;
Expand Down
16 changes: 12 additions & 4 deletions core/src/main/java/dev/faststats/SimpleContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -90,6 +94,10 @@ private SdkInfo constructSdkInfo(final String name) throws UncheckedIOException,
}
}

public LoggerFactory getLoggerFactory() {
return loggerFactory;
}

protected abstract boolean preSubmissionStart();

@Contract(pure = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<SimpleErrorTracker> DISPATCHER_TRACKERS = new CopyOnWriteArraySet<>();

private final Set<SimpleErrorTracker> errorTrackers = new CopyOnWriteArraySet<>();
Expand All @@ -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);
}

Expand Down Expand Up @@ -66,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);
// todo: replace with better solution
System.err.println("Failed to dispatch uncaught error to tracker");
t.printStackTrace(System.err);
}
}

Expand Down
25 changes: 16 additions & 9 deletions core/src/main/java/dev/faststats/SubmissionService.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package dev.faststats;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import dev.faststats.internal.Logger;
import dev.faststats.internal.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
Expand All @@ -25,12 +25,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();
Expand All @@ -45,26 +45,24 @@ 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),
HttpResponse.BodyHandlers.ofString(UTF_8)
);

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 successfully with status code: %s (%s)", null,
capitalize(submissionName), response.statusCode(), response.body());
return true;
}
Expand All @@ -79,6 +77,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);
Expand Down
42 changes: 30 additions & 12 deletions core/src/main/java/dev/faststats/internal/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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);
}

boolean isLoggable(Level level);
default void info(@PrintFormat final String message, final Object... args) {
debug(LogLevel.INFO, message, null, args);
}

void setFilter(@Nullable Predicate<Level> 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);
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));
}

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;
}
}
}
Loading