2020import com .palantir .logsafe .SafeLoggable ;
2121import java .util .ArrayList ;
2222import java .util .Collections ;
23+ import java .util .IdentityHashMap ;
2324import java .util .List ;
25+ import java .util .Set ;
2426import java .util .UUID ;
2527import javax .annotation .Nullable ;
2628
@@ -30,7 +32,7 @@ public final class ServiceException extends RuntimeException implements SafeLogg
3032 private final ErrorType errorType ;
3133 private final List <Arg <?>> args ; // unmodifiable
3234
33- private final String errorInstanceId = UUID . randomUUID (). toString () ;
35+ private final String errorInstanceId ;
3436 private final String unsafeMessage ;
3537 private final String noArgsMessage ;
3638
@@ -47,6 +49,7 @@ public ServiceException(ErrorType errorType, @Nullable Throwable cause, Arg<?>..
4749 // TODO(rfink): Memoize formatting?
4850 super (cause );
4951
52+ this .errorInstanceId = generateErrorInstanceId (cause );
5053 this .errorType = errorType ;
5154 // Note that instantiators cannot mutate List<> args since it comes through copyToList in all code paths.
5255 this .args = copyToUnmodifiableList (args );
@@ -122,4 +125,29 @@ private static String renderUnsafeMessage(ErrorType errorType, Arg<?>... args) {
122125 private static String renderNoArgsMessage (ErrorType errorType ) {
123126 return String .format ("ServiceException: %s (%s)" , errorType .code (), errorType .name ());
124127 }
128+
129+ /**
130+ * Finds the errorInstanceId of the most recent cause if present, otherwise generates a new random identifier.
131+ * Note that this only searches {@link Throwable#getCause() causal exceptions}, not
132+ * {@link Throwable#getSuppressed() suppressed causes}.
133+ */
134+ private static String generateErrorInstanceId (@ Nullable Throwable cause ) {
135+ return generateErrorInstanceId (cause , Collections .newSetFromMap (new IdentityHashMap <>()));
136+ }
137+
138+ private static String generateErrorInstanceId (
139+ @ Nullable Throwable cause ,
140+ // Guard against cause cycles, see Throwable.printStackTrace(PrintStreamOrWriter)
141+ Set <Throwable > dejaVu ) {
142+ if (cause == null || !dejaVu .add (cause )) {
143+ return UUID .randomUUID ().toString ();
144+ }
145+ if (cause instanceof ServiceException ) {
146+ return ((ServiceException ) cause ).getErrorInstanceId ();
147+ }
148+ if (cause instanceof RemoteException ) {
149+ return ((RemoteException ) cause ).getError ().errorInstanceId ();
150+ }
151+ return generateErrorInstanceId (cause .getCause (), dejaVu );
152+ }
125153}
0 commit comments