package org.jboss.errai.common.client.logging.util;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* Formats stack traces so that the Google Chrome web console will translate the line numbers with source maps.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class StackTraceFormatter {
private StackTraceFormatter() {}
public static List<String> getStackTraces(final Throwable error) {
final List<Throwable> causes = new ArrayList<>();
Throwable cur = error.getCause();
while (cur != null) {
causes.add(cur);
cur = cur.getCause();
}
final List<String> stacks = new ArrayList<>();
final String errorStack = getNativeStack(error);
if (errorStack != null) {
// Important: As late as Google Chrome 48, only stack traces starting with 'Error: ' get translated.
stacks.add("Error: " + getNameAndMessage(error) + errorStack);
for (final Throwable t : causes) {
final String rawStack = getNativeStack(t);
stacks.add("Error caused by: " + getNameAndMessage(t) + rawStack);
}
}
else {
stacks.add(getEmulatedStack(error));
}
return stacks;
}
private static String getEmulatedStack(final Throwable t) {
String stack;
final StringBuilder builder = new StringBuilder();
t.printStackTrace(new PrintStream((OutputStream) null) {
@Override
public void println(final String x) {
builder.append(x).append('\n');
}
});
stack = builder.toString();
return stack;
}
private static String getNativeStack(final Throwable t) {
return maybeGetJSError(t)
.map(jsError -> {
String stack = (String) ReflectableJSO.create(jsError).get("stack");
stack = stack.substring(stack.indexOf('\n'));
return stack;
}).orElse(null);
}
private static Optional<Object> maybeGetJSError( final Throwable t ) {
final ReflectableJSO reflectable = ReflectableJSO.create(t);
return Arrays
.stream(reflectable.properties())
.map(key -> reflectable.get(key))
.filter(obj -> obj != null)
.map(prop -> ReflectableJSO.create(prop))
.filter(prop -> prop.get("name") != null && prop.get("message") != null)
.map(prop -> prop.unwrap())
.findFirst();
}
private static String getNameAndMessage(final Throwable t) {
return t.getClass().getSimpleName() + ": " + t.getMessage();
}
}