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
This commit is contained in:
Luck 2020-05-11 01:41:01 +01:00
parent 2839c36ea2
commit ec7994a561
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
7 changed files with 90 additions and 5 deletions

View File

@ -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");

View File

@ -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");

View File

@ -214,4 +214,17 @@ public interface LuckPermsBootstrap {
*/
boolean isPlayerOnline(UUID uniqueId);
/**
* Attempts to identify the plugin behind the given classloader.
*
* <p>Used for giving more helpful log messages when things break.</p>
*
* @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;
}
}

View File

@ -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()) {

View File

@ -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;

View File

@ -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) {
}

View File

@ -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<String> 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);
}
}
}