From ec7994a561db1a6aad5dc47f9496b7f8c116a34d Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 11 May 2020 01:41:01 +0100 Subject: [PATCH] Print more useful debug message when a LinkageError is thrown due to bad SLF4J classloading This is a longstanding issue, mostly caused by plugins with bad Maven shade configurations. https://github.com/lucko/LuckPerms/issues?q=slf4j --- .../luckperms/bukkit/LPBukkitBootstrap.java | 9 ++++ .../luckperms/bungee/LPBungeeBootstrap.java | 13 +++++ .../plugin/bootstrap/LuckPermsBootstrap.java | 13 +++++ .../implementation/sql/SqlStorage.java | 2 +- .../sql/connection/ConnectionFactory.java | 4 +- .../file/FlatfileConnectionFactory.java | 3 +- .../hikari/HikariConnectionFactory.java | 51 ++++++++++++++++++- 7 files changed, 90 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitBootstrap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitBootstrap.java index a5fb3120d..c484f14c8 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitBootstrap.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitBootstrap.java @@ -39,6 +39,7 @@ import org.bukkit.Server; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; +import org.checkerframework.checker.nullness.qual.Nullable; import java.io.InputStream; import java.nio.file.Path; @@ -268,6 +269,14 @@ public class LPBukkitBootstrap extends JavaPlugin implements LuckPermsBootstrap return player != null && player.isOnline(); } + @Override + public @Nullable String identifyClassLoader(ClassLoader classLoader) { + if (classLoader instanceof org.bukkit.plugin.java.PluginClassLoader) { + return ((org.bukkit.plugin.java.PluginClassLoader) classLoader).getPlugin().getName(); + } + return null; + } + private static boolean checkIncompatibleVersion() { try { Class.forName("com.google.gson.JsonElement"); diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeeBootstrap.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeeBootstrap.java index 7e522f213..d18b0398c 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeeBootstrap.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeeBootstrap.java @@ -36,6 +36,9 @@ import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter; import net.luckperms.api.platform.Platform; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.PluginDescription; + +import org.checkerframework.checker.nullness.qual.Nullable; import java.io.InputStream; import java.nio.file.Path; @@ -260,6 +263,16 @@ public class LPBungeeBootstrap extends Plugin implements LuckPermsBootstrap { return getProxy().getPlayer(uniqueId) != null; } + @Override + public @Nullable String identifyClassLoader(ClassLoader classLoader) throws Exception { + Class pluginClassLoader = Class.forName("net.md_5.bungee.api.plugin.PluginClassloader"); + if (pluginClassLoader.isInstance(classLoader)) { + PluginDescription desc = (PluginDescription) pluginClassLoader.getDeclaredField("desc").get(classLoader); + return desc.getName(); + } + return null; + } + private static boolean checkIncompatibleVersion() { try { Class aClass = Class.forName("com.google.gson.internal.bind.TreeTypeAdapter"); diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java b/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java index 479f5286b..727454c1b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/bootstrap/LuckPermsBootstrap.java @@ -214,4 +214,17 @@ public interface LuckPermsBootstrap { */ boolean isPlayerOnline(UUID uniqueId); + /** + * Attempts to identify the plugin behind the given classloader. + * + *

Used for giving more helpful log messages when things break.

+ * + * @param classLoader the classloader to identify + * @return the name of the classloader source + * @throws Exception anything + */ + default @Nullable String identifyClassLoader(ClassLoader classLoader) throws Exception { + return null; + } + } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java index ddf92031e..bbb572224 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SqlStorage.java @@ -147,7 +147,7 @@ public class SqlStorage implements StorageImplementation { @Override public void init() throws Exception { - this.connectionFactory.init(); + this.connectionFactory.init(this.plugin); boolean tableExists; try (Connection c = this.connectionFactory.getConnection()) { diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/ConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/ConnectionFactory.java index 8de417866..2aff1a659 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/ConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/ConnectionFactory.java @@ -25,6 +25,8 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; + import java.sql.Connection; import java.sql.SQLException; import java.util.Collections; @@ -35,7 +37,7 @@ public interface ConnectionFactory { String getImplementationName(); - void init(); + void init(LuckPermsPlugin plugin); void shutdown() throws Exception; diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/FlatfileConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/FlatfileConnectionFactory.java index cf9071b21..06f2d1d9a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/FlatfileConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/file/FlatfileConnectionFactory.java @@ -25,6 +25,7 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.storage.implementation.sql.connection.ConnectionFactory; import java.io.IOException; @@ -44,7 +45,7 @@ abstract class FlatfileConnectionFactory implements ConnectionFactory { } @Override - public void init() { + public void init(LuckPermsPlugin plugin) { } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/HikariConnectionFactory.java b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/HikariConnectionFactory.java index 9c70a7d77..fcf898dc7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/HikariConnectionFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/connection/hikari/HikariConnectionFactory.java @@ -25,9 +25,12 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari; +import com.google.common.collect.ImmutableList; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.plugin.logging.PluginLogger; import me.lucko.luckperms.common.storage.implementation.sql.connection.ConnectionFactory; import me.lucko.luckperms.common.storage.misc.StorageCredentials; @@ -36,6 +39,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -73,8 +77,17 @@ public abstract class HikariConnectionFactory implements ConnectionFactory { } @Override - public void init() { - HikariConfig config = new HikariConfig(); + public void init(LuckPermsPlugin plugin) { + HikariConfig config; + try { + config = new HikariConfig(); + } catch (LinkageError e) { + // dumb plugins seem to keep doing stupid stuff with shading of SLF4J and Log4J. + // detect this and print a more useful error message. + handleLinkageError(e, plugin); + throw e; + } + config.setPoolName("luckperms-hikari"); appendConfigurationInfo(config); @@ -141,4 +154,38 @@ public abstract class HikariConnectionFactory implements ConnectionFactory { } return connection; } + + private static void handleLinkageError(LinkageError linkageError, LuckPermsPlugin plugin) { + List noteworthyClasses = ImmutableList.of( + "org.slf4j.LoggerFactory", + "org.slf4j.ILoggerFactory", + "org.apache.logging.slf4j.Log4jLoggerFactory", + "org.apache.logging.log4j.spi.LoggerContext", + "org.apache.logging.log4j.spi.AbstractLoggerAdapter", + "org.slf4j.impl.StaticLoggerBinder" + ); + + PluginLogger logger = plugin.getLogger(); + logger.warn("A " + linkageError.getClass().getSimpleName() + " has occurred whilst initialising Hikari. This is likely due to classloading conflicts between other plugins."); + logger.warn("Please check for other plugins below (and try loading LuckPerms without them installed) before reporting the issue."); + + for (String className : noteworthyClasses) { + Class clazz; + try { + clazz = Class.forName(className); + } catch (Exception ex) { + continue; + } + + ClassLoader loader = clazz.getClassLoader(); + String loaderName; + try { + loaderName = plugin.getBootstrap().identifyClassLoader(loader) + " (" + loader.toString() + ")"; + } catch (Exception e) { + loaderName = loader.toString(); + } + + logger.warn("Class " + className + " has been loaded by: " + loaderName); + } + } }