是否有可能始终使用默认的onFailure处理程序创建Future {...}块? (例如,将堆栈跟踪写入控制台)?此处理程序还应自动附加到映射的期货(通过在已有默认故障处理程序的未来调用map创建的新期货)
有关详细信息,请参阅此处的问题: Scala on Android with scala.concurrent.Future do not report exception on system err/out
我希望有一个"最后的手段"异常日志记录代码,如果有人不使用onFailure或类似于返回的未来。
答案 0 :(得分:3)
我遇到了类似的问题,在实际结果不相关且因此没有明确处理的情况下,期货会默默地失败。在ExecutionContext
中的文档中,我最初假设reportFailure
方法可以针对Future
中的任何失败进行报告。这显然是错误的 - 所以这就是我提出的记录异常(甚至是映射或其他衍生的)期货的方法:
LoggedFuture
和Future
的{{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.
*/
所以没有必要进一步附加新的失败回调,因为任何进一步的未来都会简单地包含前一个的结果,因为你已经定义了一个回调。