2015-06-08 49 views
8

我使用ExecutorService异步发送邮件的Java堆栈跟踪,所以有一类:保留跨线程

class Mailer implements Runnable { ... 

,处理发送。这被逮住被记录,为(匿名)如任何异常:

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] 

不是非常有帮助 - 我需要看到的是调用导致这一切的ExecutorService堆栈跟踪。我的解决方案是创建一个空的Exception并把它传递到Mailer

executorService.submit(new Mailer(foo, bar, new Exception())); 
... 
// constructor 
public Mailer(foo, bar, Exception cause) { this.cause = cause; ... 

现在在例外的情况下,我想记录本身的问题,并从其他线程的原因:

try { 
    // send the mail... 
} catch (Throwable t) { 
    LOG.error("Stuff went wrong", t); 
    LOG.error("This guy invoked us", cause); 
} 

这很好,但会产生两个日志。我想将tcause组合成一个例外并记录下来。在我看来,t导致cause,所以使用cause.initCause(t)应该是正确的方法。和作品。我看到一个完整的堆栈跟踪:从通话始发地到达AddressException

问题是,initCause()只能工作一次,然后崩溃。 问题1:我可以克隆Exception?我会克隆cause并每次初始化它与t

我试过t.initCause(cause),但是马上崩溃了。

问题2:是否有另一种巧妙结合这两个例外的巧妙方法?或者为了记录的目的只保留另一个线程上下文中的一个线程上下文?

+1

如何从异常中获取堆栈跟踪并使用它? – RealSkeptic

+0

我想尽可能地利用Log4j中的日志记录机制。操纵栈跟踪似乎是一件破解事情的黑客。 – vektor

+0

“问题是,initCause()只能工作一次,然后崩溃。” - 它如何崩溃? – user3707125

回答

9

继我的评论,这实际上是我的想法。请注意,我目前没有办法测试它。

您从父母线程传递的是New Exception().getStackTrace()。或者更好的是,@Radiodef评论说,Thread.currentThread().getStackTrace()。所以它基本上是一个StackTraceElement[]阵列。现在

,你可以有这样的:你的catch子句中

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); 
    } 
} 

现在你可以这样做:

catch (Throwable cause) { 
    LOG("This happened", new CrossThreadException(cause, originalStackTrace)); 
} 

,这将给你两个堆栈跟踪之间的边界。

+1

您也可以传递'Thread.currentThread()。getStackTrace()'而不是'new Exception()。getStackTrace()'。 – Radiodef

+0

@Radiodef谢谢,补充说,答案。 – RealSkeptic

+0

你甚至不需要将此作为参数传递。由于构造函数是在父线程上调用的,所以你可以在构造函数中设置stacktrace字段:'private StackTraceElement [] originalStackTrace = Thread.currentThread()。getStackTrace();'作为字段初始值 – Falco

0

我倾向于做的是让你调用的线程保存异常而不是抛出它。

然后,启动它的主线程可以查询它是否发生异常。如果有的话,那么它可以得到例外,并以此为原因抛出一个例外。

我不认为你可以克隆异常。

作为一般规则,这种修补异常的方式是坏消息,并且表明在代码中的错误处理的一般方法可能存在一些问题。

这很难做到,因为你并不是真的想要这样做。

+0

我相信我的问题是相当普遍的:沿着跨越线程边界的堆栈跟踪。有没有一个完善的解决方案呢? – vektor

+0

我同意。这个问题完全合法,而且很常见。我的方法是我以前用来解决问题的一种方法,并且是一种行之有效的方法。我确信还有其他人,但我强烈的观点是,试图通过破解例外来解决它是一种糟糕的代码异味。 –

3

您可以使用从提交调用返回的Future<v>对象,然后调用get()方法,如果在执行任务期间发生任何异常,它将被重新抛出。

另一种选择是为线程工厂定制默认异常处理程序,该线程工厂为ExecutorService创建线程。请参阅Thread.UncaughtExceptionHandler

0

您可以使用Google Guava库中的ListableFuture类。见https://code.google.com/p/guava-libraries/wiki/ListenableFutureExplained

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); 
ListenableFuture<Explosion> explosion = service.submit(new Callable<Explosion>() { 
    public Explosion call() { 
    return pushBigRedButton(); 
    } 
}); 
Futures.addCallback(explosion, new FutureCallback<Explosion>() { 
    // we want this handler to run immediately after we push the big red button! 
    public void onSuccess(Explosion explosion) { 
    walkAwayFrom(explosion); 
    } 
    public void onFailure(Throwable thrown) { 
    battleArchNemesis(); // escaped the explosion! 
    } 
}); 
+0

我仍然需要以'保存两个堆栈的方式在'onFailure'中抛出'thrown',我该怎么做? – vektor