mirror of
https://github.com/Anuken/Mindustry.git
synced 2024-12-22 16:24:00 +07:00
Improved crash logs; source mod of crashes now included in report
This commit is contained in:
parent
c36e638826
commit
68dccab5ca
@ -38,7 +38,7 @@ public class AndroidLauncher extends AndroidApplication{
|
|||||||
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler((thread, error) -> {
|
Thread.setDefaultUncaughtExceptionHandler((thread, error) -> {
|
||||||
CrashSender.log(error);
|
CrashHandler.log(error);
|
||||||
|
|
||||||
//try to forward exception to system handler
|
//try to forward exception to system handler
|
||||||
if(handler != null){
|
if(handler != null){
|
||||||
|
@ -1252,6 +1252,7 @@ public class Mods implements Loadable{
|
|||||||
if(name != null) name = Strings.stripColors(name);
|
if(name != null) name = Strings.stripColors(name);
|
||||||
if(displayName != null) displayName = Strings.stripColors(displayName);
|
if(displayName != null) displayName = Strings.stripColors(displayName);
|
||||||
if(displayName == null) displayName = name;
|
if(displayName == null) displayName = name;
|
||||||
|
if(version == null) version = "0";
|
||||||
if(author != null) author = Strings.stripColors(author);
|
if(author != null) author = Strings.stripColors(author);
|
||||||
if(description != null) description = Strings.stripColors(description);
|
if(description != null) description = Strings.stripColors(description);
|
||||||
if(subtitle != null) subtitle = Strings.stripColors(subtitle).replace("\n", "");
|
if(subtitle != null) subtitle = Strings.stripColors(subtitle).replace("\n", "");
|
||||||
|
@ -10,7 +10,6 @@ import rhino.*;
|
|||||||
import rhino.module.*;
|
import rhino.module.*;
|
||||||
import rhino.module.provider.*;
|
import rhino.module.provider.*;
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.*;
|
import java.util.regex.*;
|
||||||
@ -85,7 +84,7 @@ public class Scripts implements Disposable{
|
|||||||
|
|
||||||
public void run(LoadedMod mod, Fi file){
|
public void run(LoadedMod mod, Fi file){
|
||||||
currentMod = mod;
|
currentMod = mod;
|
||||||
run(file.readString(), file.name(), true);
|
run(file.readString(), mod.name + "/" + file.name(), true);
|
||||||
currentMod = null;
|
currentMod = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,15 +94,10 @@ public class Scripts implements Disposable{
|
|||||||
//inject script info into file
|
//inject script info into file
|
||||||
context.evaluateString(scope, "modName = \"" + currentMod.name + "\"\nscriptName = \"" + file + "\"", "initscript.js", 1);
|
context.evaluateString(scope, "modName = \"" + currentMod.name + "\"\nscriptName = \"" + file + "\"", "initscript.js", 1);
|
||||||
}
|
}
|
||||||
context.evaluateString(scope,
|
context.evaluateString(scope, wrap ? "(function(){'use strict';\n" + script + "\n})();" : script, file, 0);
|
||||||
wrap ? "(function(){'use strict';\n" + script + "\n})();" : script,
|
|
||||||
file, 0);
|
|
||||||
return true;
|
return true;
|
||||||
}catch(Throwable t){
|
}catch(Throwable t){
|
||||||
if(currentMod != null){
|
log(LogLevel.err, file, getError(t, true));
|
||||||
file = currentMod.name + "/" + file;
|
|
||||||
}
|
|
||||||
log(LogLevel.err, file, "" + getError(t, true));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,10 +117,10 @@ public class Scripts implements Disposable{
|
|||||||
@Override
|
@Override
|
||||||
public ModuleSource loadSource(String moduleId, Scriptable paths, Object validator) throws URISyntaxException{
|
public ModuleSource loadSource(String moduleId, Scriptable paths, Object validator) throws URISyntaxException{
|
||||||
if(currentMod == null) return null;
|
if(currentMod == null) return null;
|
||||||
return loadSource(moduleId, currentMod.root.child("scripts"), validator);
|
return loadSource(currentMod, moduleId, currentMod.root.child("scripts"), validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModuleSource loadSource(String moduleId, Fi root, Object validator) throws URISyntaxException{
|
private ModuleSource loadSource(LoadedMod sourceMod, String moduleId, Fi root, Object validator) throws URISyntaxException{
|
||||||
Matcher matched = directory.matcher(moduleId);
|
Matcher matched = directory.matcher(moduleId);
|
||||||
if(matched.find()){
|
if(matched.find()){
|
||||||
LoadedMod required = Vars.mods.locateMod(matched.group(1));
|
LoadedMod required = Vars.mods.locateMod(matched.group(1));
|
||||||
@ -134,18 +128,16 @@ public class Scripts implements Disposable{
|
|||||||
if(required == null){ // Mod not found, treat it as a folder
|
if(required == null){ // Mod not found, treat it as a folder
|
||||||
Fi dir = root.child(matched.group(1));
|
Fi dir = root.child(matched.group(1));
|
||||||
if(!dir.exists()) return null; // Mod and folder not found
|
if(!dir.exists()) return null; // Mod and folder not found
|
||||||
return loadSource(script, dir, validator);
|
return loadSource(sourceMod, script, dir, validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMod = required;
|
currentMod = required;
|
||||||
return loadSource(script, required.root.child("scripts"), validator);
|
return loadSource(sourceMod, script, required.root.child("scripts"), validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
Fi module = root.child(moduleId + ".js");
|
Fi module = root.child(moduleId + ".js");
|
||||||
if(!module.exists() || module.isDirectory()) return null;
|
if(!module.exists() || module.isDirectory()) return null;
|
||||||
return new ModuleSource(
|
return new ModuleSource(module.reader(Vars.bufferSize), new URI(sourceMod.name + "/" + moduleId + ".js"), root.file().toURI(), validator);
|
||||||
new InputStreamReader(new ByteArrayInputStream((module.readString()).getBytes())),
|
|
||||||
new URI(moduleId), root.file().toURI(), validator);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,17 @@ import java.util.*;
|
|||||||
import static arc.Core.*;
|
import static arc.Core.*;
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
public class CrashSender{
|
public class CrashHandler{
|
||||||
|
|
||||||
public static String createReport(String error){
|
public static String createReport(Throwable exception){
|
||||||
String report = "Mindustry has crashed. How unfortunate.\n";
|
String error = writeException(exception);
|
||||||
|
LoadedMod cause = getModCause(exception);
|
||||||
|
|
||||||
|
String report = cause == null ? "Mindustry has crashed. How unfortunate.\n" : "The mod '" + cause.meta.displayName + "' (" + cause.name + ")" + " has caused Mindustry to crash.\n";
|
||||||
if(mods != null && mods.list().size == 0 && Version.build != -1){
|
if(mods != null && mods.list().size == 0 && Version.build != -1){
|
||||||
report += "Report this at " + Vars.reportIssueURL + "\n\n";
|
report += "Report this at " + Vars.reportIssueURL + "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return report
|
return report
|
||||||
+ "Version: " + Version.combined() + (Vars.headless ? " (Server)" : "") + "\n"
|
+ "Version: " + Version.combined() + (Vars.headless ? " (Server)" : "") + "\n"
|
||||||
+ "OS: " + OS.osName + " x" + (OS.osArchBits) + " (" + OS.osArch + ")\n"
|
+ "OS: " + OS.osName + " x" + (OS.osArchBits) + " (" + OS.osArch + ")\n"
|
||||||
@ -31,6 +35,7 @@ public class CrashSender{
|
|||||||
+ "Java Version: " + OS.javaVersion + "\n"
|
+ "Java Version: " + OS.javaVersion + "\n"
|
||||||
+ "Runtime Available Memory: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "mb\n"
|
+ "Runtime Available Memory: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "mb\n"
|
||||||
+ "Cores: " + Runtime.getRuntime().availableProcessors() + "\n"
|
+ "Cores: " + Runtime.getRuntime().availableProcessors() + "\n"
|
||||||
|
+ (cause == null ? "" : "Likely Cause: " + cause.meta.displayName + " (" + cause.name + " v" + cause.meta.version + ")\n")
|
||||||
+ (mods == null ? "<no mod init>" : "Mods: " + (!mods.list().contains(LoadedMod::shouldBeEnabled) ? "none (vanilla)" : mods.list().select(LoadedMod::shouldBeEnabled).toString(", ", mod -> mod.name + ":" + mod.meta.version)))
|
+ (mods == null ? "<no mod init>" : "Mods: " + (!mods.list().contains(LoadedMod::shouldBeEnabled) ? "none (vanilla)" : mods.list().select(LoadedMod::shouldBeEnabled).toString(", ", mod -> mod.name + ":" + mod.meta.version)))
|
||||||
+ "\n\n" + error;
|
+ "\n\n" + error;
|
||||||
}
|
}
|
||||||
@ -38,12 +43,12 @@ public class CrashSender{
|
|||||||
public static void log(Throwable exception){
|
public static void log(Throwable exception){
|
||||||
try{
|
try{
|
||||||
Core.settings.getDataDirectory().child("crashes").child("crash_" + System.currentTimeMillis() + ".txt")
|
Core.settings.getDataDirectory().child("crashes").child("crash_" + System.currentTimeMillis() + ".txt")
|
||||||
.writeString(createReport(Strings.neatError(exception)));
|
.writeString(createReport(exception));
|
||||||
}catch(Throwable ignored){
|
}catch(Throwable ignored){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void send(Throwable exception, Cons<File> writeListener){
|
public static void handle(Throwable exception, Cons<File> writeListener){
|
||||||
try{
|
try{
|
||||||
try{
|
try{
|
||||||
//log to file
|
//log to file
|
||||||
@ -59,14 +64,14 @@ public class CrashSender{
|
|||||||
|
|
||||||
//don't create crash logs for custom builds, as it's expected
|
//don't create crash logs for custom builds, as it's expected
|
||||||
if(OS.username.equals("anuke") && !"steam".equals(Version.modifier)){
|
if(OS.username.equals("anuke") && !"steam".equals(Version.modifier)){
|
||||||
ret();
|
// System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//attempt to load version regardless
|
//attempt to load version regardless
|
||||||
if(Version.number == 0){
|
if(Version.number == 0){
|
||||||
try{
|
try{
|
||||||
ObjectMap<String, String> map = new ObjectMap<>();
|
ObjectMap<String, String> map = new ObjectMap<>();
|
||||||
PropertiesUtils.load(map, new InputStreamReader(CrashSender.class.getResourceAsStream("/version.properties")));
|
PropertiesUtils.load(map, new InputStreamReader(CrashHandler.class.getResourceAsStream("/version.properties")));
|
||||||
|
|
||||||
Version.type = map.get("type");
|
Version.type = map.get("type");
|
||||||
Version.number = Integer.parseInt(map.get("number"));
|
Version.number = Integer.parseInt(map.get("number"));
|
||||||
@ -87,7 +92,7 @@ public class CrashSender{
|
|||||||
try{
|
try{
|
||||||
File file = new File(OS.getAppDataDirectoryString(Vars.appName), "crashes/crash-report-" + new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss").format(new Date()) + ".txt");
|
File file = new File(OS.getAppDataDirectoryString(Vars.appName), "crashes/crash-report-" + new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss").format(new Date()) + ".txt");
|
||||||
new Fi(OS.getAppDataDirectoryString(Vars.appName)).child("crashes").mkdirs();
|
new Fi(OS.getAppDataDirectoryString(Vars.appName)).child("crashes").mkdirs();
|
||||||
new Fi(file).writeString(createReport(writeException(exception)));
|
new Fi(file).writeString(createReport(exception));
|
||||||
writeListener.get(file);
|
writeListener.get(file);
|
||||||
}catch(Throwable e){
|
}catch(Throwable e){
|
||||||
Log.err("Failed to save local crash report.", e);
|
Log.err("Failed to save local crash report.", e);
|
||||||
@ -103,11 +108,41 @@ public class CrashSender{
|
|||||||
death.printStackTrace();
|
death.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
ret();
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ret(){
|
/** @return the mod that is likely to have caused the supplied crash */
|
||||||
System.exit(1);
|
public static @Nullable LoadedMod getModCause(Throwable e){
|
||||||
|
if(Vars.mods == null) return null;
|
||||||
|
try{
|
||||||
|
for(var element : e.getStackTrace()){
|
||||||
|
String name = element.getClassName();
|
||||||
|
if(!name.matches("(mindustry|arc|java|javax|sun|jdk)\\..*")){
|
||||||
|
for(var mod : mods.list()){
|
||||||
|
if(mod.meta.main != null && getMatches(mod.meta.main, name) > 0){
|
||||||
|
return mod;
|
||||||
|
}else if(element.getFileName() != null && element.getFileName().endsWith(".js") && element.getFileName().startsWith(mod.name + "/")){
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(Throwable ignored){}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getMatches(String name1, String name2){
|
||||||
|
String[] arr1 = name1.split("\\."), arr2 = name2.split("\\.");
|
||||||
|
int matches = 0;
|
||||||
|
for(int i = 0; i < Math.min(arr1.length, arr2.length); i++){
|
||||||
|
|
||||||
|
if(!arr1[i].equals(arr2[i])){
|
||||||
|
return i;
|
||||||
|
}else if(!arr1[i].matches("net|org|com|io")){ //ignore common domain prefixes, as that's usually not enough to call something a "match"
|
||||||
|
matches ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String writeException(Throwable e){
|
private static String writeException(Throwable e){
|
@ -7,7 +7,6 @@ import arc.backend.sdl.jni.*;
|
|||||||
import arc.discord.*;
|
import arc.discord.*;
|
||||||
import arc.discord.DiscordRPC.*;
|
import arc.discord.DiscordRPC.*;
|
||||||
import arc.files.*;
|
import arc.files.*;
|
||||||
import arc.func.*;
|
|
||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
@ -19,6 +18,7 @@ import mindustry.core.*;
|
|||||||
import mindustry.desktop.steam.*;
|
import mindustry.desktop.steam.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
|
import mindustry.mod.Mods.*;
|
||||||
import mindustry.net.*;
|
import mindustry.net.*;
|
||||||
import mindustry.net.Net.*;
|
import mindustry.net.Net.*;
|
||||||
import mindustry.service.*;
|
import mindustry.service.*;
|
||||||
@ -223,27 +223,30 @@ public class DesktopLauncher extends ClientLauncher{
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void handleCrash(Throwable e){
|
static void handleCrash(Throwable e){
|
||||||
Cons<Runnable> dialog = Runnable::run;
|
|
||||||
boolean badGPU = false;
|
boolean badGPU = false;
|
||||||
String finalMessage = Strings.getFinalMessage(e);
|
String finalMessage = Strings.getFinalMessage(e);
|
||||||
String total = Strings.getCauses(e).toString();
|
String total = Strings.getCauses(e).toString();
|
||||||
|
|
||||||
if(total.contains("Couldn't create window") || total.contains("OpenGL 2.0 or higher") || total.toLowerCase().contains("pixel format") || total.contains("GLEW")|| total.contains("unsupported combination of formats")){
|
if(total.contains("Couldn't create window") || total.contains("OpenGL 2.0 or higher") || total.toLowerCase().contains("pixel format") || total.contains("GLEW")|| total.contains("unsupported combination of formats")){
|
||||||
|
|
||||||
dialog.get(() -> message(
|
message(
|
||||||
total.contains("Couldn't create window") ? "A graphics initialization error has occured! Try to update your graphics drivers:\n" + finalMessage :
|
total.contains("Couldn't create window") ? "A graphics initialization error has occured! Try to update your graphics drivers:\n" + finalMessage :
|
||||||
"Your graphics card does not support the right OpenGL features.\n" +
|
"Your graphics card does not support the right OpenGL features.\n" +
|
||||||
"Try to update your graphics drivers. If this doesn't work, your computer may not support Mindustry.\n\n" +
|
"Try to update your graphics drivers. If this doesn't work, your computer may not support Mindustry.\n\n" +
|
||||||
"Full message: " + finalMessage));
|
"Full message: " + finalMessage);
|
||||||
badGPU = true;
|
badGPU = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean fbgp = badGPU;
|
boolean fbgp = badGPU;
|
||||||
|
|
||||||
CrashSender.send(e, file -> {
|
LoadedMod cause = CrashHandler.getModCause(e);
|
||||||
|
String causeString = cause == null ? (Structs.contains(e.getStackTrace(), st -> st.getClassName().contains("rhino.gen.")) ? "A mod or script has caused Mindustry to crash.\nConsider disabling your mods if the issue persists.\n" : "Mindustry has crashed.") :
|
||||||
|
"'" + cause.meta.displayName + "' (" + cause.name + ") has caused Mindustry to crash.\nConsider disabling this mod if issues persist.\n";
|
||||||
|
|
||||||
|
CrashHandler.handle(e, file -> {
|
||||||
Throwable fc = Strings.getFinalCause(e);
|
Throwable fc = Strings.getFinalCause(e);
|
||||||
if(!fbgp){
|
if(!fbgp){
|
||||||
dialog.get(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + fc.getClass().getSimpleName().replace("Exception", "") + (fc.getMessage() == null ? "" : ":\n" + fc.getMessage())));
|
message(causeString + "\nThe logs have been saved in:\n" + file.getAbsolutePath() + "\n" + fc.getClass().getSimpleName().replace("Exception", "") + (fc.getMessage() == null ? "" : ":\n" + fc.getMessage()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -250,7 +250,7 @@ public class IOSLauncher extends IOSApplication.Delegate{
|
|||||||
UIApplication.main(argv, null, IOSLauncher.class);
|
UIApplication.main(argv, null, IOSLauncher.class);
|
||||||
}catch(Throwable t){
|
}catch(Throwable t){
|
||||||
//attempt to log the exception
|
//attempt to log the exception
|
||||||
CrashSender.log(t);
|
CrashHandler.log(t);
|
||||||
Log.err(t);
|
Log.err(t);
|
||||||
//rethrow the exception so it actually crashes
|
//rethrow the exception so it actually crashes
|
||||||
throw t;
|
throw t;
|
||||||
|
@ -32,9 +32,9 @@ public class ServerLauncher implements ApplicationListener{
|
|||||||
String result = "[" + dateTime.format(LocalDateTime.now()) + "] " + format(tags[level1.ordinal()] + " " + text + "&fr");
|
String result = "[" + dateTime.format(LocalDateTime.now()) + "] " + format(tags[level1.ordinal()] + " " + text + "&fr");
|
||||||
System.out.println(result);
|
System.out.println(result);
|
||||||
};
|
};
|
||||||
new HeadlessApplication(new ServerLauncher(), throwable -> CrashSender.send(throwable, f -> {}));
|
new HeadlessApplication(new ServerLauncher(), throwable -> CrashHandler.handle(throwable, f -> {}));
|
||||||
}catch(Throwable t){
|
}catch(Throwable t){
|
||||||
CrashSender.send(t, f -> {});
|
CrashHandler.handle(t, f -> {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,15 +46,15 @@ public class ServerLauncher implements ApplicationListener{
|
|||||||
|
|
||||||
Vars.loadSettings();
|
Vars.loadSettings();
|
||||||
Vars.init();
|
Vars.init();
|
||||||
|
|
||||||
UI.loadColors();
|
UI.loadColors();
|
||||||
Fonts.loadContentIconsHeadless();
|
Fonts.loadContentIconsHeadless();
|
||||||
|
|
||||||
content.createBaseContent();
|
content.createBaseContent();
|
||||||
mods.loadScripts();
|
mods.loadScripts();
|
||||||
content.createModContent();
|
content.createModContent();
|
||||||
content.init();
|
content.init();
|
||||||
|
|
||||||
if(mods.hasContentErrors()){
|
if(mods.hasContentErrors()){
|
||||||
err("Error occurred loading mod content:");
|
err("Error occurred loading mod content:");
|
||||||
for(LoadedMod mod : mods.list()){
|
for(LoadedMod mod : mods.list()){
|
||||||
|
Loading…
Reference in New Issue
Block a user