diff --git a/.gitignore b/.gitignore index f1660e2..78d9f40 100644 --- a/.gitignore +++ b/.gitignore @@ -43,5 +43,8 @@ libs/ # Local config overrides config.local.json +# Sentry auth token (secret — never commit) +.sentry-auth-token + # Serena .serena/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d8ef2..6264152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -*No changes yet* +### Added + +**Sentry Error Tracking Integration** +- Sentry SDK (v8.33.0) bundled for automatic error reporting to Sentry dashboard +- Non-blocking async event delivery — Sentry never impacts server performance +- All Sentry operations wrapped in try/catch — failures never crash the server +- Sentry config nested under `config/debug.json` → `"sentry"` section (no separate file) +- Auto-migration: existing `config/sentry.json` values are read into `debug.json` on first load, old file deleted +- DSN pre-configured with default — works out of the box +- Source context upload via Sentry Gradle plugin (stack traces show source code in Sentry) +- HyperFactions frames highlighted in stack traces via `addInAppInclude` +- New admin command: `/f admin sentry` — view status, enable/disable error reporting at runtime +- New admin command: `/f admin sentrytest` — sends a test error with stack trace to verify integration +- Sentry cleanly flushes pending events on server shutdown (2s timeout) +- Auth token stored in `.sentry-auth-token` file (gitignored) with env var fallback + +**Global Error Handling via ErrorHandler** +- New `ErrorHandler` utility class — centralized error handling that logs to console AND reports to Sentry +- 6 static methods covering all error patterns: `report()`, `report(@Nullable)`, `wrapTask()`, `guard()`, `runSafely()` (2 overloads) +- ~190 `Logger.severe()` calls in catch blocks across ~50 files now route through ErrorHandler to Sentry +- Scheduled/timer tasks wrapped with `wrapTask()` — exceptions no longer silently kill scheduler threads +- CompletableFuture chains guarded with `guard()` — async errors no longer swallowed +- Shutdown sequence steps isolated with `runSafely()` — one failure doesn't skip remaining cleanup +- `WriteResult.Failure` storage errors (with `@Nullable Exception cause`) now report to Sentry +- Pre-init error buffering: errors during config/data loading (before Sentry initializes) are buffered and flushed once Sentry is ready, tagged with `pre_init: true` ## [0.10.2] - 2026-02-28 diff --git a/build.gradle b/build.gradle index d3c8daf..0b4565f 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'checkstyle' id 'maven-publish' id 'com.gradleup.shadow' version '9.3.1' + id 'io.sentry.jvm.gradle' version '6.1.0' } group = 'com.hyperfactions' @@ -71,6 +72,9 @@ dependencies { // JSON handling implementation 'com.google.code.gson:gson:2.11.0' + // Sentry error tracking (bundled in shadow JAR) + implementation 'io.sentry:sentry:8.33.0' + // PlaceholderAPI Hytale (soft dependency - compileOnly) compileOnly 'at.helpch:placeholderapi-hytale:1.0.4' @@ -144,6 +148,7 @@ shadowJar { // Relocate dependencies to avoid conflicts relocate 'com.google.gson', 'com.hyperfactions.lib.gson' + relocate 'io.sentry', 'com.hyperfactions.lib.sentry' // Don't minimize - it removes Gson's inner classes needed at runtime } @@ -168,6 +173,7 @@ tasks.withType(Checkstyle).configureEach { build { dependsOn shadowJar + finalizedBy tasks.matching { it.name == 'sentryUploadSourceBundleJava' } } tasks.withType(JavaCompile).configureEach { @@ -194,6 +200,11 @@ tasks.named('compileJava') { } } +// Sentry tasks use the same generated sources dir as generateBuildInfo — declare dependencies +tasks.matching { it.name.startsWith('sentry') || it.name.startsWith('generateSentry') }.configureEach { + dependsOn 'generateBuildInfo' +} + // Dev build task - clean build with version set to 'dev' tasks.register('buildDev') { group = 'build' @@ -205,6 +216,18 @@ tasks.named('build') { mustRunAfter 'clean' } +// Sentry source context upload (reads token from .sentry-auth-token file or SENTRY_AUTH_TOKEN env var) +sentry { + includeSourceContext = true + org = "hypersystems" + projectName = "hyperfactions" + authToken = { + def tokenFile = file('.sentry-auth-token') + if (tokenFile.exists()) return tokenFile.text.trim() + return System.getenv("SENTRY_AUTH_TOKEN") + }() +} + // Maven publication for JitPack (publishes API classes, not the shadow JAR) publishing { publications { diff --git a/src/main/java/com/hyperfactions/HyperFactions.java b/src/main/java/com/hyperfactions/HyperFactions.java index 3eb2f62..ae6cdac 100644 --- a/src/main/java/com/hyperfactions/HyperFactions.java +++ b/src/main/java/com/hyperfactions/HyperFactions.java @@ -36,6 +36,7 @@ import com.hyperfactions.update.UpdateChecker; import com.hyperfactions.update.UpdateNotificationListener; import com.hyperfactions.update.UpdateNotificationPreferences; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.worldmap.MapPlayerFilterService; import com.hyperfactions.worldmap.WorldMapService; @@ -245,7 +246,7 @@ public void enable() { Files.writeString(versionFile, "1"); } } catch (IOException e) { - Logger.severe("[Storage] Failed to initialize data directory: %s", e.getMessage()); + ErrorHandler.report("[Storage] Failed to initialize data directory", e); } // Initialize HyperPerms integration (legacy, for backward compatibility) diff --git a/src/main/java/com/hyperfactions/backup/BackupManager.java b/src/main/java/com/hyperfactions/backup/BackupManager.java index ca6850d..319c105 100644 --- a/src/main/java/com/hyperfactions/backup/BackupManager.java +++ b/src/main/java/com/hyperfactions/backup/BackupManager.java @@ -3,6 +3,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.config.ConfigManager; import com.hyperfactions.storage.StorageUtils; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.FileOutputStream; import java.io.IOException; @@ -88,7 +89,7 @@ public void init() { initialized = true; Logger.info("[Backup] Initialized, backup directory: %s", backupsDir); } catch (IOException e) { - Logger.severe("[Backup] Failed to create backups directory: %s", e.getMessage()); + ErrorHandler.report("[Backup] Failed to create backups directory", e); } } @@ -202,7 +203,7 @@ private void runScheduledBackup() { } } }).exceptionally(ex -> { - Logger.severe("[Backup] Backup task failed with exception: %s", ex.getMessage()); + ErrorHandler.report("[Backup] Backup task failed with exception", ex); synchronized (backupLock) { backupInProgress = false; backupLock.notifyAll(); @@ -214,7 +215,7 @@ private void runScheduledBackup() { backupInProgress = false; backupLock.notifyAll(); } - Logger.severe("[Backup] Failed to start backup: %s", e.getMessage()); + ErrorHandler.report("[Backup] Failed to start backup", e); } } @@ -353,7 +354,7 @@ public CompletableFuture createBackup( try { Files.deleteIfExists(backupFile); } catch (IOException ignored) {} - Logger.severe("[Backup] Failed to create backup: %s", e.getMessage()); + ErrorHandler.report("[Backup] Failed to create backup", e); return new BackupResult.Failure("Failed to create backup: " + e.getMessage()); } }); @@ -397,7 +398,7 @@ public CompletableFuture restoreBackup(@NotNull String backupName return new RestoreResult.Success(backupName, filesRestored); } catch (Exception e) { - Logger.severe("[Backup] Failed to restore backup: %s", e.getMessage()); + ErrorHandler.report("[Backup] Failed to restore backup", e); return new RestoreResult.Failure("Failed to restore backup: " + e.getMessage()); } }); @@ -421,7 +422,7 @@ public CompletableFuture deleteBackup(@NotNull String backupName) { Logger.info("[Backup] Deleted backup: %s", backupName); return true; } catch (Exception e) { - Logger.severe("[Backup] Failed to delete backup '%s': %s", backupName, e.getMessage()); + ErrorHandler.report(String.format("[Backup] Failed to delete backup '%s'", backupName), e); return false; } }); @@ -460,7 +461,7 @@ public List listBackups() { } } } catch (IOException e) { - Logger.severe("[Backup] Failed to list backups: %s", e.getMessage()); + ErrorHandler.report("[Backup] Failed to list backups", e); } // Sort by timestamp, newest first diff --git a/src/main/java/com/hyperfactions/chat/PublicChatListener.java b/src/main/java/com/hyperfactions/chat/PublicChatListener.java index 1e23996..49d4009 100644 --- a/src/main/java/com/hyperfactions/chat/PublicChatListener.java +++ b/src/main/java/com/hyperfactions/chat/PublicChatListener.java @@ -2,6 +2,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.config.ConfigManager; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.event.EventPriority; import com.hypixel.hytale.server.core.event.events.player.PlayerChatEvent; @@ -65,7 +66,7 @@ public CompletableFuture onPlayerChatAsync( try { return handleChatEvent(event); } catch (Exception e) { - Logger.severe("Error handling chat event", e); + ErrorHandler.report("Error handling chat event", e); return event; } }); diff --git a/src/main/java/com/hyperfactions/command/admin/AdminSubCommand.java b/src/main/java/com/hyperfactions/command/admin/AdminSubCommand.java index 432b382..7cc070d 100644 --- a/src/main/java/com/hyperfactions/command/admin/AdminSubCommand.java +++ b/src/main/java/com/hyperfactions/command/admin/AdminSubCommand.java @@ -4,6 +4,8 @@ import com.hyperfactions.Permissions; import com.hyperfactions.command.admin.handler.AdminBackupHandler; import com.hyperfactions.command.admin.handler.AdminDebugHandler; +import com.hyperfactions.config.ConfigManager; +import com.hyperfactions.integration.SentryIntegration; import com.hyperfactions.command.admin.handler.AdminEconomyHandler; import com.hyperfactions.command.admin.handler.AdminImportHandler; import com.hyperfactions.command.admin.handler.AdminIntegrationHandler; @@ -284,6 +286,8 @@ private void dispatchCommand(@NotNull CommandContext ctx, @Nullable Store economyHandler.handleAdminEconomy(ctx, player, senderUuid, subArgs); case "world", "worlds" -> worldHandler.handleAdminWorld(ctx, player, subArgs); case "version" -> handleVersion(ctx, store, ref, player, isPlayer); + case "sentry" -> handleSentry(ctx, subArgs); + case "sentrytest" -> handleSentryTest(ctx); case "log", "logs", "activitylog" -> { if (!requirePlayer(ctx, isPlayer)) { break; @@ -375,6 +379,10 @@ private void showAdminHelp(CommandContext ctx) { commands.add(new CommandHelp("/f admin log", "View global activity log")); commands.add(new CommandHelp("/f admin world", "Per-world settings management")); commands.add(new CommandHelp("/f admin version", "View mod version and integration status")); + commands.add(new CommandHelp("/f admin sentry", "View Sentry status")); + commands.add(new CommandHelp("/f admin sentry disable", "Opt out of Sentry error reporting")); + commands.add(new CommandHelp("/f admin sentry enable", "Opt in to Sentry error reporting")); + commands.add(new CommandHelp("/f admin sentrytest", "Send a test error to Sentry")); ctx.sendMessage(HelpFormatter.buildHelp("Admin Commands", "Server administration", commands, null)); } @@ -402,6 +410,67 @@ private void handleVersion(CommandContext ctx, @Nullable Store stor } } + // === Sentry === + private void handleSentry(CommandContext ctx, String[] args) { + var debugConfig = ConfigManager.get().debug(); + + if (args.length == 0) { + // Show status + boolean configEnabled = debugConfig.isSentryEnabled(); + boolean running = SentryIntegration.isInitialized(); + ctx.sendMessage(prefix().insert(msg("Sentry Error Reporting", COLOR_CYAN))); + ctx.sendMessage(msg(" Config: " + (configEnabled ? "enabled" : "disabled"), + configEnabled ? COLOR_GREEN : COLOR_GRAY)); + ctx.sendMessage(msg(" Status: " + (running ? "active" : "inactive"), + running ? COLOR_GREEN : COLOR_GRAY)); + ctx.sendMessage(msg(" DSN: " + debugConfig.getSentryDsn(), COLOR_GRAY)); + ctx.sendMessage(msg(" Environment: " + debugConfig.getSentryEnvironment(), COLOR_GRAY)); + return; + } + + switch (args[0].toLowerCase()) { + case "disable", "optout", "off" -> { + if (!debugConfig.isSentryEnabled()) { + ctx.sendMessage(prefix().insert(msg("Sentry is already disabled.", COLOR_YELLOW))); + return; + } + debugConfig.setSentryEnabled(false); + debugConfig.save(); + SentryIntegration.close(); + ctx.sendMessage(prefix().insert(msg("Sentry disabled and config saved. Error reporting is now off.", COLOR_GREEN))); + } + case "enable", "optin", "on" -> { + if (debugConfig.isSentryEnabled()) { + ctx.sendMessage(prefix().insert(msg("Sentry is already enabled.", COLOR_YELLOW))); + return; + } + debugConfig.setSentryEnabled(true); + debugConfig.save(); + // Try to initialize now if not already running + if (!SentryIntegration.isInitialized()) { + SentryIntegration.init(debugConfig); + } + ctx.sendMessage(prefix().insert(msg("Sentry enabled and config saved. Error reporting is now on.", COLOR_GREEN))); + } + default -> ctx.sendMessage(prefix().insert(msg("Usage: /f admin sentry [disable|enable]", COLOR_RED))); + } + } + + // === Sentry Test === + private void handleSentryTest(CommandContext ctx) { + if (!SentryIntegration.isInitialized()) { + ctx.sendMessage(prefix().insert(msg("Sentry is not initialized. Check config/debug.json", COLOR_RED))); + return; + } + + boolean sent = SentryIntegration.sendTestEvent(); + if (sent) { + ctx.sendMessage(prefix().insert(msg("Test error sent to Sentry. Check your Sentry dashboard.", COLOR_GREEN))); + } else { + ctx.sendMessage(prefix().insert(msg("Failed to send test event.", COLOR_RED))); + } + } + // === Reload === private void handleReload(CommandContext ctx, PlayerRef player) { if (!hasPermission(player, Permissions.ADMIN)) { diff --git a/src/main/java/com/hyperfactions/command/admin/handler/AdminEconomyHandler.java b/src/main/java/com/hyperfactions/command/admin/handler/AdminEconomyHandler.java index 7e66072..87d86eb 100644 --- a/src/main/java/com/hyperfactions/command/admin/handler/AdminEconomyHandler.java +++ b/src/main/java/com/hyperfactions/command/admin/handler/AdminEconomyHandler.java @@ -8,6 +8,7 @@ import com.hyperfactions.manager.EconomyManager; import com.hyperfactions.util.CommandHelp; import com.hyperfactions.util.HelpFormatter; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.command.system.CommandContext; @@ -156,7 +157,7 @@ private void handleSet(CommandContext ctx, EconomyManager econ, UUID senderUuid, ctx.sendMessage(prefix().insert(msg("Failed: " + result.name(), COLOR_RED))); } }).exceptionally(ex -> { - Logger.severe("Admin economy set balance failed for %s", ex, faction.name()); + ErrorHandler.report(String.format("Admin economy set balance failed for %s", faction.name()), ex); ctx.sendMessage(prefix().insert(msg("An error occurred.", COLOR_RED))); return null; }); @@ -198,7 +199,7 @@ private void handleAdd(CommandContext ctx, EconomyManager econ, UUID senderUuid, ctx.sendMessage(prefix().insert(msg("Failed: " + result.name(), COLOR_RED))); } }).exceptionally(ex -> { - Logger.severe("Admin economy add failed for %s", ex, faction.name()); + ErrorHandler.report(String.format("Admin economy add failed for %s", faction.name()), ex); ctx.sendMessage(prefix().insert(msg("An error occurred.", COLOR_RED))); return null; }); @@ -240,7 +241,7 @@ private void handleTake(CommandContext ctx, EconomyManager econ, UUID senderUuid ctx.sendMessage(prefix().insert(msg("Failed: " + result.name(), COLOR_RED))); } }).exceptionally(ex -> { - Logger.severe("Admin economy take failed for %s", ex, faction.name()); + ErrorHandler.report(String.format("Admin economy take failed for %s", faction.name()), ex); ctx.sendMessage(prefix().insert(msg("An error occurred.", COLOR_RED))); return null; }); @@ -287,7 +288,7 @@ private void handleReset(CommandContext ctx, EconomyManager econ, UUID senderUui ctx.sendMessage(prefix().insert(msg("Failed: " + result.name(), COLOR_RED))); } }).exceptionally(ex -> { - Logger.severe("Admin economy reset failed for %s", ex, faction.name()); + ErrorHandler.report(String.format("Admin economy reset failed for %s", faction.name()), ex); ctx.sendMessage(prefix().insert(msg("An error occurred.", COLOR_RED))); return null; }); diff --git a/src/main/java/com/hyperfactions/config/ConfigFile.java b/src/main/java/com/hyperfactions/config/ConfigFile.java index b1b0a65..cc2cf88 100644 --- a/src/main/java/com/hyperfactions/config/ConfigFile.java +++ b/src/main/java/com/hyperfactions/config/ConfigFile.java @@ -6,6 +6,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -93,7 +94,7 @@ public void load() { save(); } } catch (Exception e) { - Logger.severe("[Config] Failed to load %s: %s", filePath.getFileName(), e.getMessage()); + ErrorHandler.report(String.format("[Config] Failed to load %s", filePath.getFileName()), e); createDefaults(); } } @@ -109,7 +110,7 @@ public void save() { needsSave = false; Logger.debug("[Config] Saved: %s", filePath.getFileName()); } catch (IOException e) { - Logger.severe("[Config] Failed to save %s: %s", filePath.getFileName(), e.getMessage()); + ErrorHandler.report(String.format("[Config] Failed to save %s", filePath.getFileName()), e); } } diff --git a/src/main/java/com/hyperfactions/config/modules/DebugConfig.java b/src/main/java/com/hyperfactions/config/modules/DebugConfig.java index d8e1026..5ea5891 100644 --- a/src/main/java/com/hyperfactions/config/modules/DebugConfig.java +++ b/src/main/java/com/hyperfactions/config/modules/DebugConfig.java @@ -1,8 +1,10 @@ package com.hyperfactions.config.modules; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.hyperfactions.config.ModuleConfig; import com.hyperfactions.util.Logger; +import java.nio.file.Files; import java.nio.file.Path; import org.jetbrains.annotations.NotNull; @@ -42,6 +44,20 @@ public class DebugConfig extends ModuleConfig { private boolean economy = false; + // Sentry error tracking settings + private static final String DEFAULT_SENTRY_DSN = + "https://cc41f97749e8b8b1562defea6ba3de9c@o4510966614589440.ingest.us.sentry.io/4510966616162304"; + + private boolean sentryEnabled = true; + + private String sentryDsn = DEFAULT_SENTRY_DSN; + + private String sentryEnvironment = "production"; + + private boolean sentryDebug = false; + + private double sentryTracesSampleRate = 0.0; + /** * Creates a new debug config. * @@ -82,6 +98,11 @@ protected void createDefaults() { spawning = false; integration = false; economy = false; + sentryEnabled = true; + sentryDsn = DEFAULT_SENTRY_DSN; + sentryEnvironment = "production"; + sentryDebug = false; + sentryTracesSampleRate = 0.0; } /** Loads module settings. */ @@ -107,6 +128,19 @@ protected void loadModuleSettings(@NotNull JsonObject root) { economy = getBool(categories, "economy", false); } + // Load sentry settings (nested object) + if (hasSection(root, "sentry")) { + JsonObject sentry = root.getAsJsonObject("sentry"); + sentryEnabled = getBool(sentry, "enabled", sentryEnabled); + sentryDsn = getString(sentry, "dsn", sentryDsn); + sentryEnvironment = getString(sentry, "environment", sentryEnvironment); + sentryDebug = getBool(sentry, "debug", sentryDebug); + sentryTracesSampleRate = getDouble(sentry, "tracesSampleRate", sentryTracesSampleRate); + } else { + // Auto-migrate from old config/sentry.json if it exists + migrateLegacySentryConfig(); + } + // Apply settings to Logger applyToLogger(); } @@ -131,6 +165,14 @@ protected void writeModuleSettings(@NotNull JsonObject root) { categories.addProperty("integration", integration); categories.addProperty("economy", economy); root.add("categories", categories); + + JsonObject sentry = new JsonObject(); + sentry.addProperty("enabled", sentryEnabled); + sentry.addProperty("dsn", sentryDsn); + sentry.addProperty("environment", sentryEnvironment); + sentry.addProperty("debug", sentryDebug); + sentry.addProperty("tracesSampleRate", sentryTracesSampleRate); + root.add("sentry", sentry); } /** @@ -282,6 +324,65 @@ public boolean isEconomy() { return economy; } + // === Sentry Getters === + + /** + * Checks if Sentry error tracking is enabled. + * + * @return true if Sentry is enabled + */ + public boolean isSentryEnabled() { + return sentryEnabled; + } + + /** + * Gets the Sentry DSN (Data Source Name) URL. + * + * @return DSN string + */ + @NotNull + public String getSentryDsn() { + return sentryDsn; + } + + /** + * Gets the environment name sent to Sentry (e.g., "production", "development"). + * + * @return environment name + */ + @NotNull + public String getSentryEnvironment() { + return sentryEnvironment; + } + + /** + * Checks if Sentry debug logging is enabled. + * + * @return true if Sentry debug mode is on + */ + public boolean isSentryDebug() { + return sentryDebug; + } + + /** + * Gets the traces sample rate for Sentry performance monitoring. + * 0.0 = no performance traces, 1.0 = capture all. + * + * @return sample rate between 0.0 and 1.0 + */ + public double getSentryTracesSampleRate() { + return sentryTracesSampleRate; + } + + /** + * Sets whether Sentry error tracking is enabled. + * + * @param enabled true to enable + */ + public void setSentryEnabled(boolean enabled) { + this.sentryEnabled = enabled; + } + // === Setters (for runtime toggle) === /** @@ -443,4 +544,47 @@ public void disableAll() { economy = false; applyToLogger(); } + + /** + * Migrates sentry settings from the old config/sentry.json file into this config. + * Called when debug.json has no "sentry" section and we need to check for legacy data. + * Deletes the old file after successful migration. + */ + private void migrateLegacySentryConfig() { + Path sentryFile = filePath.getParent().resolve("sentry.json"); + if (!Files.exists(sentryFile)) { + return; + } + + try { + String json = Files.readString(sentryFile); + JsonObject root = JsonParser.parseString(json).getAsJsonObject(); + + // Read sentry values from the old file + if (root.has("enabled")) { + sentryEnabled = root.get("enabled").getAsBoolean(); + } + if (root.has("dsn")) { + sentryDsn = root.get("dsn").getAsString(); + } + if (root.has("environment")) { + sentryEnvironment = root.get("environment").getAsString(); + } + if (root.has("debug")) { + sentryDebug = root.get("debug").getAsBoolean(); + } + if (root.has("tracesSampleRate")) { + sentryTracesSampleRate = root.get("tracesSampleRate").getAsDouble(); + } + + needsSave = true; + Logger.info("[Config] Migrated sentry config from sentry.json into debug.json"); + + // Delete the old file + Files.delete(sentryFile); + Logger.info("[Config] Deleted old config/sentry.json"); + } catch (Exception e) { + Logger.warn("[Config] Failed to migrate sentry.json: %s", e.getMessage()); + } + } } diff --git a/src/main/java/com/hyperfactions/gui/AdminPageOpener.java b/src/main/java/com/hyperfactions/gui/AdminPageOpener.java index f752ca8..e9a0b04 100644 --- a/src/main/java/com/hyperfactions/gui/AdminPageOpener.java +++ b/src/main/java/com/hyperfactions/gui/AdminPageOpener.java @@ -15,6 +15,7 @@ import com.hyperfactions.gui.test.ButtonTestPage; import com.hyperfactions.manager.*; import com.hyperfactions.storage.PlayerStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -56,8 +57,7 @@ public void openAdminTagModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] Admin TagModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open admin TagModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open admin TagModalPage", e); } } @@ -80,8 +80,7 @@ public void openAdminDescriptionModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] Admin DescriptionModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open admin DescriptionModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open admin DescriptionModalPage", e); } } @@ -105,8 +104,7 @@ public void openAdminRenameModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] Admin RenameModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open admin RenameModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open admin RenameModalPage", e); } } @@ -149,8 +147,7 @@ public void openAdminDashboard(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminDashboardPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminDashboardPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminDashboardPage", e); } } @@ -175,8 +172,7 @@ public void openAdminActions(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminActionsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminActionsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminActionsPage", e); } } @@ -203,8 +199,7 @@ public void openAdminFactions(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminFactionsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminFactionsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminFactionsPage", e); } } @@ -232,8 +227,7 @@ public void openAdminPlayers(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminPlayersPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminPlayersPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminPlayersPage", e); } } @@ -264,8 +258,7 @@ public void openAdminDisbandConfirm(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminDisbandConfirmPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminDisbandConfirmPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminDisbandConfirmPage", e); } } @@ -297,8 +290,7 @@ public void openAdminFactionInfo(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminFactionInfoPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminFactionInfoPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminFactionInfoPage", e); } } @@ -330,8 +322,7 @@ public void openAdminEconomy(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminEconomyPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminEconomyPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminEconomyPage", e); } } @@ -365,8 +356,7 @@ public void openAdminEconomyAdjust(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminEconomyAdjustPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminEconomyAdjustPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminEconomyAdjustPage", e); } } @@ -396,8 +386,7 @@ public void openAdminFactionMembers(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminFactionMembersPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminFactionMembersPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminFactionMembersPage", e); } } @@ -434,8 +423,7 @@ public void openAdminPlayerInfo(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminPlayerInfoPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminPlayerInfoPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminPlayerInfoPage", e); } } @@ -465,8 +453,7 @@ public void openAdminFactionRelations(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminFactionRelationsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminFactionRelationsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminFactionRelationsPage", e); } } @@ -495,8 +482,7 @@ public void openAdminFactionSettings(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminFactionSettingsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminFactionSettingsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminFactionSettingsPage", e); } } @@ -530,8 +516,7 @@ public void openAdminUnclaimAllConfirm(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminUnclaimAllConfirmPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminUnclaimAllConfirmPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminUnclaimAllConfirmPage", e); } } @@ -566,8 +551,7 @@ public void openAdminZone(Player player, Ref ref, pageManager.openCustomPage(ref, store, zonePage); Logger.debug("[GUI] AdminZonePage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminZonePage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminZonePage", e); } } @@ -589,8 +573,7 @@ public void openAdminConfig(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminConfigPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminConfigPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminConfigPage", e); } } @@ -612,8 +595,7 @@ public void openAdminBackups(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminBackupsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminBackupsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminBackupsPage", e); } } @@ -639,8 +621,7 @@ public void openAdminActivityLog(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminActivityLogPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminActivityLogPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminActivityLogPage", e); } } @@ -662,8 +643,7 @@ public void openAdminUpdates(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminUpdatesPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminUpdatesPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminUpdatesPage", e); } } @@ -685,8 +665,7 @@ public void openAdminHelp(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminHelpPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminHelpPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminHelpPage", e); } } @@ -707,8 +686,7 @@ public void openAdminVersion(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminVersionPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminVersionPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminVersionPage", e); } } @@ -798,8 +776,7 @@ public void openCreateZoneWizard(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] CreateZoneWizardPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open CreateZoneWizardPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open CreateZoneWizardPage", e); } } @@ -846,8 +823,7 @@ public void openAdminZoneMap(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminZoneMapPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminZoneMapPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminZoneMapPage", e); } } @@ -879,8 +855,7 @@ public void openAdminZoneProperties(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminZonePropertiesPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminZonePropertiesPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminZonePropertiesPage", e); } } @@ -909,8 +884,7 @@ public void openAdminZoneSettings(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminZoneSettingsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminZoneSettingsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminZoneSettingsPage", e); } } @@ -947,8 +921,7 @@ public void openAdminZoneSettings(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminZoneSettingsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminZoneSettingsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminZoneSettingsPage", e); } } @@ -978,8 +951,7 @@ public void openAdminZoneIntegrationFlags(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] AdminZoneIntegrationFlagsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open AdminZoneIntegrationFlagsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open AdminZoneIntegrationFlagsPage", e); } } @@ -1011,8 +983,7 @@ public void openZoneRenameModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] ZoneRenameModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open ZoneRenameModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open ZoneRenameModalPage", e); } } @@ -1065,8 +1036,7 @@ public void openZoneChangeTypeModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] ZoneChangeTypeModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open ZoneChangeTypeModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open ZoneChangeTypeModalPage", e); } } diff --git a/src/main/java/com/hyperfactions/gui/FactionPageOpener.java b/src/main/java/com/hyperfactions/gui/FactionPageOpener.java index 0e490e9..c05c1a4 100644 --- a/src/main/java/com/hyperfactions/gui/FactionPageOpener.java +++ b/src/main/java/com/hyperfactions/gui/FactionPageOpener.java @@ -15,6 +15,7 @@ import com.hyperfactions.gui.test.ButtonTestPage; import com.hyperfactions.manager.*; import com.hyperfactions.storage.PlayerStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -58,8 +59,7 @@ public void openMainMenu(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] MainMenuPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open MainMenuPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open MainMenuPage", e); } } @@ -97,8 +97,7 @@ public void openFactionMain(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionDashboardPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionDashboardPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionDashboardPage", e); } } else { // Player has no faction - redirect to new player browse page @@ -133,8 +132,7 @@ public void openFactionMembers(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionMembersPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionMembersPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionMembersPage", e); } } @@ -162,8 +160,7 @@ public void openChunkMap(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] ChunkMapPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open ChunkMapPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open ChunkMapPage", e); } } @@ -209,8 +206,7 @@ public void openFactionRelations(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionRelationsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionRelationsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionRelationsPage", e); } } @@ -250,8 +246,7 @@ public void openFactionBrowser(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionBrowserPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionBrowserPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionBrowserPage", e); } } @@ -280,8 +275,7 @@ public void openLeaderboard(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionLeaderboardPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionLeaderboardPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionLeaderboardPage", e); } } @@ -314,8 +308,7 @@ public void openFactionInvites(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionInvitesPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionInvitesPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionInvitesPage", e); } } @@ -346,8 +339,7 @@ public void openFactionSettings(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionSettingsPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionSettingsPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionSettingsPage", e); } } @@ -396,8 +388,7 @@ public void openSetRelationModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, modalPage); Logger.debug("[GUI] SetRelationModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open SetRelationModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open SetRelationModalPage", e); } } @@ -426,8 +417,7 @@ public void openTagModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] TagModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open TagModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open TagModalPage", e); } } @@ -455,8 +445,7 @@ public void openDescriptionModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] DescriptionModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open DescriptionModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open DescriptionModalPage", e); } } @@ -485,8 +474,7 @@ public void openRenameModal(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] RenameModalPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open RenameModalPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open RenameModalPage", e); } } @@ -514,8 +502,7 @@ public void openDisbandConfirm(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] DisbandConfirmPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open DisbandConfirmPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open DisbandConfirmPage", e); } } @@ -548,8 +535,7 @@ public void openTransferConfirm(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] TransferConfirmPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open TransferConfirmPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open TransferConfirmPage", e); } } @@ -582,8 +568,7 @@ public void openFactionDashboard(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionDashboardPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionDashboardPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionDashboardPage", e); } } @@ -611,8 +596,7 @@ public void openLeaveConfirm(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] LeaveConfirmPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open LeaveConfirmPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open LeaveConfirmPage", e); } } @@ -641,8 +625,7 @@ public void openLeaderLeaveConfirm(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] LeaderLeaveConfirmPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open LeaderLeaveConfirmPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open LeaderLeaveConfirmPage", e); } } @@ -676,8 +659,7 @@ public void openFactionChat(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionChatPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionChatPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionChatPage", e); } } @@ -706,8 +688,7 @@ public void openFactionModules(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionModulesPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionModulesPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionModulesPage", e); } } @@ -742,8 +723,7 @@ public void openFactionTreasury(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] TreasuryPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open TreasuryPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open TreasuryPage", e); } } @@ -771,8 +751,7 @@ public void openTreasuryDepositModal(Player player, Ref ref, guiManager, guiManager.getPlugin().get(), faction, mode); player.getPageManager().openCustomPage(ref, store, page); } catch (Exception e) { - Logger.severe("[GUI] Failed to open TreasuryDepositModal: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open TreasuryDepositModal", e); } } @@ -793,8 +772,7 @@ public void openTreasuryTransferSearch(Player player, Ref ref, guiManager, guiManager.getPlugin().get(), faction); player.getPageManager().openCustomPage(ref, store, page); } catch (Exception e) { - Logger.severe("[GUI] Failed to open TreasuryTransferSearch: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open TreasuryTransferSearch", e); } } @@ -816,8 +794,7 @@ public void openTreasuryTransferConfirm(Player player, Ref ref, guiManager, guiManager.getPlugin().get(), faction, targetId, targetName, targetType); player.getPageManager().openCustomPage(ref, store, page); } catch (Exception e) { - Logger.severe("[GUI] Failed to open TreasuryTransferConfirm: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open TreasuryTransferConfirm", e); } } @@ -837,8 +814,7 @@ public void openTreasurySettings(Player player, Ref ref, var page = new TreasurySettingsPage(playerRef, guiManager.getFactionManager().get(), econ, guiManager, faction); player.getPageManager().openCustomPage(ref, store, page); } catch (Exception e) { - Logger.severe("[GUI] Failed to open TreasurySettings: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open TreasurySettings", e); } } @@ -866,8 +842,7 @@ public void openLogsViewer(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] LogsViewerPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open LogsViewerPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open LogsViewerPage", e); } } @@ -917,8 +892,7 @@ public void openFactionInfo(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionInfoPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionInfoPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionInfoPage", e); } } @@ -960,8 +934,7 @@ public void openFactionInfoFromPlayerInfo(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] FactionInfoPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open FactionInfoPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open FactionInfoPage", e); } } @@ -1012,8 +985,7 @@ public void openPlayerInfo(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] PlayerInfoPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open PlayerInfoPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open PlayerInfoPage", e); } } @@ -1029,8 +1001,7 @@ public void openButtonTestPage(Player player, Ref ref, ButtonTestPage page = new ButtonTestPage(playerRef); pageManager.openCustomPage(ref, store, page); } catch (Exception e) { - Logger.severe("[GUI] Failed to open ButtonTestPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open ButtonTestPage", e); } } diff --git a/src/main/java/com/hyperfactions/gui/NewPlayerPageOpener.java b/src/main/java/com/hyperfactions/gui/NewPlayerPageOpener.java index 8da12a2..7a4ad60 100644 --- a/src/main/java/com/hyperfactions/gui/NewPlayerPageOpener.java +++ b/src/main/java/com/hyperfactions/gui/NewPlayerPageOpener.java @@ -15,6 +15,7 @@ import com.hyperfactions.gui.test.ButtonTestPage; import com.hyperfactions.manager.*; import com.hyperfactions.storage.PlayerStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -96,8 +97,7 @@ public void openNewPlayerBrowse(Player player, Ref ref, pageManager.openCustomPage(ref, store, browsePage); Logger.debug("[GUI] NewPlayerBrowsePage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open NewPlayerBrowsePage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open NewPlayerBrowsePage", e); } } @@ -122,8 +122,7 @@ public void openCreateFactionWizard(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] CreateFactionPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open CreateFactionPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open CreateFactionPage", e); } } @@ -151,8 +150,7 @@ public void openInvitesPage(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] InvitesPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open InvitesPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open InvitesPage", e); } } @@ -180,8 +178,7 @@ public void openNewPlayerMap(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] NewPlayerMapPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open NewPlayerMapPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open NewPlayerMapPage", e); } } @@ -218,8 +215,7 @@ public void openHelp(Player player, Ref ref, pageManager.openCustomPage(ref, store, page); Logger.debug("[GUI] HelpMainPage opened successfully"); } catch (Exception e) { - Logger.severe("[GUI] Failed to open HelpMainPage: %s", e.getMessage()); - e.printStackTrace(); + ErrorHandler.report("[GUI] Failed to open HelpMainPage", e); } } diff --git a/src/main/java/com/hyperfactions/gui/admin/page/AdminActionsPage.java b/src/main/java/com/hyperfactions/gui/admin/page/AdminActionsPage.java index 8bad5f3..d7819f7 100644 --- a/src/main/java/com/hyperfactions/gui/admin/page/AdminActionsPage.java +++ b/src/main/java/com/hyperfactions/gui/admin/page/AdminActionsPage.java @@ -5,6 +5,7 @@ import com.hyperfactions.gui.admin.AdminNavBarHelper; import com.hyperfactions.gui.admin.data.AdminActionsData; import com.hyperfactions.storage.PlayerStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.util.MessageUtil; import com.hypixel.hytale.component.Ref; @@ -120,7 +121,7 @@ public void handleDataEvent(Ref ref, Store store, playerRef.getUsername(), allUuids.size()); } catch (Exception e) { player.sendMessage(MessageUtil.adminError("Failed to reset K/D: " + e.getMessage())); - Logger.severe("[Admin] Global K/D reset failed: %s", e.getMessage()); + ErrorHandler.report("[Admin] Global K/D reset failed", e); } // Reopen page to reset state diff --git a/src/main/java/com/hyperfactions/gui/admin/page/AdminEconomyAdjustPage.java b/src/main/java/com/hyperfactions/gui/admin/page/AdminEconomyAdjustPage.java index eaf0f64..e3a799d 100644 --- a/src/main/java/com/hyperfactions/gui/admin/page/AdminEconomyAdjustPage.java +++ b/src/main/java/com/hyperfactions/gui/admin/page/AdminEconomyAdjustPage.java @@ -9,6 +9,7 @@ import com.hyperfactions.gui.admin.data.AdminEconomyAdjustData; import com.hyperfactions.manager.EconomyManager; import com.hyperfactions.manager.FactionManager; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; @@ -149,7 +150,7 @@ public void handleDataEvent(Ref ref, Store store, economyManager.adminAdjust(factionId, amount, playerRef.getUuid(), desc) .thenAccept(result -> handleResult(result, player, ref, store, playerRef)) .exceptionally(ex -> { - Logger.severe("Admin economy adjust failed for faction %s", ex, factionId); + ErrorHandler.report(String.format("Admin economy adjust failed for faction %s", factionId), ex); showError("An error occurred."); return null; }); @@ -172,7 +173,7 @@ public void handleDataEvent(Ref ref, Store store, economyManager.setBalance(factionId, newBalance, playerRef.getUuid()) .thenAccept(result -> handleResult(result, player, ref, store, playerRef)) .exceptionally(ex -> { - Logger.severe("Admin economy set balance failed for faction %s", ex, factionId); + ErrorHandler.report(String.format("Admin economy set balance failed for faction %s", factionId), ex); showError("An error occurred."); return null; }); diff --git a/src/main/java/com/hyperfactions/gui/faction/ChunkMapAsset.java b/src/main/java/com/hyperfactions/gui/faction/ChunkMapAsset.java index 4e3e206..9376a28 100644 --- a/src/main/java/com/hyperfactions/gui/faction/ChunkMapAsset.java +++ b/src/main/java/com/hyperfactions/gui/faction/ChunkMapAsset.java @@ -1,5 +1,6 @@ package com.hyperfactions.gui.faction; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.common.util.ArrayUtil; import com.hypixel.hytale.math.util.ChunkUtil; @@ -149,7 +150,7 @@ public static CompletableFuture generate(PlayerRef player, int ce ImageIO.write(image, "PNG", baos); return new ChunkMapAsset(baos.toByteArray()); } catch (IOException e) { - Logger.severe("Failed to encode terrain map PNG", e); + ErrorHandler.report("Failed to encode terrain map PNG", e); return null; } }); diff --git a/src/main/java/com/hyperfactions/integration/SentryIntegration.java b/src/main/java/com/hyperfactions/integration/SentryIntegration.java new file mode 100644 index 0000000..bb6188e --- /dev/null +++ b/src/main/java/com/hyperfactions/integration/SentryIntegration.java @@ -0,0 +1,343 @@ +package com.hyperfactions.integration; + +import com.hyperfactions.BuildInfo; +import com.hyperfactions.config.modules.DebugConfig; +import com.hyperfactions.util.Logger; +import com.hypixel.hytale.common.util.java.ManifestUtil; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.HytaleServerConfig; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import io.sentry.Sentry; +import io.sentry.SentryLevel; +import java.util.ArrayList; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** + * Manages Sentry error tracking integration. + * + *

