mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-12-28 17:37:52 +01:00
Updates to debug report, debug command
This commit is contained in:
parent
bea4680739
commit
2d379dcb7c
@ -23,6 +23,10 @@ public interface GameCommandArguments {
|
||||
|
||||
<T> T get(String label, Class<T> type);
|
||||
|
||||
default String getString(String label) {
|
||||
return get(label, String.class);
|
||||
}
|
||||
|
||||
default Integer getInt(String label) {
|
||||
return get(label, Integer.class);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
|
||||
import com.discordsrv.common.command.game.command.subcommand.DebugCommand;
|
||||
import com.discordsrv.common.command.game.command.subcommand.LinkCommand;
|
||||
import com.discordsrv.common.command.game.command.subcommand.ReloadCommand;
|
||||
import com.discordsrv.common.command.game.command.subcommand.VersionCommand;
|
||||
@ -38,6 +39,7 @@ public class DiscordSRVCommand implements GameCommandExecutor {
|
||||
INSTANCE = GameCommand.literal("discordsrv")
|
||||
.requiredPermission("discordsrv.player.command")
|
||||
.executor(new DiscordSRVCommand(discordSRV))
|
||||
.then(DebugCommand.get(discordSRV))
|
||||
.then(LinkCommand.get(discordSRV))
|
||||
.then(ReloadCommand.get(discordSRV))
|
||||
.then(VersionCommand.get(discordSRV));
|
||||
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.command.game.command.subcommand;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.debug.DebugReport;
|
||||
import com.discordsrv.common.paste.Paste;
|
||||
import com.discordsrv.common.paste.PasteService;
|
||||
import com.discordsrv.common.paste.service.AESEncryptedPasteService;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
public class DebugCommand implements GameCommandExecutor {
|
||||
|
||||
private static GameCommand INSTANCE;
|
||||
|
||||
public static GameCommand get(DiscordSRV discordSRV) {
|
||||
if (INSTANCE == null) {
|
||||
DebugCommand debugCommand = new DebugCommand(discordSRV);
|
||||
INSTANCE = GameCommand.literal("debug")
|
||||
.requiredPermission("discordsrv.admin.debug")
|
||||
.executor(debugCommand)
|
||||
.then(
|
||||
GameCommand.stringWord("zip")
|
||||
.suggester((sender, previousArguments, currentInput) ->
|
||||
"zip".startsWith(currentInput.toLowerCase(Locale.ROOT))
|
||||
? Collections.singletonList("zip") : Collections.emptyList())
|
||||
.executor(debugCommand)
|
||||
);
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private static final String URL_FORMAT = "https://discordsrv.vankka.dev/debug/%s#%s";
|
||||
private static final Base64.Encoder KEY_ENCODER = Base64.getUrlEncoder().withoutPadding();
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final PasteService pasteService;
|
||||
|
||||
public DebugCommand(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.pasteService = new AESEncryptedPasteService(null /* TODO: tbd */, 128);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ICommandSender sender, GameCommandArguments arguments) {
|
||||
boolean usePaste = !"zip".equals(arguments.getString("zip"));
|
||||
|
||||
discordSRV.scheduler().run(() -> {
|
||||
DebugReport report = new DebugReport(discordSRV);
|
||||
report.generate();
|
||||
|
||||
Throwable pasteError = usePaste ? paste(sender, report) : null;
|
||||
if (usePaste && pasteError == null) {
|
||||
// Success
|
||||
return;
|
||||
}
|
||||
|
||||
Throwable zipError = zip(sender, report);
|
||||
if (zipError == null) {
|
||||
// Success
|
||||
if (usePaste) {
|
||||
discordSRV.logger().warning("Failed to upload debug, zip generation succeeded", pasteError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pasteError != null) {
|
||||
zipError.addSuppressed(pasteError);
|
||||
}
|
||||
discordSRV.logger().error(usePaste ? "Failed to upload & zip debug" : "Failed to zip debug", zipError);
|
||||
sender.sendMessage(Component.text(
|
||||
usePaste
|
||||
? "Failed to upload debug report to paste & failed to generate zip"
|
||||
: "Failed to create debug zip",
|
||||
NamedTextColor.DARK_RED
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
private Throwable paste(ICommandSender sender, DebugReport report) {
|
||||
try {
|
||||
Paste paste = report.upload(pasteService);
|
||||
String key = new String(KEY_ENCODER.encode(paste.decryptionKey()), StandardCharsets.UTF_8);
|
||||
String url = String.format(URL_FORMAT, paste.id(), key);
|
||||
|
||||
sender.sendMessage(Component.text(url).clickEvent(ClickEvent.openUrl(url)));
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
private Throwable zip(ICommandSender sender, DebugReport report) {
|
||||
try {
|
||||
Path zip = report.zip();
|
||||
Path relative = discordSRV.dataDirectory().resolve("../..").relativize(zip);
|
||||
sender.sendMessage(
|
||||
Component.text("Debug generated to zip: ", NamedTextColor.GRAY)
|
||||
.append(Component.text(relative.toString(), NamedTextColor.GREEN))
|
||||
);
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -22,20 +22,30 @@ 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.paste.Paste;
|
||||
import com.discordsrv.common.paste.PasteService;
|
||||
import com.discordsrv.common.plugin.Plugin;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileStore;
|
||||
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;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class DebugReport {
|
||||
|
||||
private static final int BIG_FILE_SPLIT_SIZE = 20000;
|
||||
|
||||
private final List<DebugFile> files = new ArrayList<>();
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
@ -53,18 +63,65 @@ public class DebugReport {
|
||||
}
|
||||
}
|
||||
|
||||
public Paste upload(PasteService service) throws Throwable {
|
||||
files.sort(Comparator.comparing(DebugFile::order).reversed());
|
||||
|
||||
ArrayNode files = discordSRV.json().createArrayNode();
|
||||
for (DebugFile file : this.files) {
|
||||
int length = file.content().length();
|
||||
if (length >= BIG_FILE_SPLIT_SIZE) {
|
||||
ObjectNode node = discordSRV.json().createObjectNode();
|
||||
node.put("name", file.name());
|
||||
try {
|
||||
Paste paste = service.uploadFile(convertToJson(file).toString().getBytes(StandardCharsets.UTF_8));
|
||||
node.put("url", paste.url());
|
||||
node.put("decryption_key", new String(Base64.getUrlEncoder().encode(paste.decryptionKey()), StandardCharsets.UTF_8));
|
||||
node.put("length", length);
|
||||
} catch (Throwable e) {
|
||||
node.put("content", "Failed to upload file\n\n" + ExceptionUtils.getStackTrace(e));
|
||||
}
|
||||
files.add(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
files.add(convertToJson(file));
|
||||
}
|
||||
|
||||
return service.uploadFile(files.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public Path zip() throws Throwable {
|
||||
Path zipPath = discordSRV.dataDirectory().resolve("debug-" + System.currentTimeMillis() + ".zip");
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipPath.toFile()))) {
|
||||
for (DebugFile file : files) {
|
||||
zipOutputStream.putNextEntry(new ZipEntry(file.name()));
|
||||
|
||||
byte[] data = file.content().getBytes(StandardCharsets.UTF_8);
|
||||
zipOutputStream.write(data, 0, data.length);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
return zipPath;
|
||||
}
|
||||
|
||||
private ObjectNode convertToJson(DebugFile file) {
|
||||
ObjectNode node = discordSRV.json().createObjectNode();
|
||||
node.put("name", file.name());
|
||||
node.put("content", file.content());
|
||||
return node;
|
||||
}
|
||||
|
||||
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("discordSRV", discordSRV.getClass().getName());
|
||||
values.put("version", discordSRV.version());
|
||||
values.put("gitRevision", discordSRV.gitRevision());
|
||||
values.put("gitBranch", discordSRV.gitBranch());
|
||||
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());
|
||||
@ -78,17 +135,50 @@ public class DebugReport {
|
||||
+ " " + System.getProperty("os.version")
|
||||
+ " (" + System.getProperty("os.arch") + ")");
|
||||
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
values.put("cores", runtime.availableProcessors());
|
||||
values.put("freeMemory", runtime.freeMemory());
|
||||
values.put("totalMemory", runtime.totalMemory());
|
||||
long maxMemory = runtime.maxMemory();
|
||||
values.put("maxMemory", maxMemory == Long.MAX_VALUE ? -1 : maxMemory);
|
||||
|
||||
try {
|
||||
FileStore store = Files.getFileStore(discordSRV.dataDirectory());
|
||||
values.put("usableSpace", store.getUsableSpace());
|
||||
values.put("totalSpace", store.getTotalSpace());
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
boolean docker = false;
|
||||
try {
|
||||
docker = Files.readAllLines(Paths.get("/proc/1/cgroup"))
|
||||
.stream().anyMatch(str -> str.contains("/docker/"));
|
||||
} catch (IOException ignored) {}
|
||||
values.put("docker", docker);
|
||||
|
||||
return new KeyValueDebugFile(10, "environment.json", values);
|
||||
}
|
||||
|
||||
private DebugFile plugins() {
|
||||
List<Plugin> plugins = discordSRV.pluginManager().getPlugins();
|
||||
List<Plugin> plugins = discordSRV.pluginManager().getPlugins()
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(plugin -> plugin.name().toLowerCase(Locale.ROOT)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int longestName = 0;
|
||||
int longestVersion = 0;
|
||||
for (Plugin plugin : plugins) {
|
||||
longestName = Math.max(longestName, plugin.name().length());
|
||||
longestVersion = Math.max(longestVersion, plugin.version().length());
|
||||
}
|
||||
|
||||
longestName++;
|
||||
longestVersion++;
|
||||
StringBuilder builder = new StringBuilder("Plugins (" + plugins.size() + "):\n");
|
||||
|
||||
for (Plugin plugin : plugins) {
|
||||
builder.append('\n')
|
||||
.append(plugin.name())
|
||||
.append(" v").append(plugin.version())
|
||||
.append(StringUtils.rightPad(plugin.name(), longestName))
|
||||
.append(" v").append(StringUtils.rightPad(plugin.version(), longestVersion))
|
||||
.append(" ").append(plugin.authors());
|
||||
}
|
||||
return new TextDebugFile(5, "plugins.txt", builder.toString());
|
||||
|
Loading…
Reference in New Issue
Block a user