如何从调用方获取stackTrace

时间:2018-12-28 02:32:32

标签: scala functional-programming

我希望能够在日志中显示引起错误的人:

def printError(errorMessage: String): Error = {
   new Error(errorMessage, /* create stack trace */)
}

def badMethod(...): IO[Error, ...] = {
    ...
    IO.fail(printError("This is bad"))
}

我想要printError内有东西,这表明错误是在badMethod中创建的。

我尝试使用Thread.getAllStackTraces.get(Thread.currentThread()).toList,但是在printError中得到了栈顶。

在单元测试中,有一个Position类可以做到这一点,但是我不知道如何在非测试上下文中使用它。

也许有些implicit可以做到吗?

有什么主意吗?

谢谢。

2 个答案:

答案 0 :(得分:2)

我认为没有一个真正好的解决方案。我认为您必须选择以下选项之一,每个选项都有其自身的缺点:

  1. 以栈顶printError开头。我不确定这是否真的是一个糟糕的选择。顺便说一句,您可以像在Thread.currentThread.getStackTrace()

  2. 中那样使用更简单的Thread.getStackTrace()调用
  3. 添加显式参数printError

  4. 尝试破解返回的StackTraceElement[],以删除一些您不想被看到的顶级元素。注意这可能并不容易,因为潜在的编译器可能会内联一些方法调用。

  5. 创建一个自定义的基于宏的解决方案,例如ScalaTest的Position。我认为如果不使用宏就无法做到这一点。另一方面,宏不应该复杂,因为宏Context具有enclosingPosition属性。但是请注意,宏是Scala中最不稳定的部分之一,因此它可能不是很适合未来。

  6. 直接使用ScalaTest的Position,后者已移至可以在生产代码中使用的独立项目Scalactic。这类似于#4,但您将解决方案的维护负担转移给了ScalaTest背后的人。

#4和#5的缺点在于,您只能获得badMethod的源代码行位置,而不是整个堆栈跟踪,这对于调试问题而言是恕我直言的。

  1. 合并#3和#4以创建一个宏,该宏将在Thread.currentThread.getStackTrace()而不是Context.enclosingPosition上创建包装器值,并将其作为您的“位置”传递。

更新#6

如果您的项目中已经有宏并且可以使用quasiquotes,那么实际上实现#6并不难。这是一个简单的PoC:

宏项目

package so.stacktrace
import scala.language.experimental.macros

class StackTraceWrapper(t: Throwable) {
  lazy val stackTrace: List[String] = t.getStackTrace().toList.map(_.toString)
}

object StackTraceWrapper {
  implicit def currentStackTrace: StackTraceWrapper = macro StackTraceWrapperMacro.buildCurrent
}


object StackTraceWrapperMacro {

  import scala.reflect.macros.Context

  def buildCurrent(context: Context) = {
    import context.universe._
    // note that package here should match the actual package!
    q"new so.stacktrace.StackTraceWrapper(new Throwable())"
  }
}

主要项目

object StackTraceUsage extends App {

  import so.stacktrace.StackTraceWrapper

  case class Error(msg: String, stackTrace: List[String])

  object Error {
    def apply(msg: String)(implicit stw: StackTraceWrapper): Error = new Error(msg, stw.stackTrace)
  }


  def badMethod(): Unit = {
    println(Error("Test error"))
  }

  def badMethodCaller():Unit = {
    badMethod()
  }

  badMethodCaller()
}

这对我有效,正如我期望的那样

答案 1 :(得分:1)

在调用程序中,无论您想开始堆栈跟踪的位置,创建一个Throwable

def badMethod(...): IO[Error, ...] = {
    ...
    val t = new Throwable
    IO.fail(printError("This is bad", t))
}

,然后将其传递给您的printError方法:

def printError(errorMessage: String, t: Throwable): Error = {
   new Error(errorMessage, t.getStackTrace.toList.map(_.toString))
}

我不知道您的Error类中的第二个参数是什么,但是我从您的问题中假定它想要一个List[String]。如果它是String,则可以致电t.getStackTraceString

也就是说,如果您仅使用Throwable而不是Error类,这一切都将为您完成...但是我想您有理由。