All operations are wrapped in try/catch to guarantee that Sentry issues + * never crash the server. The Sentry SDK sends events asynchronously by default + * (via {@code AsyncHttpTransport}), so event submission is non-blocking. + * + *

Errors that occur before Sentry is initialized (e.g., during config loading) + * are buffered and flushed once {@link #init} completes successfully. + */ +public final class SentryIntegration { + + private static boolean initialized = false; + + /** Buffered errors from before Sentry was initialized. */ + private static final List preInitErrors = new ArrayList<>(); + + private record BufferedError(String message, Throwable throwable) {} + + private SentryIntegration() {} + + /** + * Initializes Sentry from the debug config's sentry section. + * Does nothing if Sentry is disabled or the DSN is empty. + * Safe to call multiple times — subsequent calls are ignored. + * + * @param config the debug configuration containing sentry settings + */ + public static void init(@NotNull DebugConfig config) { + if (initialized) { + return; + } + + if (!config.isSentryEnabled()) { + Logger.info("[Sentry] Sentry is disabled in config"); + return; + } + + String dsn = config.getSentryDsn(); + if (dsn == null || dsn.isBlank()) { + Logger.info("[Sentry] No DSN configured — Sentry will not send events"); + return; + } + + try { + Sentry.init(options -> { + options.setDsn(dsn); + options.setRelease("hyperfactions@" + BuildInfo.VERSION); + options.setDebug(config.isSentryDebug()); + + // Auto-detect environment from server patchline, fall back to config value + String environment = config.getSentryEnvironment(); + try { + String patchline = ManifestUtil.getPatchline(); + if (patchline != null && !patchline.isEmpty()) { + environment = switch (patchline) { + case "release" -> "production"; + case "pre-release" -> "staging"; + case "dev" -> "development"; + default -> patchline; + }; + } + } catch (Exception ignored) { + // ManifestUtil may not be available — use config value + } + options.setEnvironment(environment); + + // Performance monitoring (0.0 = off by default) + double traceRate = config.getSentryTracesSampleRate(); + if (traceRate > 0.0) { + options.setTracesSampleRate(traceRate); + } + + // Highlight HyperFactions frames in stack traces + options.addInAppInclude("com.hyperfactions"); + + // Don't install UncaughtExceptionHandler — the Hytale server has its own + options.setEnableUncaughtExceptionHandler(false); + + // Give 2 seconds to flush pending events on shutdown + options.setShutdownTimeoutMillis(2000); + + // Limit breadcrumb memory usage + options.setMaxBreadcrumbs(50); + + // Send PII (player context etc.) — server-side, no browser data + options.setSendDefaultPii(true); + }); + + // Set global tags — attached to every event + Sentry.configureScope(scope -> { + scope.setTag("plugin.version", BuildInfo.VERSION); + scope.setTag("java.version", System.getProperty("java.version", "unknown")); + + String serverVersion = null; + try { + serverVersion = ManifestUtil.getVersion(); + } catch (Exception ignored) { + // ManifestUtil may not be available yet + } + scope.setTag("hytale.server.version", serverVersion != null ? serverVersion : "unknown"); + + String patchline = "unknown"; + try { + String pl = ManifestUtil.getPatchline(); + if (pl != null && !pl.isEmpty()) { + patchline = pl; + } + } catch (Exception ignored) {} + scope.setTag("hytale.patchline", patchline); + + String osName = System.getProperty("os.name", "unknown"); + String osArch = System.getProperty("os.arch", "unknown"); + scope.setTag("os", osName + " " + osArch); + + // Pull Hytale server config info + String serverName = "unknown"; + int maxPlayers = 0; + String motd = ""; + try { + HytaleServerConfig serverConfig = HytaleServer.get().getConfig(); + serverName = serverConfig.getServerName(); + maxPlayers = serverConfig.getMaxPlayers(); + motd = serverConfig.getMotd(); + } catch (Exception ignored) { + // Server may not be fully initialized yet + } + + scope.setTag("server.name", serverName); + + scope.setContexts("server", java.util.Map.of( + "server_name", serverName, + "max_players", maxPlayers, + "motd", motd != null && !motd.isEmpty() ? motd : "(none)", + "hytale_version", serverVersion != null ? serverVersion : "unknown", + "plugin_version", BuildInfo.VERSION, + "java_version", System.getProperty("java.version", "unknown"), + "os", osName + " " + osArch, + "available_processors", Runtime.getRuntime().availableProcessors(), + "max_memory_mb", Runtime.getRuntime().maxMemory() / (1024 * 1024) + )); + }); + + initialized = true; + Logger.info("[Sentry] Initialized (env=%s, debug=%s)", config.getSentryEnvironment(), config.isSentryDebug()); + + // Collect installed mods (may be incomplete during init — refreshed on boot) + refreshInstalledMods(); + + // Flush any errors that occurred before Sentry was ready (e.g., during config loading) + // Done after refreshInstalledMods() so the mods context is attached to flushed events + flushPreInitErrors(); + } catch (Exception e) { + Logger.severe("Failed to initialize Sentry: %s", e.getMessage()); + } + } + + /** + * Captures an exception and sends it to Sentry. + * Also logs the exception to the server console. + * + * @param message context message describing what was happening + * @param throwable the exception to capture + */ + public static void captureException(@NotNull String message, @NotNull Throwable throwable) { + // Always log to console first (with full stack trace via withCause) + Logger.severe(message, throwable); + + if (!initialized) { + // Buffer for later — will be flushed after init() completes + synchronized (preInitErrors) { + preInitErrors.add(new BufferedError(message, throwable)); + } + return; + } + + try { + Sentry.captureException(throwable, scope -> { + scope.setExtra("context", message); + }); + } catch (Exception e) { + // Never let Sentry failures propagate + Logger.debug("Sentry captureException failed: %s", e.getMessage()); + } + } + + /** + * Captures an exception and sends it to Sentry (without additional context message). + * + * @param throwable the exception to capture + */ + public static void captureException(@NotNull Throwable throwable) { + if (!initialized) { + return; + } + + try { + Sentry.captureException(throwable); + } catch (Exception e) { + Logger.debug("Sentry captureException failed: %s", e.getMessage()); + } + } + + /** + * Captures a plain message at the given severity level. + * + * @param message the message text + * @param level Sentry severity level + */ + public static void captureMessage(@NotNull String message, @NotNull SentryLevel level) { + if (!initialized) { + return; + } + + try { + Sentry.captureMessage(message, level); + } catch (Exception e) { + Logger.debug("Sentry captureMessage failed: %s", e.getMessage()); + } + } + + /** + * Sends a test exception to Sentry to verify the integration is working. + * + * @return true if the event was sent (or at least queued), false if Sentry is not initialized + */ + public static boolean sendTestEvent() { + if (!initialized) { + return false; + } + + try { + Exception testException = new RuntimeException("HyperFactions Sentry test event — this is not a real error"); + Sentry.captureException(testException); + return true; + } catch (Exception e) { + Logger.debug("Sentry test event failed: %s", e.getMessage()); + return false; + } + } + + /** + * Refreshes the installed mods context on the Sentry scope. + * Call after all plugins have loaded (e.g., on BootEvent) for a complete list. + */ + public static void refreshInstalledMods() { + if (!initialized) { + return; + } + + try { + var plugins = HytaleServer.get().getPluginManager().getPlugins(); + var modMap = new java.util.HashMap(); + for (PluginBase plugin : plugins) { + var id = plugin.getIdentifier(); + var manifest = plugin.getManifest(); + String name = id.getGroup() + ":" + id.getName(); + String version = manifest.getVersion() != null ? manifest.getVersion().toString() : "unknown"; + modMap.put(name, version + " (" + plugin.getState() + ")"); + } + modMap.put("total_count", plugins.size()); + + Sentry.configureScope(scope -> { + scope.setTag("mods.count", String.valueOf(plugins.size())); + scope.setContexts("installed_mods", modMap); + }); + + Logger.debug("[Sentry] Refreshed installed mods list (%d plugins)", plugins.size()); + } catch (Exception e) { + Logger.debug("Failed to collect installed mods for Sentry: %s", e.getMessage()); + } + } + + /** + * Flushes pending events and closes the Sentry SDK. + * Safe to call even if Sentry was never initialized. + */ + public static void close() { + if (!initialized) { + return; + } + + try { + Sentry.close(); + initialized = false; + Logger.info("[Sentry] Closed"); + } catch (Exception e) { + Logger.debug("Sentry close failed: %s", e.getMessage()); + initialized = false; + } + } + + /** + * Flushes errors that were buffered before Sentry was initialized. + * Called once from {@link #init} after Sentry is ready. + */ + private static void flushPreInitErrors() { + List errors; + synchronized (preInitErrors) { + if (preInitErrors.isEmpty()) { + return; + } + errors = new ArrayList<>(preInitErrors); + preInitErrors.clear(); + } + + Logger.info("[Sentry] Flushing %d pre-init error(s) to Sentry", errors.size()); + for (BufferedError error : errors) { + try { + Sentry.captureException(error.throwable(), scope -> { + scope.setExtra("context", error.message()); + scope.setTag("pre_init", "true"); + }); + } catch (Exception e) { + Logger.debug("Sentry flush failed for pre-init error: %s", e.getMessage()); + } + } + } + + /** + * Checks whether Sentry has been initialized and is active. + * + * @return true if Sentry is sending events + */ + public static boolean isInitialized() { + return initialized; + } +} diff --git a/src/main/java/com/hyperfactions/integration/protection/HyperProtectIntegration.java b/src/main/java/com/hyperfactions/integration/protection/HyperProtectIntegration.java index 3f5c3a2..32bc906 100644 --- a/src/main/java/com/hyperfactions/integration/protection/HyperProtectIntegration.java +++ b/src/main/java/com/hyperfactions/integration/protection/HyperProtectIntegration.java @@ -2,6 +2,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.protection.ProtectionChecker; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.entity.entities.Player; @@ -183,7 +184,7 @@ public static void registerAllHooks(@NotNull HyperFactions hf) { MethodType.methodType(Message.class, String.class)); bridge.set(SLOT_FORMAT_HANDLE, fmtHandle); } catch (Exception e) { - Logger.severe("Failed to register format handle at slot 15: %s", e.getMessage()); + ErrorHandler.report("Failed to register format handle at slot 15", e); } Logger.debug("Registered 20 HyperProtect hook(s) + format handle at bridge slots (unconditional)"); @@ -267,7 +268,7 @@ public static void registerUniqueHooks(@NotNull HyperFactions hf) { MethodType.methodType(Message.class, String.class)); bridge.set(SLOT_FORMAT_HANDLE, fmtHandle); } catch (Exception e) { - Logger.severe("Failed to register format handle at slot 15: %s", e.getMessage()); + ErrorHandler.report("Failed to register format handle at slot 15", e); } Logger.debug("Registered HP unique hooks (slots 0,9-12,15-22) for BOTH mode"); @@ -336,7 +337,7 @@ public int evaluate(UUID playerUuid, String worldName, int x, int y, int z) { } return verdict; } catch (Exception e) { - Logger.severe("Block break mixin hook error (fail-closed)", e); + ErrorHandler.report("Block break mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -369,7 +370,7 @@ public int evaluateExplosion(Object world, int x, int y, int z) { Logger.debugInteraction("[Mixin:Explosion] world=%s, pos=(%d,%d,%d), blocked=%b", worldName, x, y, z, blocked); return blocked ? DENY_SILENT : ALLOW; } catch (Exception e) { - Logger.severe("Explosion mixin hook error (fail-closed)", e); + ErrorHandler.report("Explosion mixin hook error (fail-closed)", e); return DENY_SILENT; } } @@ -412,7 +413,7 @@ public int evaluatePaste(UUID playerUuid, String worldName, int x, int y, int z) } return verdict; } catch (Exception e) { - Logger.severe("Builder tools mixin hook error (fail-closed)", e); + ErrorHandler.report("Builder tools mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -494,7 +495,7 @@ public int evaluateCrafting(UUID playerUuid, String worldName, int x, int y, int } return verdict; } catch (Exception e) { - Logger.severe("Container access mixin hook error (fail-closed)", e); + ErrorHandler.report("Container access mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -545,7 +546,7 @@ public int evaluateTeleporter(UUID playerUuid, String worldName, int x, int y, i } return verdict; } catch (Exception e) { - Logger.severe("Teleporter mixin hook error (fail-closed)", e); + ErrorHandler.report("Teleporter mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -580,7 +581,7 @@ public int evaluateGateway(UUID playerUuid, String worldName, int x, int y, int } return verdict; } catch (Exception e) { - Logger.severe("Portal mixin hook error (fail-closed)", e); + ErrorHandler.report("Portal mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -660,7 +661,7 @@ public int evaluateEntityDamage(UUID attackerUuid, UUID targetUuid, String world } return verdict; } catch (Exception e) { - Logger.severe("Entity damage mixin hook error (fail-closed)", e); + ErrorHandler.report("Entity damage mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -696,7 +697,7 @@ public int evaluateContainerOpen(UUID playerUuid, String worldName, int x, int y } return verdict; } catch (Exception e) { - Logger.severe("Container open mixin hook error (fail-closed)", e); + ErrorHandler.report("Container open mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -730,7 +731,7 @@ public int evaluateBlockPlace(UUID playerUuid, String worldName, int x, int y, i } return verdict; } catch (Exception e) { - Logger.severe("Block place mixin hook error (fail-closed)", e); + ErrorHandler.report("Block place mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -765,7 +766,7 @@ public int evaluateHammer(UUID playerUuid, String worldName, int x, int y, int z } return verdict; } catch (Exception e) { - Logger.severe("Hammer mixin hook error (fail-closed)", e); + ErrorHandler.report("Hammer mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -813,7 +814,7 @@ public int evaluateUse(UUID playerUuid, String worldName, int x, int y, int z) { } return verdict; } catch (Exception e) { - Logger.severe("Use mixin hook error (fail-closed)", e); + ErrorHandler.report("Use mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } @@ -848,7 +849,7 @@ public int evaluateSeat(UUID playerUuid, String worldName, int x, int y, int z) } return verdict; } catch (Exception e) { - Logger.severe("Seat mixin hook error (fail-closed)", e); + ErrorHandler.report("Seat mixin hook error (fail-closed)", e); cachedReason.set("Protection error — action blocked for safety."); return DENY_WITH_MESSAGE; } diff --git a/src/main/java/com/hyperfactions/lifecycle/MembershipHistoryHandler.java b/src/main/java/com/hyperfactions/lifecycle/MembershipHistoryHandler.java index 44621ac..fb0e341 100644 --- a/src/main/java/com/hyperfactions/lifecycle/MembershipHistoryHandler.java +++ b/src/main/java/com/hyperfactions/lifecycle/MembershipHistoryHandler.java @@ -10,6 +10,7 @@ import com.hyperfactions.data.PlayerData; import com.hyperfactions.manager.FactionManager; import com.hyperfactions.storage.PlayerStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.util.UUID; import org.jetbrains.annotations.NotNull; @@ -72,7 +73,7 @@ public void handleMembershipHistory(@NotNull FactionMemberEvent event) { } playerStorage.savePlayerData(data); }).exceptionally(e -> { - Logger.severe("Failed to record membership history for %s", e, playerUuid); + ErrorHandler.report(String.format("Failed to record membership history for %s", playerUuid), e); return null; }); } @@ -91,7 +92,7 @@ public void handleDisbandHistory(@NotNull FactionDisbandEvent event) { playerStorage.savePlayerData(data); Logger.debug("Membership history: %s's faction %s disbanded", memberUuid, faction.name()); }).exceptionally(e -> { - Logger.severe("Failed to record disband history for %s", e, memberUuid); + ErrorHandler.report(String.format("Failed to record disband history for %s", memberUuid), e); return null; }); } @@ -140,7 +141,7 @@ public void migrateMembershipHistory() { playerStorage.savePlayerData(data).join(); migrated++; } catch (Exception e) { - Logger.severe("Failed to migrate membership history for %s", e, member.username()); + ErrorHandler.report(String.format("Failed to migrate membership history for %s", member.username()), e); } } } diff --git a/src/main/java/com/hyperfactions/manager/ChatHistoryManager.java b/src/main/java/com/hyperfactions/manager/ChatHistoryManager.java index dffbdf9..348ab04 100644 --- a/src/main/java/com/hyperfactions/manager/ChatHistoryManager.java +++ b/src/main/java/com/hyperfactions/manager/ChatHistoryManager.java @@ -6,6 +6,7 @@ import com.hyperfactions.data.FactionChatHistory; import com.hyperfactions.data.FactionRelation; import com.hyperfactions.storage.ChatHistoryStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.util.ArrayList; import java.util.Comparator; @@ -189,22 +190,24 @@ public void pruneExpired() { } // Prune on-disk histories not in cache - storage.listAllFactionIds().thenAccept(ids -> { - for (UUID id : ids) { - if (!cache.containsKey(id)) { - storage.loadHistory(id).thenAccept(history -> { - FactionChatHistory pruned = history.pruneOlderThan(cutoff); - if (pruned != history) { - if (pruned.isEmpty()) { - storage.deleteHistory(id); - } else { - storage.saveHistory(pruned); - } - } - }); + ErrorHandler.guard("Chat history: prune expired on-disk histories", + storage.listAllFactionIds().thenAccept(ids -> { + for (UUID id : ids) { + if (!cache.containsKey(id)) { + ErrorHandler.guard("Chat history: prune faction " + id, + storage.loadHistory(id).thenAccept(history -> { + FactionChatHistory pruned = history.pruneOlderThan(cutoff); + if (pruned != history) { + if (pruned.isEmpty()) { + storage.deleteHistory(id); + } else { + storage.saveHistory(pruned); + } + } + })); + } } - } - }); + })); Logger.debug("Chat history retention cleanup completed (cutoff: %d days)", retentionDays); } @@ -250,13 +253,13 @@ public void shutdown() { private void scheduleDebouncedSave(@NotNull UUID factionId) { cancelPendingSave(factionId); - ScheduledFuture future = scheduler.schedule(() -> { + ScheduledFuture future = scheduler.schedule(ErrorHandler.wrapTask("Chat history debounced save", () -> { pendingSaves.remove(factionId); FactionChatHistory history = cache.get(factionId); if (history != null) { - storage.saveHistory(history); + ErrorHandler.guard("Chat history save for faction " + factionId, storage.saveHistory(history)); } - }, DEBOUNCE_DELAY_MS, TimeUnit.MILLISECONDS); + }), DEBOUNCE_DELAY_MS, TimeUnit.MILLISECONDS); pendingSaves.put(factionId, future); } @@ -278,7 +281,7 @@ private void flushPendingSave(@NotNull UUID factionId) { try { storage.saveHistory(history).join(); } catch (Exception e) { - Logger.severe("Failed to flush chat history for %s: %s", factionId, e.getMessage()); + ErrorHandler.report(String.format("Failed to flush chat history for %s", factionId), e); } } } diff --git a/src/main/java/com/hyperfactions/manager/FactionKDCache.java b/src/main/java/com/hyperfactions/manager/FactionKDCache.java index 5edd7d9..8ffbfeb 100644 --- a/src/main/java/com/hyperfactions/manager/FactionKDCache.java +++ b/src/main/java/com/hyperfactions/manager/FactionKDCache.java @@ -3,6 +3,7 @@ import com.hyperfactions.data.Faction; import com.hyperfactions.data.PlayerData; import com.hyperfactions.storage.PlayerStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.util.Map; import java.util.Optional; @@ -111,7 +112,7 @@ private void refresh() { Logger.debug("[KDCache] Refreshed K/D stats for %d factions", newCache.size()); } catch (Exception e) { - Logger.severe("[KDCache] Failed to refresh: %s", e.getMessage()); + ErrorHandler.report("[KDCache] Failed to refresh", e); } } } diff --git a/src/main/java/com/hyperfactions/manager/FactionManager.java b/src/main/java/com/hyperfactions/manager/FactionManager.java index c199bb5..d37b8cb 100644 --- a/src/main/java/com/hyperfactions/manager/FactionManager.java +++ b/src/main/java/com/hyperfactions/manager/FactionManager.java @@ -8,6 +8,7 @@ import com.hyperfactions.data.*; import com.hyperfactions.integration.PermissionManager; import com.hyperfactions.storage.FactionStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -146,7 +147,7 @@ public CompletableFuture loadAll() { Logger.info("[Startup] Loaded %d factions with %d members indexed", factions.size(), playerToFaction.size()); }).exceptionally(ex -> { - Logger.severe("CRITICAL: Exception during faction loading - keeping existing data", (Throwable) ex); + ErrorHandler.report("CRITICAL: Exception during faction loading - keeping existing data", ex); return null; }); } diff --git a/src/main/java/com/hyperfactions/manager/InviteManager.java b/src/main/java/com/hyperfactions/manager/InviteManager.java index dd45d3c..bbbfa92 100644 --- a/src/main/java/com/hyperfactions/manager/InviteManager.java +++ b/src/main/java/com/hyperfactions/manager/InviteManager.java @@ -11,6 +11,7 @@ import com.hyperfactions.data.PendingInvite; import com.hyperfactions.integration.PermissionManager; import com.hyperfactions.storage.StorageUtils; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -459,7 +460,7 @@ private void load() { Logger.info("[Storage] Loaded %d invites (%d expired and skipped)", loaded, expired); } catch (Exception e) { - Logger.severe("Failed to load invites", e); + ErrorHandler.report("Failed to load invites", e); } } @@ -470,7 +471,7 @@ private void save() { try { Files.createDirectories(dataFile.getParent()); } catch (IOException e) { - Logger.severe("Failed to create invites directory", e); + ErrorHandler.report("Failed to create invites directory", e); return; } @@ -485,7 +486,7 @@ private void save() { StorageUtils.WriteResult result = StorageUtils.writeAtomic(dataFile, gson.toJson(array)); if (result instanceof StorageUtils.WriteResult.Failure failure) { - Logger.severe("Failed to save invites: %s", failure.error()); + ErrorHandler.report(String.format("Failed to save invites: %s", failure.error()), failure.cause()); } } diff --git a/src/main/java/com/hyperfactions/manager/JoinRequestManager.java b/src/main/java/com/hyperfactions/manager/JoinRequestManager.java index 20075f7..53edaab 100644 --- a/src/main/java/com/hyperfactions/manager/JoinRequestManager.java +++ b/src/main/java/com/hyperfactions/manager/JoinRequestManager.java @@ -11,6 +11,7 @@ import com.hyperfactions.data.JoinRequest; import com.hyperfactions.integration.PermissionManager; import com.hyperfactions.storage.StorageUtils; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -488,7 +489,7 @@ private void load() { Logger.info("[Storage] Loaded %d join requests (%d expired and skipped)", loaded, expired); } catch (Exception e) { - Logger.severe("Failed to load join requests", e); + ErrorHandler.report("Failed to load join requests", e); } } @@ -499,7 +500,7 @@ private void save() { try { Files.createDirectories(dataFile.getParent()); } catch (IOException e) { - Logger.severe("Failed to create join requests directory", e); + ErrorHandler.report("Failed to create join requests directory", e); return; } @@ -514,7 +515,7 @@ private void save() { StorageUtils.WriteResult result = StorageUtils.writeAtomic(dataFile, gson.toJson(array)); if (result instanceof StorageUtils.WriteResult.Failure failure) { - Logger.severe("Failed to save join requests: %s", failure.error()); + ErrorHandler.report(String.format("Failed to save join requests: %s", failure.error()), failure.cause()); } } diff --git a/src/main/java/com/hyperfactions/manager/PowerManager.java b/src/main/java/com/hyperfactions/manager/PowerManager.java index 250f1d6..22209ae 100644 --- a/src/main/java/com/hyperfactions/manager/PowerManager.java +++ b/src/main/java/com/hyperfactions/manager/PowerManager.java @@ -9,6 +9,7 @@ import com.hyperfactions.data.Faction; import com.hyperfactions.data.PlayerPower; import com.hyperfactions.storage.PlayerStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -79,7 +80,7 @@ public CompletableFuture loadAll() { Logger.info("[Startup] Loaded %d player power records", powerCache.size()); }).exceptionally(ex -> { - Logger.severe("CRITICAL: Exception during player power loading - keeping existing data", (Throwable) ex); + ErrorHandler.report("CRITICAL: Exception during player power loading - keeping existing data", ex); return null; }); } @@ -600,7 +601,7 @@ public CompletableFuture loadHardcorePowerData() { } Logger.info("[Startup] Loaded hardcore power for %d factions", hardcoreFactionPower.size()); } catch (Exception e) { - Logger.severe("Failed to load hardcore power data: %s", e.getMessage()); + ErrorHandler.report("Failed to load hardcore power data", e); } }); } @@ -619,7 +620,7 @@ public CompletableFuture saveHardcorePowerData() { Gson gson = new GsonBuilder().setPrettyPrinting().create(); Files.writeString(file, gson.toJson(obj)); } catch (IOException e) { - Logger.severe("Failed to save hardcore power data: %s", e.getMessage()); + ErrorHandler.report("Failed to save hardcore power data", e); } }); } diff --git a/src/main/java/com/hyperfactions/manager/ZoneManager.java b/src/main/java/com/hyperfactions/manager/ZoneManager.java index e315ab5..c353503 100644 --- a/src/main/java/com/hyperfactions/manager/ZoneManager.java +++ b/src/main/java/com/hyperfactions/manager/ZoneManager.java @@ -5,6 +5,7 @@ import com.hyperfactions.data.ZoneFlags; import com.hyperfactions.data.ZoneType; import com.hyperfactions.storage.ZoneStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -179,7 +180,7 @@ public CompletableFuture loadAll() { Logger.info("[Startup] Loaded %d zones with %d total chunks", zonesById.size(), zoneIndex.size()); }).exceptionally(ex -> { - Logger.severe("CRITICAL: Exception during zone loading - keeping existing data", (Throwable) ex); + ErrorHandler.report("CRITICAL: Exception during zone loading - keeping existing data", ex); return null; }); } diff --git a/src/main/java/com/hyperfactions/migration/MigrationRunner.java b/src/main/java/com/hyperfactions/migration/MigrationRunner.java index 5ce8f7a..1f601fc 100644 --- a/src/main/java/com/hyperfactions/migration/MigrationRunner.java +++ b/src/main/java/com/hyperfactions/migration/MigrationRunner.java @@ -2,6 +2,7 @@ import com.hyperfactions.backup.BackupMetadata; import com.hyperfactions.backup.BackupType; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.FileOutputStream; import java.io.IOException; @@ -113,7 +114,7 @@ public MigrationResult runMigration(@NotNull Migration migration) { Logger.info("[Migration] Created backup at: %s", backupPath); } catch (IOException e) { Duration duration = Duration.between(startTime, Instant.now()); - Logger.severe("[Migration] Failed to create backup: %s", e.getMessage()); + ErrorHandler.report("[Migration] Failed to create backup", e); return MigrationResult.failure( migration.id(), migration.fromVersion(), @@ -148,7 +149,7 @@ public MigrationResult runMigration(@NotNull Migration migration) { duration ); } catch (IOException rollbackError) { - Logger.severe("[Migration] Rollback failed: %s", rollbackError.getMessage()); + ErrorHandler.report("[Migration] Rollback failed", rollbackError); Duration duration = Duration.between(startTime, Instant.now()); return MigrationResult.failure( migration.id(), @@ -165,7 +166,7 @@ public MigrationResult runMigration(@NotNull Migration migration) { return result; } catch (Exception e) { - Logger.severe("[Migration] Migration threw exception: %s", e.getMessage()); + ErrorHandler.report("[Migration] Migration threw exception", e); // Attempt rollback boolean rolledBack = false; @@ -174,7 +175,7 @@ public MigrationResult runMigration(@NotNull Migration migration) { rollback(migration, backupPath); rolledBack = true; } catch (IOException rollbackError) { - Logger.severe("[Migration] Rollback failed: %s", rollbackError.getMessage()); + ErrorHandler.report("[Migration] Rollback failed", rollbackError); } } diff --git a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV1ToV2Migration.java b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV1ToV2Migration.java index 8d8a924..4e27720 100644 --- a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV1ToV2Migration.java +++ b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV1ToV2Migration.java @@ -8,6 +8,7 @@ import com.hyperfactions.migration.MigrationOptions; import com.hyperfactions.migration.MigrationResult; import com.hyperfactions.migration.MigrationType; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -165,7 +166,7 @@ public MigrationResult execute(@NotNull Path dataDir, @NotNull MigrationOptions } catch (Exception e) { Duration duration = Duration.between(startTime, Instant.now()); - Logger.severe("[Migration] Config migration failed: %s", e.getMessage()); + ErrorHandler.report("[Migration] Config migration failed", e); return MigrationResult.failure( id(), fromVersion(), diff --git a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV2ToV3Migration.java b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV2ToV3Migration.java index 1140504..ddeb161 100644 --- a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV2ToV3Migration.java +++ b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV2ToV3Migration.java @@ -8,6 +8,7 @@ import com.hyperfactions.migration.MigrationOptions; import com.hyperfactions.migration.MigrationResult; import com.hyperfactions.migration.MigrationType; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -202,7 +203,7 @@ public MigrationResult execute(@NotNull Path dataDir, @NotNull MigrationOptions } catch (Exception e) { Duration duration = Duration.between(startTime, Instant.now()); - Logger.severe("[Migration] Config migration failed: %s", e.getMessage()); + ErrorHandler.report("[Migration] Config migration failed", e); return MigrationResult.failure( id(), fromVersion(), diff --git a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV3ToV4Migration.java b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV3ToV4Migration.java index 816dacd..8ccbd06 100644 --- a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV3ToV4Migration.java +++ b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV3ToV4Migration.java @@ -10,6 +10,7 @@ import com.hyperfactions.migration.MigrationOptions; import com.hyperfactions.migration.MigrationResult; import com.hyperfactions.migration.MigrationType; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.nio.file.Files; import java.nio.file.Path; @@ -161,7 +162,7 @@ public MigrationResult execute(@NotNull Path dataDir, @NotNull MigrationOptions } catch (Exception e) { Duration duration = Duration.between(startTime, Instant.now()); - Logger.severe("[Migration] Config migration v3->v4 failed: %s", e.getMessage()); + ErrorHandler.report("[Migration] Config migration v3->v4 failed", e); return MigrationResult.failure( id(), fromVersion(), diff --git a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV4ToV5Migration.java b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV4ToV5Migration.java index 1c6d81d..fbb2a92 100644 --- a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV4ToV5Migration.java +++ b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV4ToV5Migration.java @@ -8,6 +8,7 @@ import com.hyperfactions.migration.MigrationOptions; import com.hyperfactions.migration.MigrationResult; import com.hyperfactions.migration.MigrationType; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.nio.file.Files; import java.nio.file.Path; @@ -148,7 +149,7 @@ public MigrationResult execute(@NotNull Path dataDir, @NotNull MigrationOptions } catch (Exception e) { Duration duration = Duration.between(startTime, Instant.now()); - Logger.severe("[Migration] Config migration v4->v5 failed: %s", e.getMessage()); + ErrorHandler.report("[Migration] Config migration v4->v5 failed", e); return MigrationResult.failure( id(), fromVersion(), diff --git a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV5ToV6Migration.java b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV5ToV6Migration.java index bbed6ee..c72a336 100644 --- a/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV5ToV6Migration.java +++ b/src/main/java/com/hyperfactions/migration/migrations/config/ConfigV5ToV6Migration.java @@ -8,6 +8,7 @@ import com.hyperfactions.migration.MigrationOptions; import com.hyperfactions.migration.MigrationResult; import com.hyperfactions.migration.MigrationType; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.nio.file.Files; import java.nio.file.Path; @@ -204,7 +205,7 @@ public MigrationResult execute(@NotNull Path dataDir, @NotNull MigrationOptions } catch (Exception e) { Duration duration = Duration.between(startTime, Instant.now()); - Logger.severe("[Migration] Config migration v5->v6 failed: %s", e.getMessage()); + ErrorHandler.report("[Migration] Config migration v5->v6 failed", e); return MigrationResult.failure( id(), fromVersion(), diff --git a/src/main/java/com/hyperfactions/migration/migrations/data/DataV0ToV1Migration.java b/src/main/java/com/hyperfactions/migration/migrations/data/DataV0ToV1Migration.java index 1f6c89d..27c32a4 100644 --- a/src/main/java/com/hyperfactions/migration/migrations/data/DataV0ToV1Migration.java +++ b/src/main/java/com/hyperfactions/migration/migrations/data/DataV0ToV1Migration.java @@ -4,6 +4,7 @@ import com.hyperfactions.migration.MigrationOptions; import com.hyperfactions.migration.MigrationResult; import com.hyperfactions.migration.MigrationType; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.*; @@ -164,7 +165,7 @@ public MigrationResult execute(@NotNull Path dataDir, @NotNull MigrationOptions } catch (Exception e) { Duration duration = Duration.between(startTime, Instant.now()); - Logger.severe("[Migration] Data migration v0→v1 failed: %s", e.getMessage()); + ErrorHandler.report("[Migration] Data migration v0→v1 failed", e); return MigrationResult.failure( id(), fromVersion(), toVersion(), options.backupPath(), e.getMessage(), false, duration diff --git a/src/main/java/com/hyperfactions/platform/HyperFactionsPlugin.java b/src/main/java/com/hyperfactions/platform/HyperFactionsPlugin.java index e6e1fff..f3fd820 100644 --- a/src/main/java/com/hyperfactions/platform/HyperFactionsPlugin.java +++ b/src/main/java/com/hyperfactions/platform/HyperFactionsPlugin.java @@ -4,7 +4,9 @@ import com.hyperfactions.api.HyperFactionsAPI; import com.hyperfactions.chat.PublicChatListener; import com.hyperfactions.command.FactionCommand; +import com.hyperfactions.config.ConfigManager; import com.hyperfactions.integration.PermissionRegistrar; +import com.hyperfactions.integration.SentryIntegration; import com.hyperfactions.integration.protection.OrbisGuardIntegration; import com.hyperfactions.integration.protection.ProtectionMixinBridge; import com.hyperfactions.listener.PlayerListener; @@ -12,6 +14,7 @@ import com.hyperfactions.protection.interactions.HyperFactionsHarvestCropInteraction; import com.hyperfactions.protection.interactions.HyperFactionsPlaceFluidInteraction; import com.hyperfactions.protection.interactions.HyperFactionsRefillContainerInteraction; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.server.core.event.events.BootEvent; import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction; @@ -111,9 +114,12 @@ protected void start() { // Configure platform callbacks configurePlatformCallbacks(); - // Enable core + // Enable core (loads config, storage, managers) hyperFactions.enable(); + // Initialize Sentry error tracking (after config is loaded, non-blocking, fail-safe) + SentryIntegration.init(ConfigManager.get().debug()); + // Initialize GravestonePlugin integration (v2 direct API — needs EventRegistry) hyperFactions.initGravestoneIntegration(getEventRegistry()); @@ -160,7 +166,11 @@ protected void start() { worldSetup.logProtectionCoverage(); // Register permission nodes with LuckPerms on BootEvent (after all plugins loaded) - getEventRegistry().registerGlobal(BootEvent.class, e -> PermissionRegistrar.registerWithLuckPerms()); + // Also refresh Sentry mod list now that all plugins are loaded + getEventRegistry().registerGlobal(BootEvent.class, e -> { + PermissionRegistrar.registerWithLuckPerms(); + SentryIntegration.refreshInstalledMods(); + }); Logger.info("[Startup] HyperFactions v%s enabled", getManifest().getVersion()); } @@ -169,29 +179,38 @@ protected void start() { @Override protected void shutdown() { // Stop periodic tasks - stopPeriodicTasks(); + ErrorHandler.runSafely("Shutdown: stopPeriodicTasks", this::stopPeriodicTasks); // Handle combat logout for all tagged players - for (UUID playerUuid : trackedPlayers.keySet()) { - hyperFactions.getCombatTagManager().handleDisconnect(playerUuid); - } + ErrorHandler.runSafely("Shutdown: combat logout processing", () -> { + for (UUID playerUuid : trackedPlayers.keySet()) { + hyperFactions.getCombatTagManager().handleDisconnect(playerUuid); + } + }); // Unregister all mixin hooks (HP or OG) - ProtectionMixinBridge.unregisterAllHooks(); + ErrorHandler.runSafely("Shutdown: unregister mixin hooks", ProtectionMixinBridge::unregisterAllHooks); // Clean up territory ticking system - if (eventRegistration != null) { - eventRegistration.shutdownTerritory(); - } + ErrorHandler.runSafely("Shutdown: territory system cleanup", () -> { + if (eventRegistration != null) { + eventRegistration.shutdownTerritory(); + } + }); // Clear instances instance = null; HyperFactionsAPI.setInstance(null); // Disable core - if (hyperFactions != null) { - hyperFactions.disable(); - } + ErrorHandler.runSafely("Shutdown: HyperFactions core disable", () -> { + if (hyperFactions != null) { + hyperFactions.disable(); + } + }); + + // Flush pending Sentry events and close + SentryIntegration.close(); // Clear tracked players trackedPlayers.clear(); @@ -205,7 +224,7 @@ protected void shutdown() { private void configurePlatformCallbacks() { // Async executor hyperFactions.setAsyncExecutor(task -> { - java.util.concurrent.CompletableFuture.runAsync(task); + java.util.concurrent.CompletableFuture.runAsync(ErrorHandler.wrapTask("Async executor task", task)); }); // Task scheduler (for one-shot delayed tasks) @@ -213,12 +232,13 @@ private void configurePlatformCallbacks() { int id = taskIdCounter.incrementAndGet(); java.util.Timer timer = new java.util.Timer(); long delayMs = delayTicks * 50L; + Runnable wrapped = ErrorHandler.wrapTask("Scheduled task (delay=" + delayTicks + ")", task); timer.schedule(new java.util.TimerTask() { /** Runs the task. */ @Override public void run() { scheduledTasks.remove(id); - task.run(); + wrapped.run(); } }, delayMs); scheduledTasks.put(id, timer); @@ -231,11 +251,12 @@ public void run() { java.util.Timer timer = new java.util.Timer(); long delayMs = delayTicks * 50L; long periodMs = periodTicks * 50L; + Runnable wrapped = ErrorHandler.wrapTask("Repeating task (period=" + periodTicks + ")", task); timer.scheduleAtFixedRate(new java.util.TimerTask() { /** Runs the task. */ @Override public void run() { - task.run(); + wrapped.run(); } }, delayMs, periodMs); scheduledTasks.put(id, timer); @@ -342,7 +363,7 @@ private void startPeriodicTasks() { try { hyperFactions.getPowerManager().tickPowerRegen(); } catch (Exception e) { - Logger.severe("Error in power regen tick", e); + ErrorHandler.report("Error in power regen tick", e); } }, 60, 60, TimeUnit.SECONDS @@ -354,7 +375,7 @@ private void startPeriodicTasks() { try { hyperFactions.getCombatTagManager().tickDecay(); } catch (Exception e) { - Logger.severe("Error in combat tag tick", e); + ErrorHandler.report("Error in combat tag tick", e); } }, 1, 1, TimeUnit.SECONDS @@ -367,7 +388,7 @@ private void startPeriodicTasks() { try { hyperFactions.getClaimManager().tickClaimDecay(); } catch (Exception e) { - Logger.severe("Error in claim decay tick", e); + ErrorHandler.report("Error in claim decay tick", e); } }, 1, 1, TimeUnit.HOURS // Initial delay of 1 hour, then every hour @@ -382,7 +403,7 @@ private void startPeriodicTasks() { try { com.hyperfactions.protection.ProtectionMessageDebounce.cleanup(); } catch (Exception e) { - Logger.severe("Error in debounce cleanup tick", e); + ErrorHandler.report("Error in debounce cleanup tick", e); } }, 30, 30, TimeUnit.SECONDS diff --git a/src/main/java/com/hyperfactions/platform/PlayerConnectionHandler.java b/src/main/java/com/hyperfactions/platform/PlayerConnectionHandler.java index 8e860d3..1929235 100644 --- a/src/main/java/com/hyperfactions/platform/PlayerConnectionHandler.java +++ b/src/main/java/com/hyperfactions/platform/PlayerConnectionHandler.java @@ -3,6 +3,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.Permissions; import com.hyperfactions.integration.PermissionManager; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.server.core.event.events.player.PlayerChatEvent; import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent; @@ -44,37 +45,39 @@ public void onPlayerConnect(PlayerConnectEvent event) { trackedPlayers.size(), uuid, trackedPlayers.containsKey(uuid)); // Cache username, track first join and last online - hyperFactions.getPlayerStorage().loadPlayerData(uuid).thenAccept(opt -> { - com.hyperfactions.data.PlayerData data = opt.orElseGet(() -> new com.hyperfactions.data.PlayerData(uuid)); - data.setUsername(username); - long now = System.currentTimeMillis(); - if (data.getFirstJoined() == 0) { - data.setFirstJoined(now); - } - data.setLastOnline(now); - hyperFactions.getPlayerStorage().savePlayerData(data); - }); + ErrorHandler.guard("Player connect: load/save player data for " + username, + hyperFactions.getPlayerStorage().loadPlayerData(uuid).thenAccept(opt -> { + com.hyperfactions.data.PlayerData data = opt.orElseGet(() -> new com.hyperfactions.data.PlayerData(uuid)); + data.setUsername(username); + long now = System.currentTimeMillis(); + if (data.getFirstJoined() == 0) { + data.setFirstJoined(now); + } + data.setLastOnline(now); + hyperFactions.getPlayerStorage().savePlayerData(data); + })); // Load player power hyperFactions.getPowerManager().playerOnline(uuid); // Restore persistent admin bypass if saved and player still has permission - hyperFactions.getPlayerStorage().loadPlayerData(uuid).thenAccept(opt -> { - if (opt.isPresent()) { - com.hyperfactions.data.PlayerData data = opt.get(); - if (data.isAdminBypassEnabled()) { - if (PermissionManager.get().hasPermission(uuid, Permissions.ADMIN)) { - hyperFactions.setAdminBypass(uuid, true); - Logger.debug("Restored admin bypass for %s", username); - } else { - // Player lost admin permission — clear the persisted flag - data.setAdminBypassEnabled(false); - hyperFactions.getPlayerStorage().savePlayerData(data); - Logger.debug("Cleared stale admin bypass for %s (no permission)", username); + ErrorHandler.guard("Player connect: restore admin bypass for " + username, + hyperFactions.getPlayerStorage().loadPlayerData(uuid).thenAccept(opt -> { + if (opt.isPresent()) { + com.hyperfactions.data.PlayerData data = opt.get(); + if (data.isAdminBypassEnabled()) { + if (PermissionManager.get().hasPermission(uuid, Permissions.ADMIN)) { + hyperFactions.setAdminBypass(uuid, true); + Logger.debug("Restored admin bypass for %s", username); + } else { + // Player lost admin permission — clear the persisted flag + data.setAdminBypassEnabled(false); + hyperFactions.getPlayerStorage().savePlayerData(data); + Logger.debug("Cleared stale admin bypass for %s (no permission)", username); + } } } - } - }); + })); // Update faction member last online hyperFactions.getFactionManager().updateLastOnline(uuid); @@ -131,13 +134,14 @@ public void onPlayerDisconnect(PlayerDisconnectEvent event) { hyperFactions.getPowerManager().playerOffline(uuid); // Update last online timestamp - hyperFactions.getPlayerStorage().loadPlayerData(uuid).thenAccept(opt -> { - if (opt.isPresent()) { - com.hyperfactions.data.PlayerData data = opt.get(); - data.setLastOnline(System.currentTimeMillis()); - hyperFactions.getPlayerStorage().savePlayerData(data); - } - }); + ErrorHandler.guard("Player disconnect: update last online for " + username, + hyperFactions.getPlayerStorage().loadPlayerData(uuid).thenAccept(opt -> { + if (opt.isPresent()) { + com.hyperfactions.data.PlayerData data = opt.get(); + data.setLastOnline(System.currentTimeMillis()); + hyperFactions.getPlayerStorage().savePlayerData(data); + } + })); // Update faction member last online hyperFactions.getFactionManager().updateLastOnline(uuid); @@ -174,18 +178,22 @@ public void onPlayerDisconnect(PlayerDisconnectEvent event) { public CompletableFuture onPlayerChatAsync( CompletableFuture futureEvent) { return futureEvent.thenApply(event -> { - if (event.isCancelled()) { - return event; - } + try { + if (event.isCancelled()) { + return event; + } - PlayerRef sender = event.getSender(); - String message = event.getContent(); + PlayerRef sender = event.getSender(); + String message = event.getContent(); - // Check if player is in faction/ally chat mode - boolean handled = hyperFactions.getChatManager().processChatMessage(sender, message); - if (handled) { - // Cancel the normal chat broadcast - event.setCancelled(true); + // Check if player is in faction/ally chat mode + boolean handled = hyperFactions.getChatManager().processChatMessage(sender, message); + if (handled) { + // Cancel the normal chat broadcast + event.setCancelled(true); + } + } catch (Exception e) { + ErrorHandler.report("Player chat: faction/ally chat processing", e); } return event; }); diff --git a/src/main/java/com/hyperfactions/platform/WorldSetup.java b/src/main/java/com/hyperfactions/platform/WorldSetup.java index f215c76..09a7d5e 100644 --- a/src/main/java/com/hyperfactions/platform/WorldSetup.java +++ b/src/main/java/com/hyperfactions/platform/WorldSetup.java @@ -3,6 +3,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.config.ConfigManager; import com.hyperfactions.integration.protection.OrbisGuardIntegration; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.worldmap.HyperFactionsWorldMapProvider; import com.hypixel.hytale.server.core.universe.Universe; @@ -200,7 +201,7 @@ public void onWorldAdd(AddWorldEvent event) { } catch (Exception e) { plugin.getLogger().at(Level.WARNING).log("Error in AddWorldEvent handler for %s: %s", world.getName(), e.getMessage()); - Logger.severe("AddWorldEvent error for %s", e, world.getName()); + ErrorHandler.report(String.format("AddWorldEvent error for %s", world.getName()), e); } } diff --git a/src/main/java/com/hyperfactions/protection/ProtectionChecker.java b/src/main/java/com/hyperfactions/protection/ProtectionChecker.java index 240a949..5fa28e6 100644 --- a/src/main/java/com/hyperfactions/protection/ProtectionChecker.java +++ b/src/main/java/com/hyperfactions/protection/ProtectionChecker.java @@ -14,6 +14,7 @@ import com.hyperfactions.integration.protection.OrbisMixinsIntegration; import com.hyperfactions.manager.*; import com.hyperfactions.util.ChunkUtil; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.util.UUID; import java.util.function.Supplier; @@ -307,8 +308,8 @@ public ProtectionResult canInteractChunk(@NotNull UUID playerUuid, @NotNull Stri return ProtectionResult.DENIED_NEUTRAL_CLAIM; } catch (Exception e) { // Fail-closed: deny on any exception to prevent unauthorized actions - Logger.severe("Protection check error (fail-closed) for player %s at %s/%d/%d type=%s", - e, playerUuid, world, chunkX, chunkZ, type); + ErrorHandler.report(String.format("Protection check error (fail-closed) for player %s at %s/%d/%d type=%s", + playerUuid, world, chunkX, chunkZ, type), e); return ProtectionResult.DENIED_NO_PERMISSION; } } @@ -814,8 +815,8 @@ private String checkMixinProtection(@NotNull UUID playerUuid, @NotNull String wo return "You cannot do that in claimed territory."; } catch (Exception e) { // Fail-closed: deny on any exception to prevent unauthorized actions - Logger.severe("Protection check error (fail-closed) for player %s at %s/%d/%d/%d type=%s", - e, playerUuid, worldName, x, y, z, factionType); + ErrorHandler.report(String.format("Protection check error (fail-closed) for player %s at %s/%d/%d/%d type=%s", + playerUuid, worldName, x, y, z, factionType), e); return "Protection error — action blocked for safety."; } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/BlockBreakProtectionSystem.java b/src/main/java/com/hyperfactions/protection/ecs/BlockBreakProtectionSystem.java index 72eee07..efe859c 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/BlockBreakProtectionSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/BlockBreakProtectionSystem.java @@ -4,6 +4,7 @@ import com.hyperfactions.protection.ProtectionChecker; import com.hyperfactions.protection.ProtectionListener; import com.hyperfactions.protection.ProtectionMessageDebounce; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.ArchetypeChunk; @@ -101,7 +102,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, } catch (Exception e) { // Fail-closed: cancel on any exception to prevent unauthorized block breaks event.setCancelled(true); - Logger.severe("Block break cancelled due to protection error (fail-closed)", e); + ErrorHandler.report("Block break cancelled due to protection error (fail-closed)", e); } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/BlockPlaceProtectionSystem.java b/src/main/java/com/hyperfactions/protection/ecs/BlockPlaceProtectionSystem.java index 5b7fca4..127741f 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/BlockPlaceProtectionSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/BlockPlaceProtectionSystem.java @@ -4,6 +4,7 @@ import com.hyperfactions.protection.ProtectionChecker; import com.hyperfactions.protection.ProtectionListener; import com.hyperfactions.protection.ProtectionMessageDebounce; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.ArchetypeChunk; @@ -95,7 +96,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, } catch (Exception e) { // Fail-closed: cancel on any exception to prevent unauthorized block placement event.setCancelled(true); - Logger.severe("Block place cancelled due to protection error (fail-closed)", e); + ErrorHandler.report("Block place cancelled due to protection error (fail-closed)", e); } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/BlockUseProtectionSystem.java b/src/main/java/com/hyperfactions/protection/ecs/BlockUseProtectionSystem.java index b4fb50c..e2970e0 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/BlockUseProtectionSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/BlockUseProtectionSystem.java @@ -5,6 +5,7 @@ import com.hyperfactions.protection.ProtectionListener; import com.hyperfactions.protection.ProtectionMessageDebounce; import com.hyperfactions.protection.zone.ZoneInteractionProtection; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.util.MessageUtil; import com.hypixel.hytale.component.Archetype; @@ -173,7 +174,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, } catch (Exception e) { // Fail-closed: cancel on any exception to prevent unauthorized block interaction event.setCancelled(true); - Logger.severe("Block use cancelled due to protection error (fail-closed)", e); + ErrorHandler.report("Block use cancelled due to protection error (fail-closed)", e); } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/DamageProtectionSystem.java b/src/main/java/com/hyperfactions/protection/ecs/DamageProtectionSystem.java index 0dd6da3..125172e 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/DamageProtectionSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/DamageProtectionSystem.java @@ -3,6 +3,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.protection.ProtectionListener; import com.hyperfactions.protection.damage.DamageProtectionHandler; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.ArchetypeChunk; @@ -93,7 +94,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, } catch (Exception e) { // Fail-closed: cancel on any exception to prevent unauthorized damage event.setCancelled(true); - Logger.severe("Damage protection cancelled due to error (fail-closed)", e); + ErrorHandler.report("Damage protection cancelled due to error (fail-closed)", e); } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/HarvestPickupProtectionSystem.java b/src/main/java/com/hyperfactions/protection/ecs/HarvestPickupProtectionSystem.java index 4226573..a817a5e 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/HarvestPickupProtectionSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/HarvestPickupProtectionSystem.java @@ -5,6 +5,7 @@ import com.hyperfactions.protection.ProtectionListener; import com.hyperfactions.protection.ProtectionMessageDebounce; import com.hyperfactions.protection.zone.ZoneInteractionProtection; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.util.MessageUtil; import com.hypixel.hytale.component.Archetype; @@ -152,7 +153,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, } catch (Exception e) { // Fail-closed: cancel on any exception to prevent unauthorized harvest pickup event.setCancelled(true); - Logger.severe("Harvest pickup cancelled due to protection error (fail-closed)", e); + ErrorHandler.report("Harvest pickup cancelled due to protection error (fail-closed)", e); } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/ItemDropProtectionSystem.java b/src/main/java/com/hyperfactions/protection/ecs/ItemDropProtectionSystem.java index 885a3cf..64146e7 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/ItemDropProtectionSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/ItemDropProtectionSystem.java @@ -4,6 +4,7 @@ import com.hyperfactions.config.ConfigManager; import com.hyperfactions.protection.zone.ZoneInteractionProtection; import com.hyperfactions.util.ChunkUtil; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.util.MessageUtil; import com.hypixel.hytale.component.Archetype; @@ -113,7 +114,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, } catch (Exception e) { // Fail-closed: cancel on any exception to prevent unauthorized item drop event.setCancelled(true); - Logger.severe("Item drop cancelled due to protection error (fail-closed)", e); + ErrorHandler.report("Item drop cancelled due to protection error (fail-closed)", e); } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/ItemPickupProtectionSystem.java b/src/main/java/com/hyperfactions/protection/ecs/ItemPickupProtectionSystem.java index 6499cbb..d1a0318 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/ItemPickupProtectionSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/ItemPickupProtectionSystem.java @@ -3,6 +3,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.protection.ProtectionListener; import com.hyperfactions.protection.zone.ZoneInteractionProtection; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.ArchetypeChunk; import com.hypixel.hytale.component.CommandBuffer; @@ -98,7 +99,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, } catch (Exception e) { // Fail-closed: cancel on any exception to prevent unauthorized item pickup event.setCancelled(true); - Logger.severe("Item pickup cancelled due to protection error (fail-closed)", e); + ErrorHandler.report("Item pickup cancelled due to protection error (fail-closed)", e); } } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/PlayerDeathSystem.java b/src/main/java/com/hyperfactions/protection/ecs/PlayerDeathSystem.java index 1987420..80909b0 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/PlayerDeathSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/PlayerDeathSystem.java @@ -12,6 +12,7 @@ import com.hyperfactions.manager.CombatTagManager; import com.hyperfactions.manager.PowerManager; import com.hyperfactions.util.ChunkUtil; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; @@ -190,7 +191,7 @@ public void onComponentAdded(@NotNull Ref ref, // Death location announcement for gravestone integration announceDeathLocation(victimUuid, playerRef, store, commandBuffer, ref); } catch (Exception e) { - Logger.severe("Error handling player death in ECS system", e); + ErrorHandler.report("Error handling player death in ECS system", e); } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/PlayerRespawnSystem.java b/src/main/java/com/hyperfactions/protection/ecs/PlayerRespawnSystem.java index ffcd6e0..dee936a 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/PlayerRespawnSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/PlayerRespawnSystem.java @@ -3,6 +3,7 @@ import com.hyperfactions.HyperFactions; import com.hyperfactions.config.ConfigManager; import com.hyperfactions.util.ChunkUtil; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentType; @@ -120,7 +121,7 @@ public void onComponentRemoved(@NotNull Ref ref, playerRef.getUuid()); } } catch (Exception e) { - Logger.severe("Error handling player respawn in ECS system", e); + ErrorHandler.report("Error handling player respawn in ECS system", e); } } } diff --git a/src/main/java/com/hyperfactions/protection/ecs/TeleportCancelOnDamageSystem.java b/src/main/java/com/hyperfactions/protection/ecs/TeleportCancelOnDamageSystem.java index a972cda..16f3dbe 100644 --- a/src/main/java/com/hyperfactions/protection/ecs/TeleportCancelOnDamageSystem.java +++ b/src/main/java/com/hyperfactions/protection/ecs/TeleportCancelOnDamageSystem.java @@ -1,6 +1,7 @@ package com.hyperfactions.protection.ecs; import com.hyperfactions.HyperFactions; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.ArchetypeChunk; @@ -56,7 +57,7 @@ public void handle(int entityIndex, ArchetypeChunk chunk, player::sendMessage ); } catch (Exception e) { - Logger.severe("Error processing damage event for teleport cancellation", e); + ErrorHandler.report("Error processing damage event for teleport cancellation", e); } } } diff --git a/src/main/java/com/hyperfactions/storage/JsonEconomyStorage.java b/src/main/java/com/hyperfactions/storage/JsonEconomyStorage.java index 23c77e9..481bbcf 100644 --- a/src/main/java/com/hyperfactions/storage/JsonEconomyStorage.java +++ b/src/main/java/com/hyperfactions/storage/JsonEconomyStorage.java @@ -4,6 +4,7 @@ import com.hyperfactions.api.EconomyAPI; import com.hyperfactions.data.FactionEconomy.TreasuryLimits; import com.hyperfactions.data.FactionEconomy; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.math.BigDecimal; @@ -44,7 +45,7 @@ public CompletableFuture init() { StorageUtils.cleanupOrphanedFiles(economyDir); Logger.info("[Storage] Economy storage initialized at %s", economyDir); } catch (IOException e) { - Logger.severe("Failed to create economy directory", e); + ErrorHandler.report("Failed to create economy directory", e); } }); } @@ -71,12 +72,11 @@ public CompletableFuture> loadAll() { JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); economies.put(factionId, deserialize(obj)); } catch (Exception e) { - Logger.severe("Failed to load economy file %s: %s", - file.getFileName(), e.getMessage()); + ErrorHandler.report(String.format("Failed to load economy file %s", file.getFileName()), e); } } } catch (IOException e) { - Logger.severe("Failed to read economy directory", e); + ErrorHandler.report("Failed to read economy directory", e); } Logger.info("[Storage] Loaded economy data for %d factions", economies.size()); @@ -97,7 +97,7 @@ public CompletableFuture save(@NotNull UUID factionId, @NotNull FactionEco String content = gson.toJson(obj); StorageUtils.WriteResult result = StorageUtils.writeAtomic(file, content); if (result instanceof StorageUtils.WriteResult.Failure failure) { - Logger.severe("Failed to save economy for faction %s: %s", factionId, failure.error()); + ErrorHandler.report(String.format("Failed to save economy for faction %s: %s", factionId, failure.error()), failure.cause()); } }); } @@ -115,8 +115,7 @@ public CompletableFuture saveAll(@NotNull Map econom String content = gson.toJson(obj); StorageUtils.WriteResult result = StorageUtils.writeAtomic(file, content); if (result instanceof StorageUtils.WriteResult.Failure failure) { - Logger.severe("Failed to save economy for faction %s: %s", - entry.getKey(), failure.error()); + ErrorHandler.report(String.format("Failed to save economy for faction %s: %s", entry.getKey(), failure.error()), failure.cause()); } } Logger.debug("Saved economy data for %d factions", economies.size()); diff --git a/src/main/java/com/hyperfactions/storage/StorageUtils.java b/src/main/java/com/hyperfactions/storage/StorageUtils.java index fbd2393..29804a2 100644 --- a/src/main/java/com/hyperfactions/storage/StorageUtils.java +++ b/src/main/java/com/hyperfactions/storage/StorageUtils.java @@ -1,5 +1,6 @@ package com.hyperfactions.storage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -134,7 +135,7 @@ public static WriteResult writeAtomic(@NotNull Path targetFile, @NotNull String } catch (IOException ignored) {} String error = "I/O error during atomic write: " + e.getMessage(); - Logger.severe("[Storage] %s for %s", error, targetFile); + ErrorHandler.report(String.format("[Storage] %s for %s", error, targetFile), e); return new WriteResult.Failure(targetFile, error, e); } catch (Exception e) { @@ -144,7 +145,7 @@ public static WriteResult writeAtomic(@NotNull Path targetFile, @NotNull String } catch (IOException ignored) {} String error = "Unexpected error during atomic write: " + e.getMessage(); - Logger.severe("[Storage] %s for %s", error, targetFile); + ErrorHandler.report(String.format("[Storage] %s for %s", error, targetFile), e); return new WriteResult.Failure(targetFile, error, e); } } @@ -248,7 +249,7 @@ public static boolean recoverFromBackup(@NotNull Path targetFile) { return true; } catch (IOException e) { - Logger.severe("[Storage] Failed to recover %s from backup: %s", targetFile, e.getMessage()); + ErrorHandler.report(String.format("[Storage] Failed to recover %s from backup", targetFile), e); return false; } } diff --git a/src/main/java/com/hyperfactions/storage/json/JsonChatHistoryStorage.java b/src/main/java/com/hyperfactions/storage/json/JsonChatHistoryStorage.java index 3c9ae55..e5a4a5e 100644 --- a/src/main/java/com/hyperfactions/storage/json/JsonChatHistoryStorage.java +++ b/src/main/java/com/hyperfactions/storage/json/JsonChatHistoryStorage.java @@ -11,6 +11,7 @@ import com.hyperfactions.storage.ChatHistoryStorage; import com.hyperfactions.storage.StorageHealth; import com.hyperfactions.storage.StorageUtils; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.util.UuidUtil; import java.io.IOException; @@ -51,7 +52,7 @@ public CompletableFuture init() { StorageUtils.cleanupOrphanedFiles(chatDir); Logger.info("[Storage] Chat history storage initialized at %s", chatDir); } catch (IOException e) { - Logger.severe("Failed to create chat history directory", e); + ErrorHandler.report("Failed to create chat history directory", e); } }); } @@ -83,14 +84,14 @@ public CompletableFuture loadHistory(@NotNull UUID factionId JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); return deserializeHistory(factionId, obj); } catch (Exception e) { - Logger.severe("Failed to load chat history for %s, attempting backup recovery", e, factionId); + ErrorHandler.report(String.format("Failed to load chat history for %s, attempting backup recovery", factionId), e); if (StorageUtils.recoverFromBackup(file)) { try { String json = Files.readString(file); JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); return deserializeHistory(factionId, obj); } catch (Exception e2) { - Logger.severe("Backup recovery failed for chat history %s", e2, factionId); + ErrorHandler.report(String.format("Backup recovery failed for chat history %s", factionId), e2); } } return FactionChatHistory.empty(factionId); @@ -116,11 +117,11 @@ public CompletableFuture saveHistory(@NotNull FactionChatHistory history) Logger.debug("Saved chat history for %s (%d messages)", history.factionId(), history.size()); } else if (result instanceof StorageUtils.WriteResult.Failure failure) { StorageHealth.get().recordFailure(filePath, failure.error()); - Logger.severe("Failed to save chat history for %s: %s", history.factionId(), failure.error()); + ErrorHandler.report(String.format("Failed to save chat history for %s: %s", history.factionId(), failure.error()), failure.cause()); } } catch (Exception e) { StorageHealth.get().recordFailure(filePath, e.getMessage()); - Logger.severe("Failed to save chat history for %s", e, history.factionId()); + ErrorHandler.report(String.format("Failed to save chat history for %s", history.factionId()), e); } }); } @@ -156,7 +157,7 @@ public CompletableFuture> listAllFactionIds() { } } } catch (IOException e) { - Logger.severe("Failed to list chat history files", e); + ErrorHandler.report("Failed to list chat history files", e); } return ids; diff --git a/src/main/java/com/hyperfactions/storage/json/JsonFactionStorage.java b/src/main/java/com/hyperfactions/storage/json/JsonFactionStorage.java index 48d090f..3aeeb64 100644 --- a/src/main/java/com/hyperfactions/storage/json/JsonFactionStorage.java +++ b/src/main/java/com/hyperfactions/storage/json/JsonFactionStorage.java @@ -10,6 +10,7 @@ import com.hyperfactions.storage.FactionStorage; import com.hyperfactions.storage.StorageHealth; import com.hyperfactions.storage.StorageUtils; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -51,7 +52,7 @@ public CompletableFuture init() { StorageUtils.cleanupOrphanedFiles(factionsDir); Logger.info("[Storage] Faction storage initialized at %s", factionsDir); } catch (IOException e) { - Logger.severe("Failed to create factions directory", e); + ErrorHandler.report("Failed to create factions directory", e); } }); } @@ -86,7 +87,7 @@ public CompletableFuture> loadFaction(@NotNull UUID factionId) JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); return Optional.of(deserializeFaction(obj)); } catch (Exception e) { - Logger.severe("Failed to load faction %s, attempting backup recovery", e, factionId); + ErrorHandler.report(String.format("Failed to load faction %s, attempting backup recovery", factionId), e); // Attempt backup recovery on parse failure if (StorageUtils.recoverFromBackup(file)) { try { @@ -95,7 +96,7 @@ public CompletableFuture> loadFaction(@NotNull UUID factionId) Logger.info("[Storage] Successfully loaded faction %s from recovered backup", factionId); return Optional.of(deserializeFaction(obj)); } catch (Exception e2) { - Logger.severe("Backup recovery failed for faction %s", e2, factionId); + ErrorHandler.report(String.format("Backup recovery failed for faction %s", factionId), e2); } } return Optional.empty(); @@ -122,11 +123,11 @@ public CompletableFuture saveFaction(@NotNull Faction faction) { Logger.debug("Saved faction %s (checksum: %s)", faction.name(), success.checksum().substring(0, 8)); } else if (result instanceof StorageUtils.WriteResult.Failure failure) { StorageHealth.get().recordFailure(filePath, failure.error()); - Logger.severe("Failed to save faction %s: %s", faction.name(), failure.error()); + ErrorHandler.report(String.format("Failed to save faction %s: %s", faction.name(), failure.error()), failure.cause()); } } catch (Exception e) { StorageHealth.get().recordFailure(filePath, e.getMessage()); - Logger.severe("Failed to save faction %s", e, faction.name()); + ErrorHandler.report(String.format("Failed to save faction %s", faction.name()), e); } }); } @@ -163,13 +164,13 @@ public CompletableFuture> loadAllFactions() { factions.add(deserializeFaction(obj)); } catch (Exception e) { failedFiles.add(file.getFileName().toString()); - Logger.severe("Failed to load faction file %s: %s", file.getFileName(), e.getMessage()); + ErrorHandler.report(String.format("Failed to load faction file %s", file.getFileName()), e); // Log full stack trace for debugging Logger.debug("Stack trace for %s: %s", file.getFileName(), e.toString()); } } } catch (IOException e) { - Logger.severe("CRITICAL: Failed to read factions directory - data may be lost!", e); + ErrorHandler.report("CRITICAL: Failed to read factions directory - data may be lost!", e); throw new RuntimeException("Failed to read factions directory", e); } diff --git a/src/main/java/com/hyperfactions/storage/json/JsonPlayerStorage.java b/src/main/java/com/hyperfactions/storage/json/JsonPlayerStorage.java index 3997669..43fe0e6 100644 --- a/src/main/java/com/hyperfactions/storage/json/JsonPlayerStorage.java +++ b/src/main/java/com/hyperfactions/storage/json/JsonPlayerStorage.java @@ -12,6 +12,7 @@ import com.hyperfactions.storage.PlayerStorage; import com.hyperfactions.storage.StorageHealth; import com.hyperfactions.storage.StorageUtils; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hyperfactions.util.UuidUtil; import java.io.IOException; @@ -63,7 +64,7 @@ public CompletableFuture init() { StorageUtils.cleanupOrphanedFiles(playersDir); Logger.info("[Storage] Player storage initialized at %s", playersDir); } catch (IOException e) { - Logger.severe("Failed to create players directory", e); + ErrorHandler.report("Failed to create players directory", e); } }); } @@ -98,7 +99,7 @@ public CompletableFuture savePlayerPower(@NotNull PlayerPower power) { } catch (Exception e) { Path file = playersDir.resolve(power.uuid() + ".json"); StorageHealth.get().recordFailure(file.toString(), e.getMessage()); - Logger.severe("Failed to save player power %s", e, power.uuid()); + ErrorHandler.report(String.format("Failed to save player power %s", power.uuid()), e); } finally { lock.unlock(); } @@ -137,11 +138,11 @@ public CompletableFuture> loadAllPlayerPower() { powers.add(deserializePlayerData(obj).toPower()); } catch (Exception e) { failedFiles.add(file.getFileName().toString()); - Logger.severe("Failed to load player file %s: %s", file.getFileName(), e.getMessage()); + ErrorHandler.report(String.format("Failed to load player file %s", file.getFileName()), e); } } } catch (IOException e) { - Logger.severe("CRITICAL: Failed to read players directory - data may be lost!", e); + ErrorHandler.report("CRITICAL: Failed to read players directory - data may be lost!", e); throw new RuntimeException("Failed to read players directory", e); } @@ -176,7 +177,7 @@ public CompletableFuture> getAllPlayerUuids() { // Skip non-UUID filenames } } catch (IOException e) { - Logger.severe("Failed to list player files: %s", e.getMessage()); + ErrorHandler.report("Failed to list player files", e); } return uuids; }); @@ -242,11 +243,11 @@ private void savePlayerDataSync(@NotNull PlayerData data) { StorageHealth.get().recordSuccess(filePath); } else if (result instanceof StorageUtils.WriteResult.Failure failure) { StorageHealth.get().recordFailure(filePath, failure.error()); - Logger.severe("Failed to save player data %s: %s", data.getUuid(), failure.error()); + ErrorHandler.report(String.format("Failed to save player data %s: %s", data.getUuid(), failure.error()), failure.cause()); } } catch (Exception e) { StorageHealth.get().recordFailure(filePath, e.getMessage()); - Logger.severe("Failed to save player data %s", e, data.getUuid()); + ErrorHandler.report(String.format("Failed to save player data %s", data.getUuid()), e); } } @@ -272,7 +273,7 @@ private PlayerData loadPlayerDataSync(@NotNull UUID uuid) { JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); return deserializePlayerData(obj); } catch (Exception e) { - Logger.severe("Failed to load player data %s, attempting backup recovery", e, uuid); + ErrorHandler.report(String.format("Failed to load player data %s, attempting backup recovery", uuid), e); if (StorageUtils.recoverFromBackup(file)) { try { String json = Files.readString(file); @@ -280,7 +281,7 @@ private PlayerData loadPlayerDataSync(@NotNull UUID uuid) { Logger.info("[Storage] Successfully loaded player data %s from recovered backup", uuid); return deserializePlayerData(obj); } catch (Exception e2) { - Logger.severe("Backup recovery failed for player data %s", e2, uuid); + ErrorHandler.report(String.format("Backup recovery failed for player data %s", uuid), e2); } } return null; diff --git a/src/main/java/com/hyperfactions/storage/json/JsonZoneStorage.java b/src/main/java/com/hyperfactions/storage/json/JsonZoneStorage.java index 53e8de3..47692a3 100644 --- a/src/main/java/com/hyperfactions/storage/json/JsonZoneStorage.java +++ b/src/main/java/com/hyperfactions/storage/json/JsonZoneStorage.java @@ -13,6 +13,7 @@ import com.hyperfactions.storage.StorageHealth; import com.hyperfactions.storage.StorageUtils; import com.hyperfactions.storage.ZoneStorage; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.IOException; import java.nio.file.Files; @@ -55,7 +56,7 @@ public CompletableFuture init() { StorageUtils.cleanupOrphanedFiles(dataDir); Logger.info("[Storage] Zone storage initialized"); } catch (IOException e) { - Logger.severe("Failed to create data directory", e); + ErrorHandler.report("Failed to create data directory", e); } }); } @@ -106,7 +107,7 @@ public CompletableFuture> loadAllZones() { } } catch (Exception ignored) {} failedZones.add(zoneName); - Logger.severe("Failed to parse zone '%s': %s", zoneName, e.getMessage()); + ErrorHandler.report(String.format("Failed to parse zone '%s'", zoneName), e); } } @@ -126,7 +127,7 @@ public CompletableFuture> loadAllZones() { } } catch (Exception e) { - Logger.severe("CRITICAL: Failed to load zones file, attempting backup recovery", e); + ErrorHandler.report("CRITICAL: Failed to load zones file, attempting backup recovery", e); // Attempt backup recovery on parse failure if (StorageUtils.recoverFromBackup(zonesFile)) { try { @@ -140,7 +141,7 @@ public CompletableFuture> loadAllZones() { Logger.info("[Storage] Successfully loaded %d zones from recovered backup", zones.size()); return zones; } catch (Exception e2) { - Logger.severe("Backup recovery failed for zones file", e2); + ErrorHandler.report("Backup recovery failed for zones file", e2); } } throw new RuntimeException("Failed to load zones file", e); @@ -176,7 +177,7 @@ public CompletableFuture saveAllZones(@NotNull Collection zones) { } } catch (Exception e) { String error = "Pre-write validation failed: generated JSON is not valid: " + e.getMessage(); - Logger.severe("[ZoneStorage] %s", error); + ErrorHandler.report(String.format("[ZoneStorage] %s", error), e); StorageHealth.get().recordFailure(filePath, error); return; } @@ -189,11 +190,11 @@ public CompletableFuture saveAllZones(@NotNull Collection zones) { Logger.debug("Saved %d zones (checksum: %s)", zones.size(), success.checksum().substring(0, 8)); } else if (result instanceof StorageUtils.WriteResult.Failure failure) { StorageHealth.get().recordFailure(filePath, failure.error()); - Logger.severe("Failed to save zones: %s", failure.error()); + ErrorHandler.report(String.format("Failed to save zones: %s", failure.error()), failure.cause()); } } catch (Exception e) { StorageHealth.get().recordFailure(filePath, e.getMessage()); - Logger.severe("Failed to save zones", e); + ErrorHandler.report("Failed to save zones", e); } }); } diff --git a/src/main/java/com/hyperfactions/update/UpdateChecker.java b/src/main/java/com/hyperfactions/update/UpdateChecker.java index 95dfb9d..1608cb0 100644 --- a/src/main/java/com/hyperfactions/update/UpdateChecker.java +++ b/src/main/java/com/hyperfactions/update/UpdateChecker.java @@ -3,6 +3,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import java.io.*; import java.net.HttpURLConnection; @@ -622,7 +623,7 @@ public RollbackResult performRollback() { return new RollbackResult(true, backupVersion, info != null ? info.toVersion() : null, null); } catch (IOException e) { - Logger.severe("[Update:%s] Rollback failed: %s", artifactName, e.getMessage()); + ErrorHandler.report(String.format("[Update:%s] Rollback failed", artifactName), e); return new RollbackResult(false, backupVersion, null, "Failed to rollback: " + e.getMessage()); } } diff --git a/src/main/java/com/hyperfactions/util/ErrorHandler.java b/src/main/java/com/hyperfactions/util/ErrorHandler.java new file mode 100644 index 0000000..6b55ade --- /dev/null +++ b/src/main/java/com/hyperfactions/util/ErrorHandler.java @@ -0,0 +1,119 @@ +package com.hyperfactions.util; + +import com.hyperfactions.integration.SentryIntegration; +import io.sentry.SentryLevel; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Centralized error handling utility that logs to console and reports to Sentry. + * + *

All methods are safe to call regardless of whether Sentry is initialized — + * {@link SentryIntegration#captureException(String, Throwable)} handles both + * console logging and Sentry reporting internally. + */ +public final class ErrorHandler { + + private ErrorHandler() {} + + /** + * Reports an exception with context. Use in existing catch blocks + * that currently only call {@code Logger.severe()}. + * + * @param context description of what was happening when the error occurred + * @param throwable the exception + */ + public static void report(@NotNull String context, @NotNull Throwable throwable) { + SentryIntegration.captureException(context, throwable); + } + + /** + * Reports a failure that may or may not have an exception cause. + * If a cause is present, reports it as an exception to Sentry. + * If no cause, sends a message event so the failure is still visible in Sentry. + * Always logs to console via {@code Logger.severe()}. + * + * @param context description of what failed + * @param cause the exception, or null if only a string error is available + */ + public static void report(@NotNull String context, @Nullable Exception cause) { + if (cause != null) { + SentryIntegration.captureException(context, cause); + } else { + Logger.severe(context); + SentryIntegration.captureMessage(context, SentryLevel.ERROR); + } + } + + /** + * Wraps a {@link Runnable} so that any exception is caught, logged, and reported. + * Use for scheduled/timer tasks to prevent exceptions from killing scheduler threads. + * + * @param context description of the task for error reports + * @param task the runnable to wrap + * @return a wrapped runnable that never throws + */ + @NotNull + public static Runnable wrapTask(@NotNull String context, @NotNull Runnable task) { + return () -> { + try { + task.run(); + } catch (Exception e) { + SentryIntegration.captureException(context, e); + } + }; + } + + /** + * Adds exception handling to a {@link CompletableFuture} chain. + * Logs and reports any exception, then returns {@code null} to prevent propagation. + * + * @param context description of the async operation + * @param future the future to guard + * @param the future's result type + * @return the same future with an {@code exceptionally} handler attached + */ + @NotNull + public static CompletableFuture guard(@NotNull String context, @NotNull CompletableFuture future) { + return future.exceptionally(e -> { + SentryIntegration.captureException(context, e); + return null; + }); + } + + /** + * Runs an action in isolation, catching and reporting any exception. + * Use for shutdown/cleanup sequences where each step should run independently. + * + * @param context description of the action + * @param action the action to run + */ + public static void runSafely(@NotNull String context, @NotNull Runnable action) { + try { + action.run(); + } catch (Exception e) { + SentryIntegration.captureException(context, e); + } + } + + /** + * Runs a supplier in isolation, returning a default value on failure. + * Use for operations that need a fallback value when they fail. + * + * @param context description of the operation + * @param defaultValue the value to return on failure + * @param supplier the supplier to run + * @param the return type + * @return the supplier's result, or {@code defaultValue} on exception + */ + public static T runSafely(@NotNull String context, T defaultValue, @NotNull Supplier supplier) { + try { + return supplier.get(); + } catch (Exception e) { + SentryIntegration.captureException(context, e); + return defaultValue; + } + } +} diff --git a/src/main/java/com/hyperfactions/worldmap/WorldMapRefreshScheduler.java b/src/main/java/com/hyperfactions/worldmap/WorldMapRefreshScheduler.java index b64586f..7b3d886 100644 --- a/src/main/java/com/hyperfactions/worldmap/WorldMapRefreshScheduler.java +++ b/src/main/java/com/hyperfactions/worldmap/WorldMapRefreshScheduler.java @@ -4,6 +4,7 @@ import com.hyperfactions.config.modules.WorldMapConfig; import com.hyperfactions.data.ChunkKey; import com.hyperfactions.util.ChunkUtil; +import com.hyperfactions.util.ErrorHandler; import com.hyperfactions.util.Logger; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.server.core.entity.entities.Player; @@ -105,7 +106,7 @@ public void start() { long intervalMs = (intervalTicks * 1000L) / 30; batchTask = scheduler.scheduleAtFixedRate( - this::processBatch, + ErrorHandler.wrapTask("World map batch processing", this::processBatch), intervalMs, intervalMs, TimeUnit.MILLISECONDS @@ -433,7 +434,7 @@ private void scheduleDebounceRefresh() { int delaySeconds = config.getDebouncedDelaySeconds(); - debounceTask = scheduler.schedule(() -> { + debounceTask = scheduler.schedule(ErrorHandler.wrapTask("World map debounce refresh", () -> { // Check if enough time has passed since last change long elapsed = System.currentTimeMillis() - lastChangeTime; if (elapsed >= delaySeconds * 1000L) { @@ -443,7 +444,7 @@ private void scheduleDebounceRefresh() { // Reschedule scheduleDebounceRefresh(); } - }, delaySeconds, TimeUnit.SECONDS); + }), delaySeconds, TimeUnit.SECONDS); } /**