diff --git a/src/main/java/com/squareup/squash/SquashBacktrace.java b/src/main/java/com/squareup/squash/SquashBacktrace.java index f3e766d..a5edc53 100644 --- a/src/main/java/com/squareup/squash/SquashBacktrace.java +++ b/src/main/java/com/squareup/squash/SquashBacktrace.java @@ -24,35 +24,27 @@ /** Creates the Squash stacktrace format for serialization by gson. */ public final class SquashBacktrace { - // This lets Squash know that the stacktrace is java-style and can contain obfuscated classes. - public static final String JAVA_PREFIX = "_JAVA_"; - private SquashBacktrace() { // Should not be instantiated: this is a utility class. } - public static List> getBacktraces(Throwable error) { + public static List getBacktraces(Throwable error) { if (error == null) { return null; } - final ArrayList> threadList = new ArrayList>(); - final ArrayList currentThread = new ArrayList(); - currentThread.add(Thread.currentThread().getName()); - currentThread.add(true); - currentThread.add(getStacktraceArray(error)); + final List threadList = new ArrayList(); + final SquashException currentThread = + new SquashException(Thread.currentThread().getName(), true, getStacktraceArray(error)); threadList.add(currentThread); return threadList; } - private static List> getStacktraceArray(Throwable error) { - List> stackElems = new ArrayList>(); + private static List getStacktraceArray(Throwable error) { + List stackElems = new ArrayList(); for (StackTraceElement element : error.getStackTrace()) { - List elementList = new ArrayList(); - elementList.add(JAVA_PREFIX); - elementList.add(element.getFileName()); - elementList.add(element.getLineNumber()); - elementList.add(element.getMethodName()); - elementList.add(element.getClassName()); + StackElement elementList = + new StackElement(element.getClassName(), element.getFileName(), element.getLineNumber(), + element.getMethodName()); stackElems.add(elementList); } return stackElems; @@ -92,21 +84,35 @@ public static void populateNestedExceptions(List nestedExceptio return; } final Throwable cause = error.getCause(); - NestedException doc = new NestedException(cause.getClass().getName(), cause.getMessage(), - getBacktraces(cause), getIvars(cause)); + NestedException doc = + new NestedException(cause.getClass().getName(), cause.getMessage(), getBacktraces(cause), + getIvars(cause)); nestedExceptions.add(doc); // Exceptions all the way down! populateNestedExceptions(nestedExceptions, cause); } + /** Wrapper object for top-level exceptions. */ + static final class SquashException { + final String name; + final boolean faulted; + final List backtrace; + + public SquashException(String name, boolean faulted, List backtrace) { + this.backtrace = backtrace; + this.name = name; + this.faulted = faulted; + } + } + /** Wrapper object for nested exceptions. */ - public static class NestedException { + static final class NestedException { final String class_name; final String message; - final List> backtraces; + final List backtraces; final Map ivars; - public NestedException(String className, String message, List> backtraces, + public NestedException(String className, String message, List backtraces, Map ivars) { this.class_name = className; this.message = message; @@ -114,4 +120,23 @@ public NestedException(String className, String message, List> back this.ivars = ivars; } } + + /** Wrapper object for a stacktrace entry. */ + static final class StackElement { + // This field is necessary so Squash knows that this is a java stacktrace that might need + // obfuscation lookup and git filename lookup. Our stacktrace elements don't give us the full + // path to the java file, so Squash has to do a SCM lookup to try and do its cause analysis. + @SuppressWarnings("UnusedDeclaration") final String type = "obfuscated"; + final String file; + final int line; + final String symbol; + final String class_name; + + private StackElement(String className, String file, int line, String methodName) { + this.class_name = className; + this.file = file; + this.line = line; + this.symbol = methodName; + } + } } diff --git a/src/main/java/com/squareup/squash/SquashEntry.java b/src/main/java/com/squareup/squash/SquashEntry.java index a0df896..a7f5ad4 100644 --- a/src/main/java/com/squareup/squash/SquashEntry.java +++ b/src/main/java/com/squareup/squash/SquashEntry.java @@ -46,7 +46,7 @@ public class SquashEntry { private final String occurred_at; // Used in tests. - final List> backtraces; + final List backtraces; final Map ivars; final List parent_exceptions; final String class_name; diff --git a/src/test/java/com/squareup/squash/SquashEntryTest.java b/src/test/java/com/squareup/squash/SquashEntryTest.java index 35a32e8..76c9738 100644 --- a/src/test/java/com/squareup/squash/SquashEntryTest.java +++ b/src/test/java/com/squareup/squash/SquashEntryTest.java @@ -60,10 +60,10 @@ private SquashEntry serializeAndDeserialize(SquashEntry logEntry) throws IOExcep final SquashEntry logEntry = factory.create(logMessage, exception); SquashEntry deserialized = serializeAndDeserialize(logEntry); assertThat(deserialized.backtraces).isNotEmpty(); - final List backtrace = deserialized.backtraces.get(0); - assertThat(backtrace.get(0)).isEqualTo(Thread.currentThread().getName()); - assertThat(backtrace.get(1)).isEqualTo(true); - List> stackElements = (List>) backtrace.get(2); + final SquashBacktrace.SquashException backtrace = deserialized.backtraces.get(0); + assertThat(backtrace.name).isEqualTo(Thread.currentThread().getName()); + assertThat(backtrace.faulted).isEqualTo(true); + List stackElements = backtrace.backtrace; assertBacktracesMatch(myLittleStackTrace, stackElements); assertThat(deserialized.ivars).isEmpty(); assertThat(deserialized.log_message).isEqualTo(logMessage); @@ -73,15 +73,14 @@ private SquashEntry serializeAndDeserialize(SquashEntry logEntry) throws IOExcep } private void assertBacktracesMatch(StackTraceElement[] myLittleStackTrace, - List> stackElements) { + List stackElements) { for (int i = 0, stackElementsSize = stackElements.size(); i < stackElementsSize; i++) { - List stackElement = stackElements.get(i); + SquashBacktrace.StackElement stackElement = stackElements.get(i); StackTraceElement expected = myLittleStackTrace[i]; - assertThat(stackElement.get(0)).isEqualTo(SquashBacktrace.JAVA_PREFIX); - assertThat(stackElement.get(1)).isEqualTo(expected.getFileName()); - assertThat(((Double) stackElement.get(2)).intValue()).isEqualTo(expected.getLineNumber()); - assertThat(stackElement.get(3)).isEqualTo(expected.getMethodName()); - assertThat(stackElement.get(4)).isEqualTo(expected.getClassName()); + assertThat(stackElement.file).isEqualTo(expected.getFileName()); + assertThat(stackElement.line).isEqualTo(expected.getLineNumber()); + assertThat(stackElement.symbol).isEqualTo(expected.getMethodName()); + assertThat(stackElement.class_name).isEqualTo(expected.getClassName()); } } @@ -133,10 +132,10 @@ private void assertBacktracesMatch(StackTraceElement[] myLittleStackTrace, final SquashEntry logEntry = factory.create(logMessage, exception); SquashEntry deserialized = serializeAndDeserialize(logEntry); assertThat(deserialized.backtraces).isNotEmpty(); - List backtrace = deserialized.backtraces.get(0); - assertThat(backtrace.get(0)).isEqualTo(Thread.currentThread().getName()); - assertThat(backtrace.get(1)).isEqualTo(true); - List> stackElements = (List>) backtrace.get(2); + SquashBacktrace.SquashException backtrace = deserialized.backtraces.get(0); + assertThat(backtrace.name).isEqualTo(Thread.currentThread().getName()); + assertThat(backtrace.faulted).isEqualTo(true); + List stackElements = backtrace.backtrace; assertBacktracesMatch(myLittleStackTrace, stackElements); assertThat(deserialized.ivars).isEmpty(); assertThat(deserialized.log_message).isEqualTo(logMessage); @@ -149,18 +148,18 @@ private void assertBacktracesMatch(StackTraceElement[] myLittleStackTrace, assertThat(nested1.ivars).isEmpty(); assertThat(nested1.message).isEqualTo(nestedExceptionMessage); backtrace = nested1.backtraces.get(0); - assertThat(backtrace.get(0)).isEqualTo(Thread.currentThread().getName()); - assertThat(backtrace.get(1)).isEqualTo(true); - assertBacktracesMatch(nestedStackTrace, (List>) backtrace.get(2)); + assertThat(backtrace.name).isEqualTo(Thread.currentThread().getName()); + assertThat(backtrace.faulted).isEqualTo(true); + assertBacktracesMatch(nestedStackTrace, backtrace.backtrace); final SquashBacktrace.NestedException nested2 = nestedExceptions.get(1); assertThat(nested2.class_name).isEqualTo(doublyNestedException.getClass().getName()); assertThat(nested2.ivars).isEmpty(); assertThat(nested2.message).isEqualTo(doublyNestedExceptionMessage); backtrace = nested1.backtraces.get(0); - assertThat(backtrace.get(0)).isEqualTo(Thread.currentThread().getName()); - assertThat(backtrace.get(1)).isEqualTo(true); - assertBacktracesMatch(nestedStackTrace, (List>) backtrace.get(2)); + assertThat(backtrace.name).isEqualTo(Thread.currentThread().getName()); + assertThat(backtrace.faulted).isEqualTo(true); + assertBacktracesMatch(nestedStackTrace, backtrace.backtrace); } @Test public void testInfinitelyNestedExceptions() throws Exception { @@ -212,10 +211,10 @@ private void assertBacktracesMatch(StackTraceElement[] myLittleStackTrace, final SquashEntry logEntry = factory.create(logMessage, exception); SquashEntry deserialized = serializeAndDeserialize(logEntry); assertThat(deserialized.backtraces).isNotEmpty(); - List backtrace = deserialized.backtraces.get(0); - assertThat(backtrace.get(0)).isEqualTo(Thread.currentThread().getName()); - assertThat(backtrace.get(1)).isEqualTo(true); - List> stackElements = (List>) backtrace.get(2); + SquashBacktrace.SquashException backtrace = deserialized.backtraces.get(0); + assertThat(backtrace.name).isEqualTo(Thread.currentThread().getName()); + assertThat(backtrace.faulted).isEqualTo(true); + List stackElements = backtrace.backtrace; assertBacktracesMatch(myLittleStackTrace, stackElements); assertThat(deserialized.ivars).isEmpty(); assertThat(deserialized.log_message).isEqualTo(logMessage); @@ -228,18 +227,18 @@ private void assertBacktracesMatch(StackTraceElement[] myLittleStackTrace, assertThat(nested1.ivars).isEmpty(); assertThat(nested1.message).isEqualTo(nestedExceptionMessage); backtrace = nested1.backtraces.get(0); - assertThat(backtrace.get(0)).isEqualTo(Thread.currentThread().getName()); - assertThat(backtrace.get(1)).isEqualTo(true); - assertBacktracesMatch(nestedStackTrace, (List>) backtrace.get(2)); + assertThat(backtrace.name).isEqualTo(Thread.currentThread().getName()); + assertThat(backtrace.faulted).isEqualTo(true); + assertBacktracesMatch(nestedStackTrace, backtrace.backtrace); final SquashBacktrace.NestedException nested2 = nestedExceptions.get(1); assertThat(nested2.class_name).isEqualTo(doublyNestedException.getClass().getName()); assertThat(nested2.ivars).isEmpty(); assertThat(nested2.message).isEqualTo(doublyNestedExceptionMessage); backtrace = nested1.backtraces.get(0); - assertThat(backtrace.get(0)).isEqualTo(Thread.currentThread().getName()); - assertThat(backtrace.get(1)).isEqualTo(true); - assertBacktracesMatch(nestedStackTrace, (List>) backtrace.get(2)); + assertThat(backtrace.name).isEqualTo(Thread.currentThread().getName()); + assertThat(backtrace.faulted).isEqualTo(true); + assertBacktracesMatch(nestedStackTrace, backtrace.backtrace); } private class EntryFactory {