From acc39b08798ab86b8d4af5b7d05a67b266be7adf Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:56:20 -0500 Subject: [PATCH] Pufferfish: Add Sentry --- patches/api/0011-Pufferfish-Sentry.patch | 237 ++++++++++++++++++++ patches/server/0031-Pufferfish-Sentry.patch | 217 ++++++++++++++++++ 2 files changed, 454 insertions(+) create mode 100644 patches/api/0011-Pufferfish-Sentry.patch create mode 100644 patches/server/0031-Pufferfish-Sentry.patch diff --git a/patches/api/0011-Pufferfish-Sentry.patch b/patches/api/0011-Pufferfish-Sentry.patch new file mode 100644 index 00000000..0272b696 --- /dev/null +++ b/patches/api/0011-Pufferfish-Sentry.patch @@ -0,0 +1,237 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kevin Raneri +Date: Tue, 9 Nov 2021 14:01:56 -0500 +Subject: [PATCH] Pufferfish: Sentry + +Original license: GPL v3 +Original project: https://github.com/pufferfish-gg/Pufferfish + +diff --git a/build.gradle.kts b/build.gradle.kts +index 3075ae381824f8b46d17f40022f23d58f9e50c12..ba12ba4240adea32aa8a58ddfef8a949d8f2b8e8 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -41,6 +41,7 @@ dependencies { + apiAndDocs("net.kyori:adventure-text-logger-slf4j") + api("org.apache.logging.log4j:log4j-api:2.19.0") + api("org.slf4j:slf4j-api:1.8.0-beta4") // Leaf TODO - Bump later, need to fix breaking compatibility ++ api("io.sentry:sentry:6.13.1") // Pufferfish + + implementation("org.ow2.asm:asm:9.4") + implementation("org.ow2.asm:asm-commons:9.4") +diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java +new file mode 100644 +index 0000000000000000000000000000000000000000..10310fdd53de28efb8a8250f6d3b0c8eb08fb68a +--- /dev/null ++++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java +@@ -0,0 +1,161 @@ ++package gg.pufferfish.pufferfish.sentry; ++ ++import com.google.gson.Gson; ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.Map; ++import java.util.TreeMap; ++import org.apache.logging.log4j.ThreadContext; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Event; ++import org.bukkit.event.player.PlayerEvent; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.RegisteredListener; ++import org.jetbrains.annotations.Nullable; ++ ++public class SentryContext { ++ ++ private static final Gson GSON = new Gson(); ++ ++ public static void setPluginContext(@Nullable Plugin plugin) { ++ if (plugin != null) { ++ ThreadContext.put("pufferfishsentry_pluginname", plugin.getName()); ++ ThreadContext.put("pufferfishsentry_pluginversion", plugin.getDescription().getVersion()); ++ } ++ } ++ ++ public static void removePluginContext() { ++ ThreadContext.remove("pufferfishsentry_pluginname"); ++ ThreadContext.remove("pufferfishsentry_pluginversion"); ++ } ++ ++ public static void setSenderContext(@Nullable CommandSender sender) { ++ if (sender != null) { ++ ThreadContext.put("pufferfishsentry_playername", sender.getName()); ++ if (sender instanceof Player player) { ++ ThreadContext.put("pufferfishsentry_playerid", player.getUniqueId().toString()); ++ } ++ } ++ } ++ ++ public static void removeSenderContext() { ++ ThreadContext.remove("pufferfishsentry_playername"); ++ ThreadContext.remove("pufferfishsentry_playerid"); ++ } ++ ++ public static void setEventContext(Event event, RegisteredListener registration) { ++ setPluginContext(registration.getPlugin()); ++ ++ try { ++ // Find the player that was involved with this event ++ Player player = null; ++ if (event instanceof PlayerEvent) { ++ player = ((PlayerEvent) event).getPlayer(); ++ } else { ++ Class eventClass = event.getClass(); ++ ++ Field playerField = null; ++ ++ for (Field field : eventClass.getDeclaredFields()) { ++ if (field.getType().equals(Player.class)) { ++ playerField = field; ++ break; ++ } ++ } ++ ++ if (playerField != null) { ++ playerField.setAccessible(true); ++ player = (Player) playerField.get(event); ++ } ++ } ++ ++ if (player != null) { ++ setSenderContext(player); ++ } ++ } catch (Exception e) {} // We can't really safely log exceptions. ++ ++ ThreadContext.put("pufferfishsentry_eventdata", GSON.toJson(serializeFields(event))); ++ } ++ ++ public static void removeEventContext() { ++ removePluginContext(); ++ removeSenderContext(); ++ ThreadContext.remove("pufferfishsentry_eventdata"); ++ } ++ ++ private static Map serializeFields(Object object) { ++ Map fields = new TreeMap<>(); ++ fields.put("_class", object.getClass().getName()); ++ for (Field declaredField : object.getClass().getDeclaredFields()) { ++ try { ++ if (Modifier.isStatic(declaredField.getModifiers())) { ++ continue; ++ } ++ ++ String fieldName = declaredField.getName(); ++ if (fieldName.equals("handlers")) { ++ continue; ++ } ++ declaredField.setAccessible(true); ++ Object value = declaredField.get(object); ++ if (value != null) { ++ fields.put(fieldName, value.toString()); ++ } else { ++ fields.put(fieldName, ""); ++ } ++ } catch (Exception e) {} // We can't really safely log exceptions. ++ } ++ return fields; ++ } ++ ++ public static class State { ++ ++ private Plugin plugin; ++ private Command command; ++ private String commandLine; ++ private Event event; ++ private RegisteredListener registeredListener; ++ ++ public Plugin getPlugin() { ++ return plugin; ++ } ++ ++ public void setPlugin(Plugin plugin) { ++ this.plugin = plugin; ++ } ++ ++ public Command getCommand() { ++ return command; ++ } ++ ++ public void setCommand(Command command) { ++ this.command = command; ++ } ++ ++ public String getCommandLine() { ++ return commandLine; ++ } ++ ++ public void setCommandLine(String commandLine) { ++ this.commandLine = commandLine; ++ } ++ ++ public Event getEvent() { ++ return event; ++ } ++ ++ public void setEvent(Event event) { ++ this.event = event; ++ } ++ ++ public RegisteredListener getRegisteredListener() { ++ return registeredListener; ++ } ++ ++ public void setRegisteredListener(RegisteredListener registeredListener) { ++ this.registeredListener = registeredListener; ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 730394fd53fdaa90133ee399f86e76da8ccc08e6..17b8d42f8e2897eb019ef12ad772ed0d505f7e84 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -625,7 +625,9 @@ public final class SimplePluginManager implements PluginManager { + + // Paper start + private void handlePluginException(String msg, Throwable ex, Plugin plugin) { ++ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish + server.getLogger().log(Level.SEVERE, msg, ex); ++ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish + callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerPluginEnableDisableException(msg, ex, plugin))); + } + // Paper end +@@ -693,9 +695,11 @@ public final class SimplePluginManager implements PluginManager { + )); + } + } catch (Throwable ex) { ++ gg.pufferfish.pufferfish.sentry.SentryContext.setEventContext(event, registration); // Pufferfish + // Paper start - error reporting + String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(); + server.getLogger().log(Level.SEVERE, msg, ex); ++ gg.pufferfish.pufferfish.sentry.SentryContext.removeEventContext(); // Pufferfish + if (!(event instanceof com.destroystokyo.paper.event.server.ServerExceptionEvent)) { // We don't want to cause an endless event loop + callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); + } +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +index 7e5149a21b6747f64425a277c142e69f0ef43b3f..bf93183aa76af005d5daf8cf533f908d535166e8 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +@@ -370,7 +370,9 @@ public final class JavaPluginLoader implements PluginLoader { + try { + jPlugin.setEnabled(true); + } catch (Throwable ex) { ++ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish + server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish + // Paper start - Disable plugins that fail to load + this.server.getPluginManager().disablePlugin(jPlugin); + return; +@@ -399,7 +401,9 @@ public final class JavaPluginLoader implements PluginLoader { + try { + jPlugin.setEnabled(false); + } catch (Throwable ex) { ++ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish + server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish + } + + if (cloader instanceof PluginClassLoader) { diff --git a/patches/server/0031-Pufferfish-Sentry.patch b/patches/server/0031-Pufferfish-Sentry.patch new file mode 100644 index 00000000..09470fbd --- /dev/null +++ b/patches/server/0031-Pufferfish-Sentry.patch @@ -0,0 +1,217 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kevin Raneri +Date: Tue, 9 Nov 2021 14:08:14 -0500 +Subject: [PATCH] Pufferfish: Sentry + +Original license: GPL v3 +Original project: https://github.com/pufferfish-gg/Pufferfish + +diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java +new file mode 100644 +index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834733d0621 +--- /dev/null ++++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java +@@ -0,0 +1,135 @@ ++package gg.pufferfish.pufferfish.sentry; ++ ++import com.google.common.reflect.TypeToken; ++import com.google.gson.Gson; ++import io.sentry.Breadcrumb; ++import io.sentry.Sentry; ++import io.sentry.SentryEvent; ++import io.sentry.SentryLevel; ++import io.sentry.protocol.Message; ++import io.sentry.protocol.User; ++import java.util.Map; ++import org.apache.logging.log4j.Level; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Marker; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.Logger; ++import org.apache.logging.log4j.core.appender.AbstractAppender; ++import org.apache.logging.log4j.core.filter.AbstractFilter; ++ ++public class PufferfishSentryAppender extends AbstractAppender { ++ ++ private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class); ++ private static final Gson GSON = new Gson(); ++ ++ public PufferfishSentryAppender() { ++ super("PufferfishSentryAdapter", new SentryFilter(), null); ++ } ++ ++ @Override ++ public void append(LogEvent logEvent) { ++ if (logEvent.getThrown() != null && logEvent.getLevel().isMoreSpecificThan(Level.WARN)) { ++ try { ++ logException(logEvent); ++ } catch (Exception e) { ++ logger.warn("Failed to log event with sentry", e); ++ } ++ } else { ++ try { ++ logBreadcrumb(logEvent); ++ } catch (Exception e) { ++ logger.warn("Failed to log event with sentry", e); ++ } ++ } ++ } ++ ++ private void logException(LogEvent e) { ++ SentryEvent event = new SentryEvent(e.getThrown()); ++ ++ Message sentryMessage = new Message(); ++ sentryMessage.setMessage(e.getMessage().getFormattedMessage()); ++ ++ event.setThrowable(e.getThrown()); ++ event.setLevel(getLevel(e.getLevel())); ++ event.setLogger(e.getLoggerName()); ++ event.setTransaction(e.getLoggerName()); ++ event.setExtra("thread_name", e.getThreadName()); ++ ++ boolean hasContext = e.getContextData() != null; ++ ++ if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) { ++ User user = new User(); ++ user.setId(e.getContextData().getValue("pufferfishsentry_playerid")); ++ user.setUsername(e.getContextData().getValue("pufferfishsentry_playername")); ++ event.setUser(user); ++ } ++ ++ if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) { ++ event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname")); ++ event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion")); ++ event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname")); ++ } ++ ++ if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) { ++ Map eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken>() {}.getType()); ++ if (eventFields != null) { ++ event.setExtra("event", eventFields); ++ } ++ } ++ ++ Sentry.captureEvent(event); ++ } ++ ++ private void logBreadcrumb(LogEvent e) { ++ Breadcrumb breadcrumb = new Breadcrumb(); ++ ++ breadcrumb.setLevel(getLevel(e.getLevel())); ++ breadcrumb.setCategory(e.getLoggerName()); ++ breadcrumb.setType(e.getLoggerName()); ++ breadcrumb.setMessage(e.getMessage().getFormattedMessage()); ++ ++ Sentry.addBreadcrumb(breadcrumb); ++ } ++ ++ private SentryLevel getLevel(Level level) { ++ switch (level.getStandardLevel()) { ++ case TRACE: ++ case DEBUG: ++ return SentryLevel.DEBUG; ++ case WARN: ++ return SentryLevel.WARNING; ++ case ERROR: ++ return SentryLevel.ERROR; ++ case FATAL: ++ return SentryLevel.FATAL; ++ case INFO: ++ default: ++ return SentryLevel.INFO; ++ } ++ } ++ ++ private static class SentryFilter extends AbstractFilter { ++ ++ @Override ++ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, String msg, ++ Object... params) { ++ return this.filter(logger.getName()); ++ } ++ ++ @Override ++ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) { ++ return this.filter(logger.getName()); ++ } ++ ++ @Override ++ public Result filter(LogEvent event) { ++ return this.filter(event == null ? null : event.getLoggerName()); ++ } ++ ++ private Result filter(String loggerName) { ++ return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY ++ : Result.NEUTRAL; ++ } ++ ++ } ++} +diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bcacccd9d47697bea0405f2544be94fca863c494 +--- /dev/null ++++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java +@@ -0,0 +1,40 @@ ++package gg.pufferfish.pufferfish.sentry; ++ ++import io.sentry.Sentry; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import org.dreeam.leaf.LeafConfig; ++ ++public class SentryManager { ++ ++ private static final Logger logger = LogManager.getLogger(SentryManager.class); ++ ++ private SentryManager() { ++ ++ } ++ ++ private static boolean initialized = false; ++ ++ public static synchronized void init() { ++ if (initialized) { ++ return; ++ } ++ try { ++ initialized = true; ++ ++ Sentry.init(options -> { ++ options.setDsn(LeafConfig.sentryDsn); ++ options.setMaxBreadcrumbs(100); ++ }); ++ ++ PufferfishSentryAppender appender = new PufferfishSentryAppender(); ++ appender.start(); ++ ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender); ++ logger.info("Sentry logging started!"); ++ } catch (Exception e) { ++ logger.warn("Failed to initialize sentry!", e); ++ initialized = false; ++ } ++ } ++ ++} +diff --git a/src/main/java/org/dreeam/leaf/LeafConfig.java b/src/main/java/org/dreeam/leaf/LeafConfig.java +index e0d5e00b4291e66b27a3a0c1701af3c4e7b3db26..cf55ae28c3868d23bed9d3eef04b16a0cc83775f 100644 +--- a/src/main/java/org/dreeam/leaf/LeafConfig.java ++++ b/src/main/java/org/dreeam/leaf/LeafConfig.java +@@ -139,6 +139,17 @@ public class LeafConfig { + serverModName = getString("settings.server-mod-name", serverModName); + } + ++ public static String sentryDsn; ++ private static void sentry() { ++ String sentryEnvironment = System.getenv("SENTRY_DSN"); ++ String sentryConfig = getString("sentry-dsn", "", "Sentry DSN for improved error logging, leave blank to disable", "Obtain from https://sentry.io/"); ++ ++ sentryDsn = sentryEnvironment == null ? sentryConfig : sentryEnvironment; ++ if (sentryDsn != null && !sentryDsn.isBlank()) { ++ gg.pufferfish.pufferfish.sentry.SentryManager.init(); ++ } ++ } ++ + public static boolean removeMojangUsernameCheck; + public static boolean removeSpigotCheckBungeeConfig; + public static boolean removeUseItemOnPacketTooFar;