Skip to content

Commit 0d72346

Browse files
authored
Merge pull request #100 from faststats-dev/refactor/error-tracking
Refactor error tracking
2 parents 901868e + 09eb701 commit 0d72346

File tree

2 files changed

+61
-38
lines changed

2 files changed

+61
-38
lines changed

core/src/main/java/dev/faststats/core/ErrorHelper.java

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.IdentityHashMap;
1111
import java.util.List;
1212
import java.util.Set;
13+
import java.util.regex.Pattern;
1314

1415
final class ErrorHelper {
1516
private static final int MESSAGE_LENGTH = Math.min(1000, Integer.getInteger("faststats.message-length", 500));
@@ -20,42 +21,64 @@ public static JsonObject compile(final Throwable error, @Nullable final List<Str
2021
final var report = new JsonObject();
2122
final var message = getAnonymizedMessage(error);
2223

23-
report.addProperty("error", error.getClass().getName());
24-
if (message != null) report.addProperty("message", message);
24+
final var stacktrace = new JsonArray();
25+
final var header = message != null
26+
? error.getClass().getName() + ": " + message
27+
: error.getClass().getName();
28+
stacktrace.add(header);
2529

2630
final var elements = error.getStackTrace();
2731
final var stack = collapseStackTrace(elements);
2832
final var list = new ArrayList<>(stack);
2933
if (suppress != null) list.removeAll(suppress);
3034
final var traces = Math.min(list.size(), STACK_TRACE_LIMIT);
3135

32-
final var stacktrace = populateTraces(traces, list, elements);
33-
if (!stacktrace.isEmpty()) report.add("stack", stacktrace);
36+
populateTraces(traces, list, elements, stacktrace);
37+
appendCauseChain(error.getCause(), stack, suppress, stacktrace);
3438

35-
if (error.getCause() != null) {
36-
final var toSuppress = new ArrayList<>(stack);
37-
if (suppress != null) toSuppress.addAll(suppress);
38-
report.add("cause", compile(error.getCause(), toSuppress));
39-
}
39+
report.addProperty("error", error.getClass().getName());
40+
if (message != null) report.addProperty("message", message);
41+
42+
report.add("stack", stacktrace);
4043

4144
return report;
4245
}
4346

44-
private static JsonArray populateTraces(final int traces, final ArrayList<String> list, final StackTraceElement[] elements) {
45-
final var stacktrace = new JsonArray(traces);
47+
private static void appendCauseChain(@Nullable Throwable cause, final List<String> parentStack,
48+
@Nullable final List<String> suppress, final JsonArray stacktrace) {
49+
final var toSuppress = new ArrayList<>(parentStack);
50+
if (suppress != null) toSuppress.addAll(suppress);
51+
final var visited = Collections.<Throwable>newSetFromMap(new IdentityHashMap<>());
52+
while (cause != null && visited.add(cause)) {
53+
final var causeMessage = getAnonymizedMessage(cause);
54+
final var header = causeMessage != null
55+
? "Caused by: " + cause.getClass().getName() + ": " + causeMessage
56+
: "Caused by: " + cause.getClass().getName();
57+
stacktrace.add(header);
58+
59+
final var causeElements = cause.getStackTrace();
60+
final var causeStack = collapseStackTrace(causeElements);
61+
final var causeList = new ArrayList<>(causeStack);
62+
causeList.removeAll(toSuppress);
63+
final var causeTraces = Math.min(causeList.size(), STACK_TRACE_LIMIT);
64+
populateTraces(causeTraces, causeList, causeElements, stacktrace);
65+
66+
cause = cause.getCause();
67+
}
68+
}
4669

70+
private static void populateTraces(final int traces, final List<String> list, final StackTraceElement[] elements, final JsonArray stacktrace) {
4771
for (var i = 0; i < traces; i++) {
4872
final var string = list.get(i);
49-
if (string.length() <= STACK_TRACE_LENGTH) stacktrace.add(string);
50-
else stacktrace.add(string.substring(0, STACK_TRACE_LENGTH) + "...");
73+
if (string.length() <= STACK_TRACE_LENGTH) stacktrace.add(" at " + string);
74+
else stacktrace.add(" at " + string.substring(0, STACK_TRACE_LENGTH) + "...");
5175
}
5276
if (traces > 0 && traces < list.size()) {
53-
stacktrace.add("and " + (list.size() - traces) + " more...");
77+
stacktrace.add(" ... " + (list.size() - traces) + " more");
5478
} else {
5579
final var i = elements.length - list.size();
56-
if (i > 0) stacktrace.add("Omitted " + i + " duplicate stack frame" + (i == 1 ? "" : "s"));
80+
if (i > 0) stacktrace.add(" ... " + i + " more");
5781
}
58-
return stacktrace;
5982
}
6083

6184
private static List<String> collapseStackTrace(final StackTraceElement[] trace) {
@@ -164,29 +187,29 @@ private static boolean isSameClassLoader(final ClassLoader classLoader, final Cl
164187
return loader == current;
165188
}
166189

167-
private static final String IPV4_PATTERN =
168-
"\\b(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\b";
169-
private static final String IPV6_PATTERN =
170-
"(?i)\\b([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}\\b|" + // Full form
171-
"(?i)\\b([0-9a-f]{1,4}:){1,7}:\\b|" + // Trailing ::
172-
"(?i)\\b([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}\\b|" + // :: in middle (1 group after)
173-
"(?i)\\b([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}\\b|" + // :: in middle (2 groups after)
174-
"(?i)\\b([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}\\b|" + // :: in middle (3 groups after)
175-
"(?i)\\b([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\\b|" + // :: in middle (4 groups after)
176-
"(?i)\\b([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\\b|" + // :: in middle (5 groups after)
177-
"(?i)\\b[0-9a-f]{1,4}:(:[0-9a-f]{1,4}){1,6}\\b|" + // :: in middle (6 groups after)
178-
"(?i)\\b:(:[0-9a-f]{1,4}){1,7}\\b|" + // Leading ::
179-
"(?i)\\b::([0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4}\\b|" + // :: at start
180-
"(?i)\\b::\\b"; // Just ::
181-
private static final String USER_HOME_PATH_PATTERN =
182-
"(/home/)[^/\\s]+" + // Linux: /home/username
183-
"|(/Users/)[^/\\s]+" + // macOS: /Users/username
184-
"|((?i)[A-Z]:\\\\Users\\\\)[^\\\\\\s]+"; // Windows: A-Z:\\Users\\username
190+
private static final Pattern IPV4_PATTERN = Pattern.compile(
191+
"\\b(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\b");
192+
private static final Pattern IPV6_PATTERN = Pattern.compile(
193+
"(?i)\\b([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}\\b|" + // Full form
194+
"(?i)\\b([0-9a-f]{1,4}:){1,7}:\\b|" + // Trailing ::
195+
"(?i)\\b([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}\\b|" + // :: in middle (1 group after)
196+
"(?i)\\b([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}\\b|" + // :: in middle (2 groups after)
197+
"(?i)\\b([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}\\b|" + // :: in middle (3 groups after)
198+
"(?i)\\b([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\\b|" + // :: in middle (4 groups after)
199+
"(?i)\\b([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\\b|" + // :: in middle (5 groups after)
200+
"(?i)\\b[0-9a-f]{1,4}:(:[0-9a-f]{1,4}){1,6}\\b|" + // :: in middle (6 groups after)
201+
"(?i)\\b:(:[0-9a-f]{1,4}){1,7}\\b|" + // Leading ::
202+
"(?i)\\b::([0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4}\\b|" + // :: at start
203+
"(?i)\\b::\\b"); // Just ::
204+
private static final Pattern USER_HOME_PATH_PATTERN = Pattern.compile(
205+
"(/home/)[^/\\s]+" + // Linux: /home/username
206+
"|(/Users/)[^/\\s]+" + // macOS: /Users/username
207+
"|((?i)[A-Z]:\\\\Users\\\\)[^\\\\\\s]+"); // Windows: A-Z:\\Users\\username
185208

186209
private static String anonymize(String message) {
187-
message = message.replaceAll(IPV4_PATTERN, "[IP hidden]");
188-
message = message.replaceAll(IPV6_PATTERN, "[IP hidden]");
189-
message = message.replaceAll(USER_HOME_PATH_PATTERN, "$1$2$3[username hidden]");
210+
message = IPV4_PATTERN.matcher(message).replaceAll("[IP hidden]");
211+
message = IPV6_PATTERN.matcher(message).replaceAll("[IP hidden]");
212+
message = USER_HOME_PATH_PATTERN.matcher(message).replaceAll("$1$2$3[username hidden]");
190213
final var username = System.getProperty("user.name");
191214
if (username != null) message = message.replace(username, "[username hidden]");
192215
return message;

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=0.17.0
1+
version=0.17.1

0 commit comments

Comments
 (0)