4

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

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

如果有人在返回的未来不使用 onFailure 或类似的东西,我想要一个“最后的手段”异常记录代码。

4

3 回答 3

3

我遇到了类似的问题,在实际结果不相关且因此未明确处理的情况下,期货会默默地失败。从ExecutionContext我最初的文档中假设该reportFailure方法用于报告Future. 这显然是错误的 - 所以这是我想出的记录异常(即使是映射或以其他方式派生的)期货的方法:

  • 一个LoggedFuture委托给 aFutureonFailure记录异常的类,类似于@LimbSoups 答案
  • 对于这样的方法,也会map返回一个新的Future产量 aLoggedFuture
  • 使用 aPromise作为在级联之间共享的某种失败事件LoggedFutures以仅记录一次异常,即使由于传播而多次应用 onFailure 回调
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定义了一个隐式转换为(如上),因此我可以轻松地使用future.asLogged.

于 2014-09-01T10:04:30.857 回答
1

使用以下隐式类,您可以轻松记录期货的失败,同时避免以下样板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
      }
  }

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

 import com.typesafe.scalalogging._
 import scala.language.postfixOps

 class MyClass extends LazyLogging {
   def f = Future {
     // do something that fails
     throw new Exception("this future fails")
   } withFailureLogging(logger, "some error message")

   def g = Future {
     // do something that fails
     throw new Exception("this future fails")
   } withPrintStackTraceOnFailure
 }
于 2017-10-21T23:55:48.747 回答
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

如您所见,第一个回调打印错误消息,第二个忽略 throws 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.
 */

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

于 2014-06-27T13:47:08.850 回答