1010import java .util .IdentityHashMap ;
1111import java .util .List ;
1212import java .util .Set ;
13+ import java .util .regex .Pattern ;
1314
1415final 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 ;
0 commit comments