我希望能够在日志中显示引起错误的人:
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
可以做到吗?
有什么主意吗?
谢谢。
答案 0 :(得分:2)
我认为没有一个真正好的解决方案。我认为您必须选择以下选项之一,每个选项都有其自身的缺点:
以栈顶printError
开头。我不确定这是否真的是一个糟糕的选择。顺便说一句,您可以像在Thread.currentThread.getStackTrace()
Thread.getStackTrace()
调用
添加显式参数printError
尝试破解返回的StackTraceElement[]
,以删除一些您不想被看到的顶级元素。注意这可能并不容易,因为潜在的编译器可能会内联一些方法调用。
创建一个自定义的基于宏的解决方案,例如ScalaTest的Position。我认为如果不使用宏就无法做到这一点。另一方面,宏不应该复杂,因为宏Context
具有enclosingPosition
属性。但是请注意,宏是Scala中最不稳定的部分之一,因此它可能不是很适合未来。
直接使用ScalaTest的Position
,后者已移至可以在生产代码中使用的独立项目Scalactic。这类似于#4,但您将解决方案的维护负担转移给了ScalaTest背后的人。
#4和#5的缺点在于,您只能获得badMethod
的源代码行位置,而不是整个堆栈跟踪,这对于调试问题而言是恕我直言的。
Thread.currentThread.getStackTrace()
而不是Context.enclosingPosition
上创建包装器值,并将其作为您的“位置”传递。如果您的项目中已经有宏并且可以使用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
类,这一切都将为您完成...但是我想您有理由。