diff --git a/core/src/main/java/dev/faststats/core/ErrorHelper.java b/core/src/main/java/dev/faststats/core/ErrorHelper.java index 988005d..1972777 100644 --- a/core/src/main/java/dev/faststats/core/ErrorHelper.java +++ b/core/src/main/java/dev/faststats/core/ErrorHelper.java @@ -10,6 +10,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; final class ErrorHelper { private static final int MESSAGE_LENGTH = Math.min(1000, Integer.getInteger("faststats.message-length", 500)); @@ -20,8 +21,11 @@ public static JsonObject compile(final Throwable error, @Nullable final List(stack); - if (suppress != null) toSuppress.addAll(suppress); - report.add("cause", compile(error.getCause(), toSuppress)); - } + report.addProperty("error", error.getClass().getName()); + if (message != null) report.addProperty("message", message); + + report.add("stack", stacktrace); return report; } - private static JsonArray populateTraces(final int traces, final ArrayList list, final StackTraceElement[] elements) { - final var stacktrace = new JsonArray(traces); + private static void appendCauseChain(@Nullable Throwable cause, final List parentStack, + @Nullable final List suppress, final JsonArray stacktrace) { + final var toSuppress = new ArrayList<>(parentStack); + if (suppress != null) toSuppress.addAll(suppress); + final var visited = Collections.newSetFromMap(new IdentityHashMap<>()); + while (cause != null && visited.add(cause)) { + final var causeMessage = getAnonymizedMessage(cause); + final var header = causeMessage != null + ? "Caused by: " + cause.getClass().getName() + ": " + causeMessage + : "Caused by: " + cause.getClass().getName(); + stacktrace.add(header); + + final var causeElements = cause.getStackTrace(); + final var causeStack = collapseStackTrace(causeElements); + final var causeList = new ArrayList<>(causeStack); + causeList.removeAll(toSuppress); + final var causeTraces = Math.min(causeList.size(), STACK_TRACE_LIMIT); + populateTraces(causeTraces, causeList, causeElements, stacktrace); + + cause = cause.getCause(); + } + } + private static void populateTraces(final int traces, final List list, final StackTraceElement[] elements, final JsonArray stacktrace) { for (var i = 0; i < traces; i++) { final var string = list.get(i); - if (string.length() <= STACK_TRACE_LENGTH) stacktrace.add(string); - else stacktrace.add(string.substring(0, STACK_TRACE_LENGTH) + "..."); + if (string.length() <= STACK_TRACE_LENGTH) stacktrace.add(" at " + string); + else stacktrace.add(" at " + string.substring(0, STACK_TRACE_LENGTH) + "..."); } if (traces > 0 && traces < list.size()) { - stacktrace.add("and " + (list.size() - traces) + " more..."); + stacktrace.add(" ... " + (list.size() - traces) + " more"); } else { final var i = elements.length - list.size(); - if (i > 0) stacktrace.add("Omitted " + i + " duplicate stack frame" + (i == 1 ? "" : "s")); + if (i > 0) stacktrace.add(" ... " + i + " more"); } - return stacktrace; } private static List collapseStackTrace(final StackTraceElement[] trace) { @@ -164,29 +187,29 @@ private static boolean isSameClassLoader(final ClassLoader classLoader, final Cl return loader == current; } - private static final String IPV4_PATTERN = - "\\b(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\b"; - private static final String IPV6_PATTERN = - "(?i)\\b([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}\\b|" + // Full form - "(?i)\\b([0-9a-f]{1,4}:){1,7}:\\b|" + // Trailing :: - "(?i)\\b([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}\\b|" + // :: in middle (1 group after) - "(?i)\\b([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}\\b|" + // :: in middle (2 groups after) - "(?i)\\b([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}\\b|" + // :: in middle (3 groups after) - "(?i)\\b([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\\b|" + // :: in middle (4 groups after) - "(?i)\\b([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\\b|" + // :: in middle (5 groups after) - "(?i)\\b[0-9a-f]{1,4}:(:[0-9a-f]{1,4}){1,6}\\b|" + // :: in middle (6 groups after) - "(?i)\\b:(:[0-9a-f]{1,4}){1,7}\\b|" + // Leading :: - "(?i)\\b::([0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4}\\b|" + // :: at start - "(?i)\\b::\\b"; // Just :: - private static final String USER_HOME_PATH_PATTERN = - "(/home/)[^/\\s]+" + // Linux: /home/username - "|(/Users/)[^/\\s]+" + // macOS: /Users/username - "|((?i)[A-Z]:\\\\Users\\\\)[^\\\\\\s]+"; // Windows: A-Z:\\Users\\username + private static final Pattern IPV4_PATTERN = Pattern.compile( + "\\b(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\b"); + private static final Pattern IPV6_PATTERN = Pattern.compile( + "(?i)\\b([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}\\b|" + // Full form + "(?i)\\b([0-9a-f]{1,4}:){1,7}:\\b|" + // Trailing :: + "(?i)\\b([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}\\b|" + // :: in middle (1 group after) + "(?i)\\b([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}\\b|" + // :: in middle (2 groups after) + "(?i)\\b([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}\\b|" + // :: in middle (3 groups after) + "(?i)\\b([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\\b|" + // :: in middle (4 groups after) + "(?i)\\b([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\\b|" + // :: in middle (5 groups after) + "(?i)\\b[0-9a-f]{1,4}:(:[0-9a-f]{1,4}){1,6}\\b|" + // :: in middle (6 groups after) + "(?i)\\b:(:[0-9a-f]{1,4}){1,7}\\b|" + // Leading :: + "(?i)\\b::([0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4}\\b|" + // :: at start + "(?i)\\b::\\b"); // Just :: + private static final Pattern USER_HOME_PATH_PATTERN = Pattern.compile( + "(/home/)[^/\\s]+" + // Linux: /home/username + "|(/Users/)[^/\\s]+" + // macOS: /Users/username + "|((?i)[A-Z]:\\\\Users\\\\)[^\\\\\\s]+"); // Windows: A-Z:\\Users\\username private static String anonymize(String message) { - message = message.replaceAll(IPV4_PATTERN, "[IP hidden]"); - message = message.replaceAll(IPV6_PATTERN, "[IP hidden]"); - message = message.replaceAll(USER_HOME_PATH_PATTERN, "$1$2$3[username hidden]"); + message = IPV4_PATTERN.matcher(message).replaceAll("[IP hidden]"); + message = IPV6_PATTERN.matcher(message).replaceAll("[IP hidden]"); + message = USER_HOME_PATH_PATTERN.matcher(message).replaceAll("$1$2$3[username hidden]"); final var username = System.getProperty("user.name"); if (username != null) message = message.replace(username, "[username hidden]"); return message; diff --git a/gradle.properties b/gradle.properties index 9f28a3f..beefc2c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.17.0 +version=0.17.1