From 01f5ffb45b1a66d202dceb833eae05e8482b7771 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 4 Mar 2026 20:07:18 +0100 Subject: [PATCH 1/5] Refactor error stack trace formatting --- .../java/dev/faststats/core/ErrorHelper.java | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/dev/faststats/core/ErrorHelper.java b/core/src/main/java/dev/faststats/core/ErrorHelper.java index 988005d..fb88c25 100644 --- a/core/src/main/java/dev/faststats/core/ErrorHelper.java +++ b/core/src/main/java/dev/faststats/core/ErrorHelper.java @@ -20,8 +20,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 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); + final var toSuppress = new ArrayList<>(parentStack); + if (suppress != null) toSuppress.addAll(suppress); + 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) { From a19ee19c387e42bf16492caf8f1fec16839b1abb Mon Sep 17 00:00:00 2001 From: david Date: Wed, 4 Mar 2026 20:13:48 +0100 Subject: [PATCH 2/5] Refactor regex patterns to compiled Pattern objects --- .../java/dev/faststats/core/ErrorHelper.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/dev/faststats/core/ErrorHelper.java b/core/src/main/java/dev/faststats/core/ErrorHelper.java index fb88c25..e6db338 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)); @@ -186,9 +187,9 @@ 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 = + 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) @@ -199,16 +200,16 @@ private static boolean isSameClassLoader(final ClassLoader classLoader, final Cl "(?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 = + "(?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 + "|((?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; From 71c6c606bc0afd4604c4c7067179685475c78b7f Mon Sep 17 00:00:00 2001 From: david Date: Wed, 4 Mar 2026 20:13:53 +0100 Subject: [PATCH 3/5] Refactor toSuppress list initialization in ErrorHelper --- core/src/main/java/dev/faststats/core/ErrorHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/dev/faststats/core/ErrorHelper.java b/core/src/main/java/dev/faststats/core/ErrorHelper.java index e6db338..ab8a251 100644 --- a/core/src/main/java/dev/faststats/core/ErrorHelper.java +++ b/core/src/main/java/dev/faststats/core/ErrorHelper.java @@ -46,6 +46,8 @@ public static JsonObject compile(final Throwable error, @Nullable 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); @@ -57,8 +59,6 @@ private static void appendCauseChain(@Nullable Throwable cause, final List(causeStack); - final var toSuppress = new ArrayList<>(parentStack); - if (suppress != null) toSuppress.addAll(suppress); causeList.removeAll(toSuppress); final var causeTraces = Math.min(causeList.size(), STACK_TRACE_LIMIT); populateTraces(causeTraces, causeList, causeElements, stacktrace); From bbb1efbc6478487b5b46d46ff3abf761e7b88b4a Mon Sep 17 00:00:00 2001 From: david Date: Wed, 4 Mar 2026 20:15:58 +0100 Subject: [PATCH 4/5] Align regex pattern comment formatting --- .../java/dev/faststats/core/ErrorHelper.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/dev/faststats/core/ErrorHelper.java b/core/src/main/java/dev/faststats/core/ErrorHelper.java index ab8a251..1972777 100644 --- a/core/src/main/java/dev/faststats/core/ErrorHelper.java +++ b/core/src/main/java/dev/faststats/core/ErrorHelper.java @@ -190,21 +190,21 @@ private static boolean isSameClassLoader(final ClassLoader classLoader, final Cl 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 :: + "(?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 + "(/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 = IPV4_PATTERN.matcher(message).replaceAll("[IP hidden]"); From 09eb701eda46e26236fd82eb7128d7937ac7c24a Mon Sep 17 00:00:00 2001 From: david Date: Wed, 4 Mar 2026 20:17:18 +0100 Subject: [PATCH 5/5] Bump version to 0.17.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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