Documented and prettified CrashReports
This commit is contained in:
parent
bedc9fc729
commit
01645f5c6e
@ -690,6 +690,34 @@ public class StringUtil {
|
||||
return padToRight(src, length, ' ');
|
||||
}
|
||||
|
||||
public static String center(String src, int length) {
|
||||
return center(src, length, ' ');
|
||||
}
|
||||
|
||||
public static String center(String src, int length, char filler) {
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException("length must be positive (" + length + ")");
|
||||
}
|
||||
|
||||
if (src == null || length <= src.length()) {
|
||||
return src;
|
||||
}
|
||||
|
||||
char[] result = new char[length];
|
||||
|
||||
int leftPaddingLength = (length - src.length()) / 2;
|
||||
|
||||
Arrays.fill(result, 0, leftPaddingLength, filler);
|
||||
|
||||
for (int i = 0; i < src.length(); ++i) {
|
||||
result[i + leftPaddingLength] = src.charAt(i);
|
||||
}
|
||||
|
||||
Arrays.fill(result, leftPaddingLength + src.length(), result.length, filler);
|
||||
|
||||
return new String(result);
|
||||
}
|
||||
|
||||
public static int countWords(String src) {
|
||||
int i = 0;
|
||||
boolean isWord = false;
|
||||
|
@ -18,11 +18,14 @@ import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A utility for reporting critical problems, gathering system context and terminating the application consequently (crashing).
|
||||
* Do not hesitate to use {@link #report(Throwable, String, Object...)} at every other line.
|
||||
*
|
||||
* @author serega404
|
||||
*/
|
||||
public class CrashReports {
|
||||
|
||||
private CrashReports() {
|
||||
}
|
||||
|
||||
private static final Path CRASH_REPORTS_PATH = Paths.get("crash-reports");
|
||||
|
||||
private static final Collection<ContextProvider> PROVIDERS = Collections.synchronizedCollection(new ArrayList<>());
|
||||
@ -31,31 +34,68 @@ public class CrashReports {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("crash");
|
||||
|
||||
/**
|
||||
* Creates a {@link ReportedException} that describes the provided problem so the program can crash later.
|
||||
* This method is intended to be used like so:
|
||||
*
|
||||
* <pre>
|
||||
* try {
|
||||
* doSomethingDifficult(x);
|
||||
* } catch (CouldntMakeItException e) {
|
||||
* throw CrashReports.report(e, "We couldn't make it at x = %d", x);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>Such usage ensures that the report will be dealt with at the top of the call stack
|
||||
* (at least in methods that have a properly set up
|
||||
* {@linkplain #crash(Throwable, String, Object...) crash handler}). Not throwing the returned
|
||||
* exception is pointless; using this in a thread without a crash handler will not produce a crash.
|
||||
*
|
||||
* <p>Avoid inserting variable information into {@code messageFormat} directly; use
|
||||
* {@linkplain Formatter#summary format string} syntax and {@code args}. Different Strings
|
||||
* in {@code messageFormat} may be interpreted as unrelated problems by {@linkplain Analyzer crash analyzers}.
|
||||
*
|
||||
* @param throwable a {@link Throwable} that caused the problem, if any; {@code null} otherwise
|
||||
* @param messageFormat a human-readable description of the problem displayed in the crash report
|
||||
* @param args an array of arguments for formatting {@code messageFormat}
|
||||
* @return an exception containing the provided information that must be thrown
|
||||
*/
|
||||
public static ReportedException report(Throwable throwable, String messageFormat, Object... args) {
|
||||
if (throwable instanceof ReportedException) return (ReportedException) throwable;
|
||||
if (throwable instanceof ReportedException) return (ReportedException) throwable;
|
||||
|
||||
return new ReportedException(throwable, messageFormat, args);
|
||||
}
|
||||
|
||||
public static RuntimeException crash(Throwable throwable, String messageFormat, Object... args) {
|
||||
if (throwable instanceof ReportedException) {
|
||||
throw crash((ReportedException) throwable);
|
||||
} else {
|
||||
throw crash(report(throwable, messageFormat, args));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <em>This method never returns.</em>
|
||||
* <p>
|
||||
* TODO document
|
||||
*
|
||||
* @param reportException
|
||||
* Crashes the program due to the supplied problem.
|
||||
*
|
||||
* <p><em>Use {@link #report(Throwable, String, Object...)} unless you are creating a catch-all handler for a
|
||||
* thread.</em>
|
||||
*
|
||||
* <p>This method recovers information about the problem by casting {@code throwable} to {@link ReportedException},
|
||||
* or, failing that, uses the provided arguments as the information instead. It then constructs a full crash
|
||||
* report, exports it and terminates the program by invoking {@link System#exit(int)}.
|
||||
*
|
||||
* <p>Such behavior can be dangerous or lead to unwanted consequences in the middle of the call stack, so it is
|
||||
* necessary to invoke this method as high on the call stack as possible, usually in a {@code catch} clause
|
||||
* of a {@code try} statement enveloping the thread's main method(s).
|
||||
*
|
||||
* @param throwable a {@link ReportedException} or another {@link Throwable} that caused the problem, if any;
|
||||
* {@code null} otherwise
|
||||
* @param messageFormat a human-readable description of the problem used when {@code throwable} is not a
|
||||
* {@link ReportedException}. See {@link #report(Throwable, String, Object...)} for details.
|
||||
* @param args an array of arguments for formatting {@code messageFormat}
|
||||
* @return {@code null}, although this method never returns normally. Provided for convenience.
|
||||
*/
|
||||
public static RuntimeException crash(ReportedException reportException) {
|
||||
Throwable throwable = reportException.getCause();
|
||||
String messageFormat = reportException.getMessageFormat();
|
||||
Object[] args = reportException.getArgs();
|
||||
public static RuntimeException crash(Throwable throwable, String messageFormat, Object... args) {
|
||||
if (throwable instanceof ReportedException) {
|
||||
ReportedException reportedException = (ReportedException) throwable;
|
||||
|
||||
// Discard provided arguments
|
||||
throwable = reportedException.getCause();
|
||||
messageFormat = reportedException.getMessageFormat();
|
||||
args = reportedException.getArgs();
|
||||
}
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
@ -92,7 +132,7 @@ public class CrashReports {
|
||||
export(output.toString());
|
||||
|
||||
System.exit(0);
|
||||
return reportException;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void appendContextProviders(StringBuilder output) {
|
||||
@ -111,7 +151,7 @@ public class CrashReports {
|
||||
provider.provideContext(buf);
|
||||
|
||||
if (!buf.isEmpty()) {
|
||||
output.append(StringUtilsTemp.center(provider.getName(), 80)).append("\n");
|
||||
output.append(StringUtil.center(provider.getName(), 80)).append("\n");
|
||||
for (Map.Entry<String, String> entry : buf.entrySet()) {
|
||||
output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
|
||||
}
|
||||
@ -139,7 +179,7 @@ public class CrashReports {
|
||||
Analyzer[] localAnalyzersCopy = ANALYZERS.toArray(new Analyzer[ANALYZERS.size()]);
|
||||
|
||||
if (localAnalyzersCopy.length > 0) {
|
||||
output.append(StringUtilsTemp.center("Analyzers", 80)).append("\n");
|
||||
output.append(StringUtil.center("Analyzers", 80)).append("\n");
|
||||
}
|
||||
|
||||
for (Analyzer analyzer : localAnalyzersCopy) {
|
||||
@ -239,57 +279,73 @@ public class CrashReports {
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerProvider(ContextProvider provider) {
|
||||
PROVIDERS.add(provider);
|
||||
}
|
||||
|
||||
public static void registerAnalyzer(Analyzer analyzer) {
|
||||
ANALYZERS.add(analyzer);
|
||||
}
|
||||
|
||||
private static void addSeparator(StringBuilder sb) {
|
||||
sb.append(StringUtil.sequence('-', 80)).append("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the provided {@link ContextProvider} so it is consulted in the case of a crash.
|
||||
* @param provider the provider to register
|
||||
*/
|
||||
public static void registerProvider(ContextProvider provider) {
|
||||
PROVIDERS.add(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the provided {@link Analyzer} so it is consulted in the case of a crash.
|
||||
* @param analyzer the analyzer to register
|
||||
*/
|
||||
public static void registerAnalyzer(Analyzer analyzer) {
|
||||
ANALYZERS.add(analyzer);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper used by {@link CrashReports} to transfer problem details from the place of
|
||||
* occurrence to the handler at the top of the stack. Rethrow if caught
|
||||
* (unless using {@link CrashReports#report(Throwable, String, Object...)}, which does
|
||||
* so automatically).
|
||||
*
|
||||
* @author serega404
|
||||
*/
|
||||
public static class ReportedException extends RuntimeException {
|
||||
|
||||
private String messageFormat;
|
||||
private Object[] args;
|
||||
private static final long serialVersionUID = 223720835231091533L;
|
||||
|
||||
private final String messageFormat;
|
||||
private final Object[] args;
|
||||
|
||||
public ReportedException(Throwable throwable, String messageFormat, Object... args){
|
||||
/**
|
||||
* Constructs a {@link ReportedException}.
|
||||
* @param throwable the reported {@link Throwable} or {@code null}
|
||||
* @param messageFormat the reported message format.
|
||||
* <em>This is not the message of the constructed Exception</em>.
|
||||
* @param args the reported message format arguments
|
||||
*/
|
||||
public ReportedException(Throwable throwable, String messageFormat, Object... args) {
|
||||
super(throwable);
|
||||
this.messageFormat = messageFormat;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reported message format.
|
||||
* @return message format
|
||||
*/
|
||||
public String getMessageFormat() {
|
||||
return messageFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reported message format arguments.
|
||||
* @return message format arguments
|
||||
*/
|
||||
public Object[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
private CrashReports() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StringUtilsTemp {
|
||||
public static String center(String s, int size) {
|
||||
return center(s, size, ' ');
|
||||
}
|
||||
|
||||
public static String center(String s, int size, char pad) {
|
||||
if (s == null || size <= s.length())
|
||||
return s;
|
||||
|
||||
StringBuilder sb = new StringBuilder(size);
|
||||
for (int i = 0; i < (size - s.length()) / 2; i++) {
|
||||
sb.append(pad);
|
||||
}
|
||||
sb.append(s);
|
||||
while (sb.length() < size) {
|
||||
sb.append(pad);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ public class StackTraceProvider implements ContextProvider {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n");
|
||||
for (int i = 4; i < stackTraceBuffer.length; i++) {
|
||||
sb.append(stackTraceBuffer[i].toString()).append("\n");
|
||||
sb.append('\t').append(stackTraceBuffer[i].toString()).append("\n");
|
||||
}
|
||||
|
||||
output.put("Reported from " + Thread.currentThread().getName(), sb.toString());
|
||||
|
Reference in New Issue
Block a user