Scala Futures:每个新创建或映射的异常的默认错误处理程序

时间:2014-06-27 13:30:16

标签: scala concurrent.futures

是否有可能始终使用默认的onFailure处理程序创建Future {...}块? (例如,将堆栈跟踪写入控制台)?此处理程序还应自动附加到映射的期货(通过在已有默认故障处理程序的未来调用map创建的新期货)

有关详细信息,请参阅此处的问题: Scala on Android with scala.concurrent.Future do not report exception on system err/out

我希望有一个"最后的手段"异常日志记录代码,如果有人不使用onFailure或类似于返回的未来。

3 个答案:

答案 0 :(得分:3)

我遇到了类似的问题,在实际结果不相关且因此没有明确处理的情况下,期货会默默地失败。在ExecutionContext中的文档中,我最初假设reportFailure方法可以针对Future中的任何失败进行报告。这显然是错误的 - 所以这就是我提出的记录异常(甚至是映射或其他衍生的)期货的方法:

  • 委派给LoggedFutureFuture的{​​{1}}类记录与@LimbSoups回答类似的异常
  • 对于onFailure这样的方法,返回新的map也会产生Future
  • 使用LoggedFuture作为某种类型的失败事件,在级联Promise之间共享以仅记录一次异常,即使由于传播而多次应用onFailure回调
LoggedFutures

此外,我还隐式转换为object LoggedFuture { def apply[T](future: Future[T])(implicit ec: ExecutionContext): Future[T] = { if (future.isInstanceOf[LoggedFuture[T]]) { // don't augment to prevent double logging future.asInstanceOf[LoggedFuture[T]] } else { val failEvent = promise[Unit] failEvent.future.onFailure { // do your actual logging here case t => t.printStackTrace() } new LoggedFuture(future, failEvent, ec) } } } private class LoggedFuture[T](future: Future[T], failEvent: Promise[Unit], ec: ExecutionContext) extends Future[T] { // fire "log event" on failure future.onFailure { // complete log event promise // the promise is used to log the error only once, even if the // future is mapped and thus further callbacks attached case t => failEvent.tryComplete(Failure(t)) } (ec) // delegate methods override def ready(atMost: Duration)(implicit permit: CanAwait): this.type = { future.ready(atMost) this } override def result(atMost: scala.concurrent.duration.Duration)(implicit permit: CanAwait): T = future.result(atMost) override def isCompleted: Boolean = future.isCompleted override def onComplete[U](func: scala.util.Try[T] => U)(implicit executor: ExecutionContext): Unit = future.onComplete(func) override def value: Option[Try[T]] = future.value // propagate LoggedFuture (and shared log event) whenever a new future is returned override def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = new LoggedFuture(super.map(f), failEvent, executor) override def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = new LoggedFuture(super.transform(s, f), failEvent, executor) override def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = new LoggedFuture(super.flatMap(f), failEvent, executor) override def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = new LoggedFuture(super.recover(pf), failEvent, executor) override def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = new LoggedFuture(super.recoverWith(pf), failEvent, executor) override def zip[U](that: Future[U]): Future[(T, U)] = new LoggedFuture(super.zip(that), failEvent, ec) override def fallbackTo[U >: T](that: Future[U]): Future[U] = new LoggedFuture(super.fallbackTo(that), failEvent, ec) override def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = new LoggedFuture(super.andThen(pf), failEvent, executor) } class RichFuture[T](future: Future[T]) { def asLogged(implicit ec: ExecutionContext): Future[T] = LoggedFuture(future) } (如上所述),因此我可以使用RichFuture之类的调用轻松转换现有的期货。

答案 1 :(得分:1)

使用以下隐式类,您可以轻松记录期货的失败,同时避免使用htmlFile .Replace("Resources/Image.png", string.concat("data:image/png;base64," + imageData))的样板:

recover

您可以使用它,如下所示:

  import com.typesafe.scalalogging.Logger

  implicit class LoggingFuture[+T](val f: Future[T]) extends AnyVal {
    def withFailureLogging(l: Logger, message: String): Future[T] = f recover {
      case e =>
        l.error(s"$message: $e")
        throw e
    }

    def withPrintStackTraceOnFailure: Future[T] = f recover {
      case e =>
        e.printStackTrace()
        throw e
      }
  }

答案 2 :(得分:0)

就像我评论的扩展名一样:

你没有明白这一点,没有必要为每个映射的未来做出失败回调,因为在失败的情况下,地图不会进行任何计算,只是进一步传递现有的失败。因此,如果你将更多的计算链接到失败的计算,那么所有新的回调都不会被调用。

考虑这个例子:

case class TestError(msg) extends Throwable(msg)

val f1 = Future { 10 / 0 }
val f2 = f1 map { x => throw new TestError("Hello"); x + 10 }
f1.onFailure {
  case error => println(error.getMessage)
}
f2.onFailure {
  case er: TestError => println("TestError")
  case _ => println("Number error")
}

// Exiting paste mode, now interpreting.

/ by zero
Number error
f1: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@54659bf8
f2: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@5ae2e211

如您所见,第一个回调打印错误消息,第二个回调忽略抛出TestError。这是因为你没有应用地图功能。如果你看一下map的评论:

/** Creates a new future by applying a function to the successful result of
 *  this future. If this future is completed with an exception then the new
 *  future will also contain this exception.
 */

所以没有必要进一步附加新的失败回调,因为任何进一步的未来都会简单地包含前一个的结果,因为你已经定义了一个回调。