From 9a413991780833896edd67d0bf7597d6e61f383a Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 29 Aug 2018 22:09:55 -0400 Subject: [PATCH 1/4] Initial proof-of-concept for crash report server --- build.gradle | 4 ++ core/src/io/anuke/mindustry/Vars.java | 1 + core/src/io/anuke/mindustry/core/Control.java | 1 + core/src/io/anuke/mindustry/net/Net.java | 6 ++- .../anuke/mindustry/desktop/CrashHandler.java | 6 ++- reporter/build.gradle | 26 ++++++++++++ .../io/anuke/mindustry/reporter/Launcher.java | 41 +++++++++++++++++++ .../mindustry/reporter/ReportHandler.java | 10 +++++ run-reporter | 2 + settings.gradle | 2 +- 10 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 reporter/build.gradle create mode 100644 reporter/src/io/anuke/mindustry/reporter/Launcher.java create mode 100644 reporter/src/io/anuke/mindustry/reporter/ReportHandler.java create mode 100755 run-reporter diff --git a/build.gradle b/build.gradle index e838020014..14ff10a9dd 100644 --- a/build.gradle +++ b/build.gradle @@ -232,6 +232,10 @@ project(":kryonet") { } } +project(":reporter"){ + apply plugin: "java" +} + tasks.eclipse.doLast { delete ".project" } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 255fb51585..7527a06987 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -40,6 +40,7 @@ public class Vars{ //discord group URL public static final String discordURL = "https://discord.gg/BKADYds"; public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; + public static final String crashReportURL = "http://localhost:8080/report"; public static final int maxTextLength = 150; public static final int maxNameLength = 40; public static final float itemSize = 5f; diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index fac84a7ef6..9b4b53766c 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -51,6 +51,7 @@ public class Control extends Module{ private Throwable error; public Control(){ + if(true) throw new RuntimeException("This should crash."); saves = new Saves(); db = new ContentDatabase(); diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index a2201c2365..720e57eaf5 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -309,8 +309,12 @@ public class Net{ } public static void http(String url, String method, Consumer listener, Consumer failure){ + http(url, method, null, listener, failure); + } + + public static void http(String url, String method, String body, Consumer listener, Consumer failure){ HttpRequest req = new HttpRequestBuilder().newRequest() - .method(method).url(url).build(); + .method(method).url(url).content(body).build(); Gdx.net.sendHttpRequest(req, new HttpResponseListener(){ @Override diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index cfa1fba347..4009796426 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.desktop; +import io.anuke.mindustry.Vars; import io.anuke.mindustry.game.Version; import io.anuke.mindustry.net.Net; import io.anuke.ucore.core.Settings; @@ -13,7 +14,6 @@ import java.util.Date; public class CrashHandler{ public static void handle(Throwable e){ - //TODO send full error report to server via HTTP e.printStackTrace(); boolean netActive = false, netServer = false; @@ -28,7 +28,7 @@ public class CrashHandler{ } //don't create crash logs for me (anuke), as it's expected - if(System.getProperty("user.name").equals("anuke")) return; + //if(System.getProperty("user.name").equals("anuke")) return; String header = "--CRASH REPORT--\n"; @@ -50,6 +50,8 @@ public class CrashHandler{ String filename = ""; + Net.http(Vars.crashReportURL, "POST", result, r -> {}, Throwable::printStackTrace); + //try to write it try{ filename = "crash-report-" + new SimpleDateFormat("dd-MM-yy h.mm.ss").format(new Date()) + ".txt"; diff --git a/reporter/build.gradle b/reporter/build.gradle new file mode 100644 index 0000000000..ed7a1bc999 --- /dev/null +++ b/reporter/build.gradle @@ -0,0 +1,26 @@ +apply plugin: "java" + +sourceCompatibility = 1.8 +sourceSets.main.java.srcDirs = [ "src/" ] + +project.ext.mainClassName = "io.anuke.mindustry.reporter.Launcher" + +task run(dependsOn: classes, type: JavaExec) { + main = project.mainClassName + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + ignoreExitValue = true +} + +task dist(type: Jar) { + dependsOn classes + from files(sourceSets.main.output.classesDirs) + from files(sourceSets.main.output.resourcesDir) + from {configurations.compile.collect {zipTree(it)}} + + writeVersion() + + manifest { + attributes 'Main-Class': project.mainClassName + } +} diff --git a/reporter/src/io/anuke/mindustry/reporter/Launcher.java b/reporter/src/io/anuke/mindustry/reporter/Launcher.java new file mode 100644 index 0000000000..058f4bffce --- /dev/null +++ b/reporter/src/io/anuke/mindustry/reporter/Launcher.java @@ -0,0 +1,41 @@ +package io.anuke.mindustry.reporter; + +import com.sun.net.httpserver.HttpServer; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; + +import static java.lang.System.currentTimeMillis; +import static java.lang.System.out; + +public class Launcher{ + private static final long REQUEST_TIME = 1000 * 6; + + public static void main(String[] args) throws IOException{ + ReportHandler handler = new ReportHandler(); + HashMap rateLimit = new HashMap<>(); + + HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); + server.createContext("/report", t -> { + String key = t.getRemoteAddress().getAddress().getHostName(); + if(rateLimit.get(key) != null && (currentTimeMillis() - rateLimit.get(key)) < REQUEST_TIME){ + rateLimit.put(key, currentTimeMillis()); + out.println("connection " + key + " is being rate limited"); + return; + } + + rateLimit.put(key, currentTimeMillis()); + byte[] bytes = new byte[t.getRequestBody().available()]; + new DataInputStream(t.getRequestBody()).readFully(bytes); + handler.handle(new String(bytes)); + + t.sendResponseHeaders(200, 0); + }); + server.setExecutor(null); + server.start(); + out.println("server up"); + } + +} diff --git a/reporter/src/io/anuke/mindustry/reporter/ReportHandler.java b/reporter/src/io/anuke/mindustry/reporter/ReportHandler.java new file mode 100644 index 0000000000..164073514d --- /dev/null +++ b/reporter/src/io/anuke/mindustry/reporter/ReportHandler.java @@ -0,0 +1,10 @@ +package io.anuke.mindustry.reporter; + +import static java.lang.System.out; + +public class ReportHandler{ + + public void handle(String text){ + out.println("recieved text: " + text); + } +} diff --git a/run-reporter b/run-reporter new file mode 100755 index 0000000000..e95e9eaf7e --- /dev/null +++ b/run-reporter @@ -0,0 +1,2 @@ +./gradlew reporter:dist +java -jar reporter/build/libs/reporter-release.jar diff --git a/settings.gradle b/settings.gradle index 802a36e0dd..f0ebb1b115 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,4 @@ -include 'desktop', 'html', 'core', 'android', 'kryonet', 'server', 'ios', 'annotations', 'packer' +include 'desktop', 'html', 'core', 'android', 'kryonet', 'server', 'ios', 'annotations', 'packer', 'reporter' if(System.properties["release"] == null || System.properties["release"].equals("false")){ if (new File(settingsDir, '../uCore').exists()) { From 28b9de2e7a4b8ba6be8ee41ebd9940c76012cd22 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 29 Aug 2018 22:54:15 -0400 Subject: [PATCH 2/4] Moved reporting system to CoreBot; JSON-base reports --- core/src/io/anuke/mindustry/Vars.java | 2 +- core/src/io/anuke/mindustry/core/Control.java | 1 + .../anuke/mindustry/desktop/CrashHandler.java | 82 +++++++++---------- reporter/build.gradle | 26 ------ .../io/anuke/mindustry/reporter/Launcher.java | 41 ---------- .../mindustry/reporter/ReportHandler.java | 10 --- run-reporter | 2 - 7 files changed, 41 insertions(+), 123 deletions(-) delete mode 100644 reporter/build.gradle delete mode 100644 reporter/src/io/anuke/mindustry/reporter/Launcher.java delete mode 100644 reporter/src/io/anuke/mindustry/reporter/ReportHandler.java delete mode 100755 run-reporter diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 7527a06987..2a02485b00 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -40,7 +40,7 @@ public class Vars{ //discord group URL public static final String discordURL = "https://discord.gg/BKADYds"; public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; - public static final String crashReportURL = "http://localhost:8080/report"; + public static final String crashReportURL = "http:/mindustry.us.to/report"; public static final int maxTextLength = 150; public static final int maxNameLength = 40; public static final float itemSize = 5f; diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 9b4b53766c..5e42aee08c 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -51,6 +51,7 @@ public class Control extends Module{ private Throwable error; public Control(){ + //TODO remove; only for testing if(true) throw new RuntimeException("This should crash."); saves = new Saves(); diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index 4009796426..735d727b01 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -1,21 +1,26 @@ package io.anuke.mindustry.desktop; +import com.badlogic.gdx.utils.JsonValue; +import com.badlogic.gdx.utils.JsonValue.ValueType; +import com.badlogic.gdx.utils.JsonWriter.OutputType; import io.anuke.mindustry.Vars; import io.anuke.mindustry.game.Version; import io.anuke.mindustry.net.Net; import io.anuke.ucore.core.Settings; -import io.anuke.ucore.util.Strings; +import io.anuke.ucore.util.Log; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.io.PrintWriter; +import java.io.StringWriter; public class CrashHandler{ public static void handle(Throwable e){ e.printStackTrace(); + //don't create crash logs for me (anuke), as it's expected + //also don't create logs for custom builds + if(System.getProperty("user.name").equals("anuke") || Version.build == -1) return; + boolean netActive = false, netServer = false; //attempt to close connections, if applicable @@ -27,47 +32,38 @@ public class CrashHandler{ p.printStackTrace(); } - //don't create crash logs for me (anuke), as it's expected - //if(System.getProperty("user.name").equals("anuke")) return; + JsonValue value = new JsonValue(ValueType.object); - String header = "--CRASH REPORT--\n"; + boolean fn = netActive, fs = netServer; + //add all relevant info, ignoring exceptions + ex(() -> value.addChild("build", new JsonValue(Version.build))); + ex(() -> value.addChild("net", new JsonValue(fn))); + ex(() -> value.addChild("server", new JsonValue(fs))); + ex(() -> value.addChild("os", new JsonValue(System.getProperty("os.name")))); + ex(() -> value.addChild("multithreading", new JsonValue(Settings.getBool("multithread")))); + ex(() -> value.addChild("trace", new JsonValue(parseException(e)))); + + Log.info("Sending crash report."); + //post to crash report URL + Net.http(Vars.crashReportURL, "POST", value.toJson(OutputType.json), r -> System.exit(1), t -> System.exit(1)); + + //sleep forever + try{ Thread.sleep(Long.MAX_VALUE); }catch(InterruptedException ignored){} + } + + private static String parseException(Throwable e){ + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + + private static void ex(Runnable r){ try{ - header += "--GAME INFO--\n"; - header += "Build: " + Version.build + "\n"; - header += "Net Active: " + netActive + "\n"; - header += "Net Server: " + netServer + "\n"; - header += "OS: " + System.getProperty("os.name") + "\n"; - header += "Multithreading: " + Settings.getBool("multithread") + "\n----\n"; - }catch(Throwable e4){ - header += "--error getting additional info--\n"; - e4.printStackTrace(); - } - - //parse exception - String result = header + Strings.parseFullException(e); - boolean failed = false; - - String filename = ""; - - Net.http(Vars.crashReportURL, "POST", result, r -> {}, Throwable::printStackTrace); - - //try to write it - try{ - filename = "crash-report-" + new SimpleDateFormat("dd-MM-yy h.mm.ss").format(new Date()) + ".txt"; - Files.write(Paths.get(System.getProperty("user.home"), filename), result.getBytes()); - }catch(Throwable i){ - i.printStackTrace(); - failed = true; - } - - try{ - javax.swing.JOptionPane.showMessageDialog(null, "An error has occured: \n" + result + "\n\n" + - (!failed ? "A crash report has been written to " + Paths.get(System.getProperty("user.home"), filename).toFile().getAbsolutePath() + ".\nPlease send this file to the developer!" - : "Failed to generate crash report.\nPlease send an image of this crash log to the developer!")); - }catch(Throwable i){ - i.printStackTrace(); - //what now? + r.run(); + }catch(Throwable t){ + t.printStackTrace(); } } } diff --git a/reporter/build.gradle b/reporter/build.gradle deleted file mode 100644 index ed7a1bc999..0000000000 --- a/reporter/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -apply plugin: "java" - -sourceCompatibility = 1.8 -sourceSets.main.java.srcDirs = [ "src/" ] - -project.ext.mainClassName = "io.anuke.mindustry.reporter.Launcher" - -task run(dependsOn: classes, type: JavaExec) { - main = project.mainClassName - classpath = sourceSets.main.runtimeClasspath - standardInput = System.in - ignoreExitValue = true -} - -task dist(type: Jar) { - dependsOn classes - from files(sourceSets.main.output.classesDirs) - from files(sourceSets.main.output.resourcesDir) - from {configurations.compile.collect {zipTree(it)}} - - writeVersion() - - manifest { - attributes 'Main-Class': project.mainClassName - } -} diff --git a/reporter/src/io/anuke/mindustry/reporter/Launcher.java b/reporter/src/io/anuke/mindustry/reporter/Launcher.java deleted file mode 100644 index 058f4bffce..0000000000 --- a/reporter/src/io/anuke/mindustry/reporter/Launcher.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.anuke.mindustry.reporter; - -import com.sun.net.httpserver.HttpServer; - -import java.io.DataInputStream; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.HashMap; - -import static java.lang.System.currentTimeMillis; -import static java.lang.System.out; - -public class Launcher{ - private static final long REQUEST_TIME = 1000 * 6; - - public static void main(String[] args) throws IOException{ - ReportHandler handler = new ReportHandler(); - HashMap rateLimit = new HashMap<>(); - - HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); - server.createContext("/report", t -> { - String key = t.getRemoteAddress().getAddress().getHostName(); - if(rateLimit.get(key) != null && (currentTimeMillis() - rateLimit.get(key)) < REQUEST_TIME){ - rateLimit.put(key, currentTimeMillis()); - out.println("connection " + key + " is being rate limited"); - return; - } - - rateLimit.put(key, currentTimeMillis()); - byte[] bytes = new byte[t.getRequestBody().available()]; - new DataInputStream(t.getRequestBody()).readFully(bytes); - handler.handle(new String(bytes)); - - t.sendResponseHeaders(200, 0); - }); - server.setExecutor(null); - server.start(); - out.println("server up"); - } - -} diff --git a/reporter/src/io/anuke/mindustry/reporter/ReportHandler.java b/reporter/src/io/anuke/mindustry/reporter/ReportHandler.java deleted file mode 100644 index 164073514d..0000000000 --- a/reporter/src/io/anuke/mindustry/reporter/ReportHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.anuke.mindustry.reporter; - -import static java.lang.System.out; - -public class ReportHandler{ - - public void handle(String text){ - out.println("recieved text: " + text); - } -} diff --git a/run-reporter b/run-reporter deleted file mode 100755 index e95e9eaf7e..0000000000 --- a/run-reporter +++ /dev/null @@ -1,2 +0,0 @@ -./gradlew reporter:dist -java -jar reporter/build/libs/reporter-release.jar From 3e18fb3ca0bd2944c333afc149bb921fffca8d49 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 29 Aug 2018 23:46:04 -0400 Subject: [PATCH 3/4] Final crash report system for desktop --- core/src/io/anuke/mindustry/Vars.java | 2 +- core/src/io/anuke/mindustry/core/Control.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 2a02485b00..2369de3f50 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -40,7 +40,7 @@ public class Vars{ //discord group URL public static final String discordURL = "https://discord.gg/BKADYds"; public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; - public static final String crashReportURL = "http:/mindustry.us.to/report"; + public static final String crashReportURL = "http://mindustry.us.to/report"; public static final int maxTextLength = 150; public static final int maxNameLength = 40; public static final float itemSize = 5f; diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 5e42aee08c..fac84a7ef6 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -51,8 +51,6 @@ public class Control extends Module{ private Throwable error; public Control(){ - //TODO remove; only for testing - if(true) throw new RuntimeException("This should crash."); saves = new Saves(); db = new ContentDatabase(); From febc1e53b61cfaeb9845020ada6de1c452bb094f Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 30 Aug 2018 09:05:15 -0400 Subject: [PATCH 4/4] Added dedicated server crash reports --- .../io/anuke/mindustry/core/GameState.java | 1 - .../anuke/mindustry/net/Administration.java | 49 ------------- .../anuke/mindustry/desktop/CrashHandler.java | 1 + .../anuke/mindustry/server/CrashHandler.java | 70 +++++++++++++++++++ .../anuke/mindustry/server/ServerControl.java | 10 ++- .../mindustry/server/ServerLauncher.java | 16 +++-- 6 files changed, 90 insertions(+), 57 deletions(-) create mode 100644 server/src/io/anuke/mindustry/server/CrashHandler.java diff --git a/core/src/io/anuke/mindustry/core/GameState.java b/core/src/io/anuke/mindustry/core/GameState.java index 76a0ae95b1..a38a46e01f 100644 --- a/core/src/io/anuke/mindustry/core/GameState.java +++ b/core/src/io/anuke/mindustry/core/GameState.java @@ -14,7 +14,6 @@ public class GameState{ public boolean gameOver = false; public GameMode mode = GameMode.waves; public Difficulty difficulty = Difficulty.normal; - public boolean friendlyFire; public WaveSpawner spawner = new WaveSpawner(); public Teams teams = new Teams(); private State state = State.menu; diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index 87ca337415..51129ac9f7 100644 --- a/core/src/io/anuke/mindustry/net/Administration.java +++ b/core/src/io/anuke/mindustry/net/Administration.java @@ -63,55 +63,6 @@ public class Administration{ return editLogs; } - /* - public void rollbackWorld(int rollbackTimes) { - for(IntMap.Entry> editLog : editLogs.entries()) { - int coords = editLog.key; - Array logs = editLog.value; - - for(int i = 0; i < rollbackTimes; i++) { - - EditLog log = logs.get(logs.size - 1); - - int x = coords % world.width(); - int y = coords / world.width(); - Block result = log.block; - int rotation = log.rotation; - - //TODO fix this mess, broken with 4.0 - - if(log.action == EditLog.EditAction.PLACE) { - // Build.breakBlock(x, y, false, false); - - Packets.BreakPacket packet = new Packets.BreakPacket(); - packet.x = (short) x; - packet.y = (short) y; - packet.playerid = 0; - - Net.send(packet, Net.SendMode.tcp); - } - else if(log.action == EditLog.EditAction.BREAK) { - //Build.placeBlock(x, y, result, rotation, false, false); - - Packets.PlacePacket packet = new Packets.PlacePacket(); - packet.x = (short) x; - packet.y = (short) y; - packet.rotation = (byte) rotation; - packet.playerid = 0; - //packet.block = result.id; - - Net.send(packet, Net.SendMode.tcp); - } - - logs.removeIndex(logs.size - 1); - if(logs.size == 0) { - editLogs.remove(coords); - break; - } - } - } - }*/ - public boolean validateBreak(String id, String ip){ if(!isAntiGrief() || isAdmin(id, ip)) return true; diff --git a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java index 735d727b01..b0bfcb5f19 100644 --- a/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java +++ b/desktop/src/io/anuke/mindustry/desktop/CrashHandler.java @@ -40,6 +40,7 @@ public class CrashHandler{ ex(() -> value.addChild("build", new JsonValue(Version.build))); ex(() -> value.addChild("net", new JsonValue(fn))); ex(() -> value.addChild("server", new JsonValue(fs))); + ex(() -> value.addChild("gamemode", new JsonValue(Vars.state.mode.toString()))); ex(() -> value.addChild("os", new JsonValue(System.getProperty("os.name")))); ex(() -> value.addChild("multithreading", new JsonValue(Settings.getBool("multithread")))); ex(() -> value.addChild("trace", new JsonValue(parseException(e)))); diff --git a/server/src/io/anuke/mindustry/server/CrashHandler.java b/server/src/io/anuke/mindustry/server/CrashHandler.java new file mode 100644 index 0000000000..706e02343b --- /dev/null +++ b/server/src/io/anuke/mindustry/server/CrashHandler.java @@ -0,0 +1,70 @@ +package io.anuke.mindustry.server; + +import com.badlogic.gdx.utils.JsonValue; +import com.badlogic.gdx.utils.JsonValue.ValueType; +import com.badlogic.gdx.utils.JsonWriter.OutputType; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.game.Version; +import io.anuke.mindustry.net.Net; +import io.anuke.ucore.core.Settings; +import io.anuke.ucore.util.Log; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class CrashHandler{ + + public static void handle(Throwable e){ + e.printStackTrace(); + + //don't create crash logs for me (anuke), as it's expected + //also don't create logs for custom builds + if(System.getProperty("user.name").equals("anuke") || Version.build == -1) return; + + //if getting the crash report property failed, OR if it set to false... don't send it + try{ + if(!Settings.getBool("crashreport")) return; + }catch(Throwable ignored){ + return; + } + + //attempt to close connections, if applicable + try{ + Net.dispose(); + }catch(Throwable p){ + p.printStackTrace(); + } + + JsonValue value = new JsonValue(ValueType.object); + + //add all relevant info, ignoring exceptions + ex(() -> value.addChild("build", new JsonValue(Version.build))); + ex(() -> value.addChild("mode", new JsonValue(Vars.state.mode.toString()))); + ex(() -> value.addChild("difficulty", new JsonValue(Vars.state.difficulty.toString()))); + ex(() -> value.addChild("players", new JsonValue(Vars.playerGroup.size()))); + ex(() -> value.addChild("os", new JsonValue(System.getProperty("os.name")))); + ex(() -> value.addChild("trace", new JsonValue(parseException(e)))); + + Log.info("&lcSending crash report."); + //post to crash report URL + Net.http(Vars.crashReportURL, "POST", value.toJson(OutputType.json), r -> System.exit(1), t -> System.exit(1)); + + //sleep forever + try{ Thread.sleep(Long.MAX_VALUE); }catch(InterruptedException ignored){} + } + + private static String parseException(Throwable e){ + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + + private static void ex(Runnable r){ + try{ + r.run(); + }catch(Throwable t){ + t.printStackTrace(); + } + } +} diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index a579ca4710..a8d0a2ab7f 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -54,6 +54,7 @@ public class ServerControl extends Module{ "admins", "", "sector_x", 0, "sector_y", 1, + "crashreport", false, "port", port ); @@ -338,7 +339,14 @@ public class ServerControl extends Module{ } }); - handler.register("debug", "", "Disables or enables debug ode", arg -> { + handler.register("crashreport", "", "Disables or enables automatic crash reporting", arg -> { + boolean value = arg[0].equalsIgnoreCase("on"); + Settings.putBool("crashreport", value); + Settings.save(); + info("Crash reporting is now {0}.", value ? "on" : "off"); + }); + + handler.register("debug", "", "Disables or enables debug mode", arg -> { boolean value = arg[0].equalsIgnoreCase("on"); debug = value; info("Debug mode is now {0}.", value ? "on" : "off"); diff --git a/server/src/io/anuke/mindustry/server/ServerLauncher.java b/server/src/io/anuke/mindustry/server/ServerLauncher.java index dbc25ea35b..3ed3280250 100644 --- a/server/src/io/anuke/mindustry/server/ServerLauncher.java +++ b/server/src/io/anuke/mindustry/server/ServerLauncher.java @@ -52,20 +52,24 @@ public class ServerLauncher extends HeadlessApplication{ } public static void main(String[] args){ + try{ - Net.setClientProvider(new KryoClient()); - Net.setServerProvider(new KryoServer()); + Net.setClientProvider(new KryoClient()); + Net.setServerProvider(new KryoServer()); - HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration(); - config.preferencesDirectory = OS.getAppDataDirectoryString("Mindustry"); + HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration(); + config.preferencesDirectory = OS.getAppDataDirectoryString("Mindustry"); - new ServerLauncher(new MindustryServer(args), config); + new ServerLauncher(new MindustryServer(args), config); + }catch(Throwable t){ + CrashHandler.handle(t); + } //find and handle uncaught exceptions in libGDX thread for(Thread thread : Thread.getAllStackTraces().keySet()){ if(thread.getName().equals("HeadlessApplication")){ thread.setUncaughtExceptionHandler((t, throwable) -> { - throwable.printStackTrace(); + CrashHandler.handle(throwable); System.exit(-1); }); break;