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