Documented and prettified CrashReports
This commit is contained in:
parent
bedc9fc729
commit
01645f5c6e
@ -690,6 +690,34 @@ public class StringUtil {
|
|||||||
return padToRight(src, length, ' ');
|
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) {
|
public static int countWords(String src) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
boolean isWord = false;
|
boolean isWord = false;
|
||||||
|
@ -18,11 +18,14 @@ import java.text.DateFormat;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
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 {
|
public class CrashReports {
|
||||||
|
|
||||||
private CrashReports() {
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = Collections.synchronizedCollection(new ArrayList<>());
|
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");
|
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) {
|
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);
|
return new ReportedException(throwable, messageFormat, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(Throwable throwable, String messageFormat, Object... args) {
|
public static RuntimeException crash(Throwable throwable, String messageFormat, Object... args) {
|
||||||
if (throwable instanceof ReportedException) {
|
if (throwable instanceof ReportedException) {
|
||||||
throw crash((ReportedException) throwable);
|
ReportedException reportedException = (ReportedException) throwable;
|
||||||
} else {
|
|
||||||
throw crash(report(throwable, messageFormat, args));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Discard provided arguments
|
||||||
* <em>This method never returns.</em>
|
throwable = reportedException.getCause();
|
||||||
* <p>
|
messageFormat = reportedException.getMessageFormat();
|
||||||
* TODO document
|
args = reportedException.getArgs();
|
||||||
*
|
}
|
||||||
* @param reportException
|
|
||||||
*/
|
|
||||||
public static RuntimeException crash(ReportedException reportException) {
|
|
||||||
Throwable throwable = reportException.getCause();
|
|
||||||
String messageFormat = reportException.getMessageFormat();
|
|
||||||
Object[] args = reportException.getArgs();
|
|
||||||
|
|
||||||
StringBuilder output = new StringBuilder();
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
@ -92,7 +132,7 @@ public class CrashReports {
|
|||||||
export(output.toString());
|
export(output.toString());
|
||||||
|
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
return reportException;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void appendContextProviders(StringBuilder output) {
|
private static void appendContextProviders(StringBuilder output) {
|
||||||
@ -111,7 +151,7 @@ public class CrashReports {
|
|||||||
provider.provideContext(buf);
|
provider.provideContext(buf);
|
||||||
|
|
||||||
if (!buf.isEmpty()) {
|
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()) {
|
for (Map.Entry<String, String> entry : buf.entrySet()) {
|
||||||
output.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
|
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()]);
|
Analyzer[] localAnalyzersCopy = ANALYZERS.toArray(new Analyzer[ANALYZERS.size()]);
|
||||||
|
|
||||||
if (localAnalyzersCopy.length > 0) {
|
if (localAnalyzersCopy.length > 0) {
|
||||||
output.append(StringUtilsTemp.center("Analyzers", 80)).append("\n");
|
output.append(StringUtil.center("Analyzers", 80)).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Analyzer analyzer : localAnalyzersCopy) {
|
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) {
|
private static void addSeparator(StringBuilder sb) {
|
||||||
sb.append(StringUtil.sequence('-', 80)).append("\n");
|
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 {
|
public static class ReportedException extends RuntimeException {
|
||||||
|
|
||||||
private String messageFormat;
|
private static final long serialVersionUID = 223720835231091533L;
|
||||||
private Object[] args;
|
|
||||||
|
|
||||||
public ReportedException(Throwable throwable, String messageFormat, Object... args){
|
private final String messageFormat;
|
||||||
|
private final 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);
|
super(throwable);
|
||||||
this.messageFormat = messageFormat;
|
this.messageFormat = messageFormat;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reported message format.
|
||||||
|
* @return message format
|
||||||
|
*/
|
||||||
public String getMessageFormat() {
|
public String getMessageFormat() {
|
||||||
return messageFormat;
|
return messageFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reported message format arguments.
|
||||||
|
* @return message format arguments
|
||||||
|
*/
|
||||||
public Object[] getArgs() {
|
public Object[] getArgs() {
|
||||||
return args;
|
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();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
for (int i = 4; i < stackTraceBuffer.length; i++) {
|
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());
|
output.put("Reported from " + Thread.currentThread().getName(), sb.toString());
|
||||||
|
Reference in New Issue
Block a user