Code Review

- Renamed CrashReportGenerator.makeCrashReport to .crash for convenience
- Split .crash into subroutines
- Documented Analyzer and ContextProvider
  - Both are now supposed to have names In Title Case
- CRG.export now assumes PrintStream does not fail and assumes Log can
fail
This commit is contained in:
OLEGSHA 2020-11-05 12:14:03 +03:00
parent ddf48c0587
commit 044c690d09
6 changed files with 130 additions and 48 deletions

View File

@ -38,7 +38,7 @@ public class ProgressiaClientMain {
long[] ssdss = new long[1 << 30]; long[] ssdss = new long[1 << 30];
} catch (Throwable t) } catch (Throwable t)
{ {
CrashReportGenerator.makeCrashReport(t, "u %s stupid", "vry"); CrashReportGenerator.crash(t, "u %s stupid", "vry");
} }
ProgressiaLauncher.launch(args, new ClientProxy()); ProgressiaLauncher.launch(args, new ClientProxy());

View File

@ -1,7 +1,32 @@
package ru.windcorp.progressia.common.util.crash; package ru.windcorp.progressia.common.util.crash;
/**
* A crash report utility that performs analysis of a problem during crash
* report generation and presents its conclusion to the user via the crash report.
* Unlike {@link ContextProvider}s, Analyzers are provided with the reported problem
* details.
* @see ContextProvider
* @author serega404
*/
public interface Analyzer { public interface Analyzer {
/**
* Provides a human-readable string describing this analyzer's conclusion
* on the presented problem, or returns {@code null} if no conclusion
* could be made.
* @param throwable The reported throwable (may be {@code null})
* @param messageFormat A {@linkplain java.util.Formatter#syntax format string} of a
* human-readable description of the problem
* @param args The arguments for the format string
* @return a conclusion or {@code null}
*/
String analyze(Throwable throwable, String messageFormat, Object... args); String analyze(Throwable throwable, String messageFormat, Object... args);
/**
* Returns this analyzer's human-readable name.
* It should be A String In Title Case With Spaces.
* @return this analyzer's name
*/
String getName(); String getName();
} }

View File

@ -2,8 +2,30 @@ package ru.windcorp.progressia.common.util.crash;
import java.util.Map; import java.util.Map;
/**
* A crash report utility that gathers information about game and system state
* when a crash occurs and presents it to the user via the crash report.
* ContextProviders are not aware of the nature of the problem, unlike {@link Analyzer}s.
* @see Analyzer
* @author serega404
*/
public interface ContextProvider { public interface ContextProvider {
/**
* Provides human-readable description of the state of the game and the system.
* This information is {@link Map#put(Object, Object) put} into the provided map
* as key-value pairs. Keys are the characteristic being described, such as "OS Name",
* and should be Strings In Title Case With Spaces.
* If this provider cannot provide any information at this moment, the map is not
* modified.
* @param output the map to append output to
*/
void provideContext(Map<String, String> output); void provideContext(Map<String, String> output);
/**
* Returns this provider's human-readable name.
* It should be A String In Title Case With Spaces.
* @return this provider's name
*/
String getName(); String getName();
} }

View File

