I am using ExecutorService
to send mails asynchronously, so there is a class:
class Mailer implements Runnable { ...
That handles the sending. Any exception that gets caught is logged, for (anonymized) example:
javax.mail.internet.AddressException: foo is bar at javax.mail.internet.InternetAddress.checkAddress(InternetAddress.java:1213) ~[mail.jar:1.4.5] at javax.mail.internet.InternetAddress.parse(InternetAddress.java:1091) ~[mail.jar:1.4.5] at javax.mail.internet.InternetAddress.parse(InternetAddress.java:633) ~[mail.jar:1.4.5] at javax.mail.internet.InternetAddress.parse(InternetAddress.java:610) ~[mail.jar:1.4.5] at mycompany.Mailer.sendMail(Mailer.java:107) [Mailer.class:?] at mycompany.Mailer.run(Mailer.java:88) [Mailer.class:?] ... suppressed 5 lines at java.lang.Thread.run(Thread.java:680) [?:1.6.0_35]
Not very helpful – I need to see the stacktrace that invoked the ExecutorService
that caused all of this. My solution is to create an empty Exception
and pass it into Mailer
:
executorService.submit(new Mailer(foo, bar, new Exception())); ... // constructor public Mailer(foo, bar, Exception cause) { this.cause = cause; ...
And now in the case of exception I want to log the problem itself and its cause from the other thread:
try { // send the mail... } catch (Throwable t) { LOG.error("Stuff went wrong", t); LOG.error("This guy invoked us", cause); }
This works great but produces two logs. I want to combine t
and cause
into a single exception and log that one. In my opinion, t
caused cause
, so using cause.initCause(t)
should be the right way. And works. I see a full stack trace: from where the call originated all the way up to the AddressException
.
Problem is, initCause()
works only once and then crashes. Question 1: can I clone Exception
? I’d clone cause
and init it with t
every time.
I tried t.initCause(cause)
, but that crashes right away.
Question 2: is there another smart way to combine these 2 exceptions? Or just keep one thread context in the other thread context for logging purposes?
Advertisement
Answer
Following my comment, this is actually what I had in mind. Mind you, I don’t have a way to test it at the moment.
What you pass from your parent thread is New Exception().getStackTrace()
. Or better yet, as @Radiodef commented, Thread.currentThread().getStackTrace()
. So it’s basically a StackTraceElement[]
array.
Now, you can have something like:
public class CrossThreadException extends Exception { public CrossThreadException( Throwable cause, StackTraceElement[] originalStackTrace ) { // No message, given cause, no supression, stack trace writable super( null, cause, false, true ); setStackTrace( originalStackTrace ); } }
Now in your catch clause you can do something like:
catch ( Throwable cause ) { LOG( "This happened", new CrossThreadException( cause, originalStackTrace ) ); }
Which will give you a boundary between the two stack traces.