Debug reports

This commit is contained in:
Vankka 2022-01-25 19:14:16 +02:00
parent 6f638ffb33
commit d1d05e7081
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
13 changed files with 438 additions and 15 deletions

View File

@ -31,6 +31,9 @@ import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -127,6 +130,12 @@ public class VaultIntegration extends PluginIntegration<BukkitDiscordSRV> implem
return discordSRV.plugin().getServer().getOfflinePlayer(player);
}
@Override
public List<String> getGroups() {
String[] groups = permission.getGroups();
return groups != null ? Arrays.asList(groups) : Collections.emptyList();
}
@Override
public CompletableFuture<Boolean> hasGroup(UUID player, String groupName, boolean includeInherited) {
if (permission == null || !permission.isEnabled() || !permission.hasGroupSupport()) {

View File

@ -40,6 +40,9 @@ dependencies {
runtimeDownloadApi 'org.spongepowered:configurate-yaml:' + rootProject.configurateVersion
runtimeDownloadApi 'org.spongepowered:configurate-hocon:' + rootProject.configurateVersion
// Jackson (transitive in :api)
compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.13.1'
// Logging
compileOnlyApi project(':common:common-slf4j-hack')
compileOnly 'org.apache.logging.log4j:log4j-core:2.0-beta9'

View File

@ -101,8 +101,8 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
*/
protected final void load() {
this.logger = new DiscordSRVLogger(this);
this.moduleManager = new ModuleManager(this);
this.eventBus = new EventBusImpl(this);
this.moduleManager = new ModuleManager(this);
this.placeholderService = new PlaceholderServiceImpl(this);
this.componentFactory = new ComponentFactory(this);
this.discordAPI = new DiscordAPIImpl(this);

View File

@ -0,0 +1,35 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.debug;
import com.discordsrv.api.event.events.Event;
import com.discordsrv.common.debug.file.DebugFile;
public class DebugGenerateEvent implements Event {
private final DebugReport report;
public DebugGenerateEvent(DebugReport report) {
this.report = report;
}
public void addFile(DebugFile file) {
report.addFile(file);
}
}

View File

@ -0,0 +1,110 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.debug;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.debug.file.DebugFile;
import com.discordsrv.common.debug.file.KeyValueDebugFile;
import com.discordsrv.common.debug.file.TextDebugFile;
import com.discordsrv.common.plugin.Plugin;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class DebugReport {
private final List<DebugFile> files = new ArrayList<>();
private final DiscordSRV discordSRV;
public DebugReport(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
public void generate() {
discordSRV.eventBus().publish(new DebugGenerateEvent(this));
addFile(environment());
addFile(plugins());
for (Path debugLog : discordSRV.logger().getDebugLogs()) {
addFile(readFile(1, debugLog));
}
}
public void addFile(DebugFile file) {
files.add(file);
}
public List<DebugFile> getFiles() {
return files;
}
private DebugFile environment() {
Map<String, Object> values = new LinkedHashMap<>();
values.put("discordSRV", discordSRV.getClass().getSimpleName());
values.put("version", discordSRV.version());
values.put("status", discordSRV.status().name());
values.put("jdaStatus", discordSRV.jda().map(jda -> jda.getStatus().name()).orElse("JDA null"));
values.put("platformLogger", discordSRV.platformLogger().getClass().getName());
values.put("onlineMode", discordSRV.onlineMode().name());
values.put("javaVersion", System.getProperty("java.version"));
values.put("javaVendor", System.getProperty("java.vendor")
+ " (" + System.getProperty("java.vendor.url") + ")");
values.put("operatingSystem", System.getProperty("os.name")
+ " " + System.getProperty("os.version")
+ " (" + System.getProperty("os.arch") + ")");
return new KeyValueDebugFile(10, "environment.json", values);
}
private DebugFile plugins() {
List<Plugin> plugins = discordSRV.pluginManager().getPlugins();
StringBuilder builder = new StringBuilder("Plugins (" + plugins.size() + "):\n");
for (Plugin plugin : plugins) {
builder.append('\n')
.append(plugin.name())
.append(" v").append(plugin.version())
.append(" ").append(plugin.authors());
}
return new TextDebugFile(5, "plugins.txt", builder.toString());
}
private DebugFile readFile(int order, Path file) {
String fileName = file.getFileName().toString();
if (!Files.exists(file)) {
return new TextDebugFile(order, fileName, "File does not exist");
}
try {
List<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8);
return new TextDebugFile(order, fileName, String.join("\n", lines));
} catch (IOException e) {
return new TextDebugFile(order, fileName, ExceptionUtils.getStackTrace(e));
}
}
}

View File

@ -0,0 +1,26 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.debug.file;
public interface DebugFile {
int order();
String name();
String content();
}

View File

@ -0,0 +1,59 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.debug.file;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.util.Map;
public class KeyValueDebugFile implements DebugFile {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final int order;
private final String name;
private final Map<String, Object> values;
public KeyValueDebugFile(int order, String name, Map<String, Object> values) {
this.order = order;
this.name = name;
this.values = values;
}
@Override
public int order() {
return order;
}
@Override
public String name() {
return name;
}
@Override
public String content() {
try {
return OBJECT_MAPPER.writeValueAsString(values);
} catch (JsonProcessingException e) {
return ExceptionUtils.getStackTrace(e);
}
}
}

View File

@ -0,0 +1,49 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.debug.file;
public class TextDebugFile implements DebugFile {
private final int order;
private final String name;
private final String content;
public TextDebugFile(String name, CharSequence content) {
this(0, name, content);
}
public TextDebugFile(int order, String name, CharSequence content) {
this.order = order;
this.name = name;
this.content = content.toString();
}
public int order() {
return order;
}
public String name() {
return name;
}
public String content() {
return content;
}
}

View File

@ -27,6 +27,8 @@ import com.discordsrv.api.event.events.Cancellable;
import com.discordsrv.api.event.events.Event;
import com.discordsrv.api.event.events.Processable;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.debug.DebugGenerateEvent;
import com.discordsrv.common.debug.file.TextDebugFile;
import com.discordsrv.common.exception.InvalidListenerMethodException;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger;
@ -60,6 +62,7 @@ public class EventBusImpl implements EventBus {
public EventBusImpl(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
this.logger = new NamedLogger(discordSRV, "EVENT_BUS");
subscribe(this);
}
@Override
@ -234,4 +237,32 @@ public class EventBusImpl implements EventBus {
state.getValue().remove();
}
}
@Subscribe
public void onDebugGenerate(DebugGenerateEvent event) {
StringBuilder builder = new StringBuilder("Registered listeners (" + listeners.size() + "/" + allListeners.size() + "):\n");
for (Map.Entry<Object, List<EventListenerImpl>> entry : listeners.entrySet()) {
Object listener = entry.getKey();
List<EventListenerImpl> eventListeners = entry.getValue();
builder.append('\n')
.append(listener)
.append(" (")
.append(listener.getClass().getName())
.append(") [")
.append(eventListeners.size())
.append("]\n");
for (EventListenerImpl eventListener : eventListeners) {
builder.append(" - ")
.append(eventListener.eventClass().getName())
.append(": ")
.append(eventListener.methodName())
.append(" @ ")
.append(eventListener.priority().name())
.append('\n');
}
}
event.addFile(new TextDebugFile("event-bus.txt", builder));
}
}

View File

@ -25,6 +25,8 @@ import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleRemoveEven
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.GroupSyncConfig;
import com.discordsrv.common.debug.DebugGenerateEvent;
import com.discordsrv.common.debug.file.TextDebugFile;
import com.discordsrv.common.future.util.CompletableFutureUtil;
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
import com.discordsrv.common.groupsync.enums.GroupSyncDirection;
@ -115,6 +117,36 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
}
}
// Debug
@Subscribe
public void onDebugGenerate(DebugGenerateEvent event) {
StringBuilder builder = new StringBuilder("Active pairs:");
for (Map.Entry<GroupSyncConfig.PairConfig, Future<?>> entry : pairs.entrySet()) {
GroupSyncConfig.PairConfig pair = entry.getKey();
builder.append("\n- ").append(pair)
.append(" (tie-breaker: ").append(pair.tieBreaker())
.append(", direction: ").append(pair.direction())
.append(", server context: ").append(pair.serverContext).append(")");
if (entry.getValue() != null) {
builder.append(" [Timed]");
}
}
PermissionDataProvider.Groups groups = getPermissionProvider();
if (groups != null) {
builder.append("\n\nAvailable groups (").append(groups.getClass().getName()).append("):");
for (String group : groups.getGroups()) {
builder.append("\n- ").append(group);
}
} else {
builder.append("\n\nNo permission provider available");
}
event.addFile(new TextDebugFile("group-sync.txt", builder));
}
private void logSummary(
UUID player,
GroupSyncCause cause,
@ -146,6 +178,15 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
return groupsContext == null ? discordSRV.getModule(PermissionDataProvider.Groups.class) : groupsContext;
}
private boolean noPermissionProvider() {
PermissionDataProvider.Groups groups = getPermissionProvider();
return groups == null || !groups.isEnabled();
}
private boolean supportsOffline() {
return getPermissionProvider().supportsOffline();
}
private CompletableFuture<Boolean> hasGroup(
UUID player,
String groupName,
@ -209,6 +250,10 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
}
public void resync(UUID player, long userId, GroupSyncCause cause) {
if (noPermissionProvider() || (!discordSRV.playerProvider().player(player).isPresent() && !supportsOffline())) {
return;
}
Map<GroupSyncConfig.PairConfig, CompletableFuture<GroupSyncResult>> futures = new LinkedHashMap<>();
for (GroupSyncConfig.PairConfig pair : pairs.keySet()) {
futures.put(pair, resyncPair(pair, player, userId));
@ -218,6 +263,10 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
}
private void resyncPair(GroupSyncConfig.PairConfig pair, GroupSyncCause cause) {
if (noPermissionProvider()) {
return;
}
for (IPlayer player : discordSRV.playerProvider().allPlayers()) {
UUID uuid = player.uniqueId();
Long userId = getLinkedAccount(uuid);
@ -352,6 +401,10 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
}
private void roleChanged(long userId, long roleId, boolean remove) {
if (noPermissionProvider()) {
return;
}
if (checkExpectation(expectedDiscordChanges, userId, roleId, remove)) {
return;
}
@ -405,6 +458,10 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
}
private void groupChanged(UUID player, String groupName, @Nullable Set<String> serverContext, GroupSyncCause cause, boolean remove) {
if (noPermissionProvider()) {
return;
}
if (cause.isDiscordSRVCanCause() && checkExpectation(expectedMinecraftChanges, player, groupName, remove)) {
return;
}

View File

@ -52,6 +52,7 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class LuckPermsIntegration extends PluginIntegration<DiscordSRV> implements PermissionDataProvider.All {
@ -241,4 +242,10 @@ public class LuckPermsIntegration extends PluginIntegration<DiscordSRV> implemen
}
}
@Override
public List<String> getGroups() {
return luckPerms.getGroupManager().getLoadedGroups().stream()
.map(Group::getName)
.collect(Collectors.toList());
}
}

View File

@ -22,10 +22,14 @@ import com.discordsrv.api.event.bus.EventPriority;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.debug.DebugGenerateEvent;
import com.discordsrv.common.debug.file.TextDebugFile;
import com.discordsrv.common.module.type.AbstractModule;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -33,20 +37,21 @@ import java.util.concurrent.CopyOnWriteArraySet;
public class ModuleManager {
private final Set<AbstractModule<?>> modules = new CopyOnWriteArraySet<>();
private final Map<String, AbstractModule<?>> moduleLookupTable = new ConcurrentHashMap<>();
private final Set<Module> modules = new CopyOnWriteArraySet<>();
private final Map<String, Module> moduleLookupTable = new ConcurrentHashMap<>();
private final DiscordSRV discordSRV;
public ModuleManager(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
discordSRV.eventBus().subscribe(this);
}
@SuppressWarnings("unchecked")
public <T extends Module> T getModule(Class<T> moduleType) {
return (T) moduleLookupTable.computeIfAbsent(moduleType.getName(), key -> {
AbstractModule<?> bestCandidate = null;
Module bestCandidate = null;
int bestCandidatePriority = Integer.MIN_VALUE;
for (AbstractModule<?> module : modules) {
for (Module module : modules) {
int priority;
if (moduleType.isAssignableFrom(module.getClass()) && ((priority = module.priority(moduleType)) > bestCandidatePriority)) {
bestCandidate = module;
@ -61,25 +66,29 @@ public class ModuleManager {
this.modules.add(module);
this.moduleLookupTable.put(module.getClass().getName(), module);
enable(module);
enable(module, true);
}
private void enable(AbstractModule<?> module) {
private void enable(Module module, boolean enableNonAbstract) {
try {
module.enableModule();
if (module instanceof AbstractModule) {
((AbstractModule<?>) module).enableModule();
} else if (enableNonAbstract) {
module.enable();
}
} catch (Throwable t) {
discordSRV.logger().error("Failed to enable " + module.getClass().getSimpleName(), t);
}
}
public void unregister(AbstractModule<?> module) {
public void unregister(Module module) {
this.modules.remove(module);
this.moduleLookupTable.values().removeIf(mod -> mod == module);
disable(module);
}
private void disable(AbstractModule<?> module) {
private void disable(Module module) {
try {
module.disable();
} catch (Throwable t) {
@ -89,16 +98,16 @@ public class ModuleManager {
@Subscribe(priority = EventPriority.EARLY)
public void onShuttingDown(DiscordSRVShuttingDownEvent event) {
for (AbstractModule<?> module : modules) {
for (Module module : modules) {
unregister(module);
}
}
@Subscribe(priority = EventPriority.EARLY)
public void onReload(DiscordSRVReloadEvent event) {
for (AbstractModule<?> module : modules) {
for (Module module : modules) {
// Check if the module needs to be enabled due to reload
enable(module);
enable(module, false);
try {
module.reload();
@ -107,4 +116,30 @@ public class ModuleManager {
}
}
}
@Subscribe
public void onDebugGenerate(DebugGenerateEvent event) {
StringBuilder builder = new StringBuilder();
builder.append("Enabled modules:");
List<Module> disabled = new ArrayList<>();
for (Module module : modules) {
if (!module.isEnabled()) {
disabled.add(module);
continue;
}
appendModule(builder, module);
}
builder.append("\n\nDisabled modules:");
for (Module module : disabled) {
appendModule(builder, module);
}
event.addFile(new TextDebugFile("modules.txt", builder));
}
private void appendModule(StringBuilder builder, Module module) {
builder.append('\n').append(module.getClass().getName());
}
}

View File

@ -21,6 +21,7 @@ package com.discordsrv.common.module.type;
import com.discordsrv.api.module.type.Module;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -33,6 +34,7 @@ public interface PermissionDataProvider extends Module {
interface All extends Basic, Meta, GroupsContext {}
interface Groups extends PermissionDataProvider {
List<String> getGroups();
CompletableFuture<Boolean> hasGroup(UUID player, String groupName, boolean includeInherited);
CompletableFuture<Void> addGroup(UUID player, String groupName);
CompletableFuture<Void> removeGroup(UUID player, String groupName);