I have written a small utility wrapper around the Scala ExecutionContext
to enable transfer of MDC contexts across Future
instances. It works as expected but an undesired side-effect is that we now don't seem to get stack traces that go across Future
s. How can I ensure that the stack trace is propagated along with the MDC?
Here's my code for reference:
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)
}