如何在 Scala ExecutionContext 之间传输堆栈跟踪?



我已经围绕ScalaExecutionContext编写了一个小型实用程序包装器,以实现跨Future实例的MDC上下文传输。它按预期工作,但一个不希望的副作用是我们现在似乎没有得到跨越Future的堆栈跟踪。如何确保堆栈跟踪与 MDC 一起传播?

这是我的代码供参考:

import org.slf4j.MDC
import scala.concurrent.ExecutionContext
import scala.jdk.CollectionConverters._

object MdcOps {
implicit class ExecutionContextExt(value: ExecutionContext) {
def withMdc: ExecutionContext = new MdcExecutionContext(value)
}
def withMdc[A](mdc: Map[String, Any], replace: Boolean)(doIt: => A): A = {
val currentMdcContext = getMdc
val newMdc = if(replace) mdc else mdc ++ currentMdcContext
try { setMdc(newMdc); doIt }
finally { setMdc(currentMdcContext) }
}
def setMdc(mdc: Map[String, Any]): Unit = {
if(mdc.isEmpty) {
MDC.clear()
} else
MDC.setContextMap(mdc.view.mapValues(_.toString).toMap.asJava)
}
def getMdc: Map[String, String] = Option(MDC.getCopyOfContextMap).map(_.asScala.toMap).getOrElse(Map.empty)
}
class MdcExecutionContext(underlying: ExecutionContext, context: Map[String, String] = Map.empty) extends ExecutionContext {
override def prepare(): ExecutionContext = new MdcExecutionContext(underlying, MdcOps.getMdc)
override def execute(runnable: Runnable): Unit = underlying.execute { () =>
MdcOps.withMdc(context, replace = true)(runnable.run())
}
override def reportFailure(t: Throwable): Unit = underlying.reportFailure(t)
}

一般来说,这很难。ZIO 实现了它 - 请参阅:从幻灯片 53 https://www.slideshare.net/jdegoes/error-management-future-vs-zio 查看结果 - 但是创建堆栈并传递它会导致性能损失 x2.4。

由于您无法更改 API 以例如使用宏(例如使用 sourcode 库(在编译时生成堆栈跟踪,因此在每个.map/.flatMap上,您必须创建一个堆栈跟踪,从中删除不相关的帧,可能将其与以前上下文中的帧组合并在 MDC 中设置它。撇开技术细节不谈 - 这很难正确且重量大,更像是整个库的材料,而不是简单的实用程序。如果可以便宜地完成,这将是一个内置。

如果您对它非常感兴趣,请选择 ZIO 或分析它是如何实现它的 - 据我所知,它需要付出很多努力并将此功能构建到库中,即使这样也会导致性能下降。我只能想象使用 JVM 堆栈跟踪并传递它们的惩罚会更大。

最新更新