Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preserve Java stack trace across threads

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?

like image 417
vektor Avatar asked Jun 08 '15 12:06

vektor


People also ask

How do you store stack trace in a string?

Example: Convert stack trace to a stringIn the catch block, we use StringWriter and PrintWriter to print any given output to a string. We then print the stack trace using printStackTrace() method of the exception and write it in the writer. Then, we simply convert it to string using toString() method.

Should you log stack trace?

Logging the stack traces of runtime exceptions assists developers in diagnosing runtime failures. However, unnecessary logging of exception stack traces can have many negative impacts such as polluting log files.

What is getStackTrace in Java?

getStackTrace() method returns an array of stack trace elements, each representing one stack frame. The zeroth element of the array (assuming the array's length is non-zero) represents the top of the stack, which is the last method invocation in the sequence.

How can I print stack trace without exception?

Just use new Throwable(). printStackTrace() method and it will print complete stack trace from where a method is called, into the console. Main difference between using dumpStack() and printStackTrace() is first entry in Stack, In case of dumpStack() first entry is always java.


2 Answers

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.

like image 189
RealSkeptic Avatar answered Sep 22 '22 18:09

RealSkeptic


You can use the Future<v> object that returned from your submit invocation, then invoke the get() method, if any exception occured during the task execution it will be re thrown.

Another option is to customize the default exception handler for the thread factory that creates the threads for your ExecutorService. See for more details: Thread.UncaughtExceptionHandler

like image 44
Maxim Kirilov Avatar answered Sep 21 '22 18:09

Maxim Kirilov