@ -2,11 +2,12 @@ package ru.windcorp.progressia.common.util.crash;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.util.StringBuilderWriter;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.Writer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -17,21 +18,43 @@ import java.util.*;
public class CrashReportGenerator { public class CrashReportGenerator {
private CrashReportGenerator() { private CrashReportGenerator() {}
}
private static final Path CRASH_REPORTS_PATH = Paths.get("crash-reports"); private static final Path CRASH_REPORTS_PATH = Paths.get("crash-reports");
private static final Collection<ContextProvider> PROVIDERS = new ArrayList<>(); private static final Collection<ContextProvider> PROVIDERS =
Collections.synchronizedCollection(new ArrayList<>());
private static final Collection<Analyzer> ANALYZERS = new ArrayList<>(); private static final Collection<Analyzer> ANALYZERS =
Collections.synchronizedCollection(new ArrayList<>());
private static final Logger LOGGER = LogManager.getLogger("crash"); private static final Logger LOGGER = LogManager.getLogger("crash");
public static void makeCrashReport(Throwable throwable, String messageFormat, Object... args) { /**
* <em>This method never returns.</em>
* <p>
* TODO document
* @param throwable
* @param messageFormat
* @param args
*/
public static void crash(Throwable throwable, String messageFormat, Object... args) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
appendContextProviders(output);
addSeparator(output);
if (appendAnalyzers(output, throwable, messageFormat, args)) {
addSeparator(output);
}
appendStackTrace(output, throwable);
export(output.toString());
System.exit(0);
}
private static void appendContextProviders(StringBuilder output) {
for (ContextProvider provider : PROVIDERS) { for (ContextProvider provider : PROVIDERS) {
if (provider != null) { if (provider != null) {
Map<String, String> buf = new HashMap<>(); Map<String, String> buf = new HashMap<>();
@ -55,10 +78,13 @@ public class CrashReportGenerator {
} }
} }
} }
}
addSeparator(output); private static boolean appendAnalyzers(
StringBuilder output,
boolean analyzerResponseExist = false; Throwable throwable, String messageFormat, Object[] args
) {
boolean analyzerResponsesExist = false;
for (Analyzer analyzer : ANALYZERS) { for (Analyzer analyzer : ANALYZERS) {
if (analyzer != null) { if (analyzer != null) {
@ -67,12 +93,12 @@ public class CrashReportGenerator {
answer = analyzer.analyze(throwable, messageFormat, args); answer = analyzer.analyze(throwable, messageFormat, args);
if (answer != null && !answer.isEmpty()) { if (answer != null && !answer.isEmpty()) {
analyzerResponseExist = true; analyzerResponsesExist = true;
output.append(analyzer.getName()).append(": ").append(answer).append("\n"); output.append(analyzer.getName()).append(": ").append(answer).append("\n");
} }
} catch (Throwable t) { } catch (Throwable t) {
try { try {
analyzerResponseExist = true; analyzerResponsesExist = true;
output.append(analyzer.getName()).append(" is broken").append("\n"); output.append(analyzer.getName()).append(" is broken").append("\n");
} catch (Throwable th) { } catch (Throwable th) {
// You stupid // You stupid
@ -82,37 +108,39 @@ public class CrashReportGenerator {
} }
} }
if (analyzerResponseExist) addSeparator(output); return analyzerResponsesExist;
}
// Formatting to a human-readable string private static void appendStackTrace(StringBuilder output, Throwable throwable) {
StringWriter sink = new StringWriter(); output.append("Stacktrace: \n");
if (throwable != null) {
try { if (throwable == null) {
throwable.printStackTrace(new PrintWriter(sink)); output.append("no Throwable provided").append("\n");
} catch (Exception e) {
// PLAK
}
} else {
sink.append("Null");
} }
output.append("Stacktrace: \n"); // Formatting to a human-readable string
output.append(sink.toString()).append("\n"); Writer sink = new StringBuilderWriter(output);
LOGGER.fatal("/n" + output.toString());
try { try {
System.err.println(output.toString()); throwable.printStackTrace(new PrintWriter(sink));
} catch (Exception e) {
// PLAK
}
output.append("\n");
}
private static void export(String report) {
try {
LOGGER.fatal("/n" + report);
} catch (Exception e) { } catch (Exception e) {
// PLAK // PLAK
} }
generateCrashReportFiles(output.toString()); System.err.println(report);
System.exit(0); generateCrashReportFiles(report);
} }
public static void generateCrashReportFiles(String output) { private static void generateCrashReportFiles(String output) {
Date date = new Date(); Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss"); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss");
@ -121,7 +149,6 @@ public class CrashReportGenerator {
try { try {
Files.createDirectory(CRASH_REPORTS_PATH); Files.createDirectory(CRASH_REPORTS_PATH);
;
pathExist = true; pathExist = true;
} catch (IOException e) { } catch (IOException e) {
// Crash Report not created // Crash Report not created
@ -135,8 +162,13 @@ public class CrashReportGenerator {
} }
} }
public static void createFileForCrashReport(String buffer, String filename) { private static void createFileForCrashReport(String buffer, String filename) {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename), StandardCharsets.UTF_8)) { try (
BufferedWriter writer = Files.newBufferedWriter(
Paths.get(filename),
StandardCharsets.UTF_8
)
) {
writer.write(buffer); writer.write(buffer);
} catch (IOException ex) { } catch (IOException ex) {
// Crash Report not created // Crash Report not created
@ -152,6 +184,9 @@ public class CrashReportGenerator {
} }
private static void addSeparator(StringBuilder sb) { private static void addSeparator(StringBuilder sb) {
sb.append("-------------------------------------------------").append("\n"); sb.append(
// 80 chars
"--------------------------------------------------------------------------------"
).append("\n");
} }
} }

View File

@ -6,12 +6,12 @@ public class OutOfMemoryAnalyzer implements Analyzer {
@Override @Override
public String analyze(Throwable throwable, String messageFormat, Object... args) { public String analyze(Throwable throwable, String messageFormat, Object... args) {
if (throwable instanceof OutOfMemoryError) if (throwable instanceof OutOfMemoryError)
return "Try add memory for the JVM"; return "Try to add memory to the JVM";
return null; return null;
} }
@Override @Override
public String getName() { public String getName() {
return this.getClass().getSimpleName(); return "Out Of Memory Analyzer";
} }
} }

View File

@ -8,13 +8,13 @@ public class OSContextProvider implements ContextProvider {
@Override @Override
public void provideContext(Map<String, String> output) { public void provideContext(Map<String, String> output) {
output.put("Name OS", System.getProperty("os.name")); output.put("OS Name", System.getProperty("os.name"));
output.put("Version OS", System.getProperty("os.version")); output.put("OS Version", System.getProperty("os.version"));
output.put("Architecture OS", System.getProperty("os.arch")); output.put("OS Architecture", System.getProperty("os.arch"));
} }
@Override @Override
public String getName() { public String getName() {
return this.getClass().getSimpleName(); return "OS Context Provider";
} }
} }