Skip to content

Commit 0bee3a1

Browse files
committed
Improve stack trace collapsing and message trimming
1 parent 9579b7c commit 0bee3a1

File tree

2 files changed

+109
-8
lines changed

2 files changed

+109
-8
lines changed

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

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
import java.util.function.BiConsumer;
1818

1919
final class SimpleErrorTracker implements ErrorTracker {
20+
private final int messageLength = Math.min(1000, Integer.getInteger("faststats.message-length", 500));
21+
private final int stackTraceLength = Math.min(500, Integer.getInteger("faststats.stack-trace-length", 300));
2022
private final int stackTraceLimit = Math.min(50, Integer.getInteger("faststats.stack-trace-limit", 15));
23+
2124
private final Map<String, Integer> collected = new ConcurrentHashMap<>();
2225
private final Map<String, JsonObject> reports = new ConcurrentHashMap<>();
2326

@@ -48,8 +51,10 @@ private String hash(JsonObject report) {
4851
return Long.toHexString(hash[0]) + Long.toHexString(hash[1]);
4952
}
5053

51-
private JsonObject compile(Throwable error, @Nullable List<StackTraceElement> suppress) {
52-
var stack = Arrays.asList(error.getStackTrace());
54+
// todo: cleanup this absolute mess
55+
private JsonObject compile(Throwable error, @Nullable List<String> suppress) {
56+
var elements = error.getStackTrace();
57+
var stack = collapseStackTrace(elements);
5358
var list = new ArrayList<>(stack);
5459
if (suppress != null) list.removeAll(suppress);
5560

@@ -59,18 +64,22 @@ private JsonObject compile(Throwable error, @Nullable List<StackTraceElement> su
5964
var stacktrace = new JsonArray(traces);
6065

6166
for (var i = 0; i < traces; i++) {
62-
stacktrace.add(list.get(i).toString());
67+
var string = list.get(i);
68+
if (string.length() <= stackTraceLength) stacktrace.add(string);
69+
else stacktrace.add(string.substring(0, stackTraceLength) + "...");
6370
}
6471
if (traces > 0 && traces < list.size()) {
6572
stacktrace.add("and " + (list.size() - traces) + " more...");
6673
} else {
67-
var i = stack.size() - list.size();
74+
var i = elements.length - list.size();
6875
if (i > 0) stacktrace.add("Omitted " + i + " duplicate stack frame" + (i == 1 ? "" : "s"));
6976
}
7077

7178
report.addProperty("error", error.getClass().getName());
72-
if (error.getMessage() != null) {
73-
report.addProperty("message", anonymize(error.getMessage()));
79+
var message = error.getMessage();
80+
if (message != null) {
81+
if (message.length() > messageLength) message = message.substring(0, messageLength) + "...";
82+
report.addProperty("message", anonymize(message));
7483
}
7584
if (!stacktrace.isEmpty()) {
7685
report.add("stack", stacktrace);
@@ -84,6 +93,58 @@ private JsonObject compile(Throwable error, @Nullable List<StackTraceElement> su
8493
return report;
8594
}
8695

96+
public static List<String> collapseStackTrace(StackTraceElement[] trace) {
97+
var lines = Arrays.stream(trace)
98+
.map(StackTraceElement::toString)
99+
.toList();
100+
101+
return collapseRepeatingPattern(lines);
102+
}
103+
104+
public static List<String> collapseRepeatingPattern(List<String> lines) {
105+
// First, collapse consecutive duplicate lines
106+
var deduplicated = collapseConsecutiveDuplicates(lines);
107+
108+
var n = deduplicated.size();
109+
110+
for (var cycleLen = 1; cycleLen <= n / 2; cycleLen++) {
111+
var isPattern = true;
112+
var repetitions = 0;
113+
114+
for (var i = 0; i < n; i++) {
115+
if (!deduplicated.get(i).equals(deduplicated.get(i % cycleLen))) {
116+
isPattern = false;
117+
break;
118+
}
119+
if (i > 0 && i % cycleLen == 0) {
120+
repetitions++;
121+
}
122+
}
123+
124+
if (isPattern && repetitions >= 2) {
125+
return deduplicated.subList(0, cycleLen);
126+
}
127+
}
128+
129+
return deduplicated;
130+
}
131+
132+
private static List<String> collapseConsecutiveDuplicates(List<String> lines) {
133+
if (lines.isEmpty()) return lines;
134+
135+
var result = new ArrayList<String>();
136+
String previous = null;
137+
138+
for (var line : lines) {
139+
if (!line.equals(previous)) {
140+
result.add(line);
141+
previous = line;
142+
}
143+
}
144+
145+
return result;
146+
}
147+
87148
private static final String IPV4_PATTERN =
88149
"\\b(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\b";
89150
private static final String IPV6_PATTERN =

core/src/test/java/dev/faststats/ErrorTrackerTest.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,36 @@ public class ErrorTrackerTest {
1010
// todo: add redaction tests
1111
// todo: add nesting tests
1212
// todo: add duplicate tests
13-
13+
1414
@Test
1515
// todo: fix this mess
1616
public void testCompile() throws InterruptedException {
1717
var tracker = ErrorTracker.contextUnaware();
1818
tracker.attachErrorContext(null);
19+
20+
try {
21+
roundAndRound(10);
22+
} catch (Throwable t) {
23+
tracker.trackError(t);
24+
}
25+
try {
26+
recursiveError();
27+
} catch (Throwable t) {
28+
tracker.trackError("↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ↓→ħſðđſ→ðđ””ſ→ʒðđ↓ʒ”ſðđʒ");
29+
tracker.trackError(t);
30+
}
31+
try {
32+
aroundAndAround();
33+
} catch (Throwable t) {
34+
tracker.trackError(t);
35+
return;
36+
}
37+
1938
tracker.trackError("Test error");
2039
var nestedError = new RuntimeException("Nested error");
2140
var error = new RuntimeException(null, nestedError);
2241
tracker.trackError(error);
23-
42+
2443
tracker.trackError("hello my name is david");
2544
tracker.trackError("/home/MyName/Documents/MyFile.txt");
2645
tracker.trackError("C:\\Users\\MyName\\AppData\\Local\\Temp");
@@ -41,4 +60,25 @@ public void testCompile() throws InterruptedException {
4160

4261
tracker.trackError("Test error");
4362
}
63+
64+
public void recursiveError() throws StackOverflowError {
65+
goRoundAndRound();
66+
}
67+
68+
public void goRoundAndRound() throws StackOverflowError {
69+
andRoundAndRound();
70+
}
71+
72+
public void andRoundAndRound() throws StackOverflowError {
73+
goRoundAndRound();
74+
}
75+
76+
public void aroundAndAround() throws StackOverflowError {
77+
aroundAndAround();
78+
}
79+
80+
public void roundAndRound(int i) throws RuntimeException {
81+
if (i <= 0) throw new RuntimeException("out of stack");
82+
roundAndRound(i - 1);
83+
}
4484
}

0 commit comments

Comments
 (0)