All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.cli.runner.StackTracePrinter.scala Maven / Gradle / Ivy

There is a newer version: 1.5.1
Show newest version
package scala.cli.runner

import java.util.Locale

import scala.annotation.tailrec

final case class StackTracePrinter(
  loader: ClassLoader = Thread.currentThread().getContextClassLoader,
  callerClass: Option[String] = None,
  cutInvoke: Boolean = false,
  colored: Boolean = StackTracePrinter.coloredStackTraces
) {

  private def bold  = if (colored) Console.BOLD else ""
  private def gray  = if (colored) "\u001b[90m" else ""
  private def reset = if (colored) Console.RESET else ""

  private def truncateStackTrace(ex: Throwable): Unit = {
    val noCallerStackTrace = callerClass match {
      case None => ex.getStackTrace
      case Some(caller) =>
        ex.getStackTrace
          .takeWhile(_.getClassName.stripSuffix("$") != caller.stripSuffix("$"))
    }
    val drop =
      if (cutInvoke)
        noCallerStackTrace
          .reverseIterator
          .takeWhile { elem =>
            def isJdkClass =
              elem.getClassName.startsWith("java.") ||
              elem.getClassName.startsWith("jdk.") ||
              elem.getClassName.startsWith("sun.")
            elem.getMethodName.startsWith("invoke") && isJdkClass
          }
          .length
      else
        0
    val truncated = noCallerStackTrace.dropRight(drop)
    if (truncated.length != ex.getStackTrace.length)
      ex.setStackTrace(truncated)
  }

  @tailrec
  private def printCause(
    ex: Throwable,
    causedStackTrace: Array[StackTraceElement],
    verbosity: Int
  ): Unit =
    if (ex != null) {
      truncateStackTrace(ex)
      System.err.println(s"Caused by: $ex")
      printStackTrace(ex.getStackTrace, causedStackTrace)
      printCause(ex.getCause, ex.getStackTrace, verbosity)
    }
  private def printStackTrace(trace: Array[StackTraceElement]): Unit =
    printStackTrace(trace, Array.empty)
  private def printStackTrace(
    trace: Array[StackTraceElement],
    causedStackTrace: Array[StackTraceElement]
  ): Unit = {
    val cut = causedStackTrace
      .reverseIterator
      .zip(trace.reverseIterator)
      .takeWhile { case (a, b) => a == b }
      .length
    for (elem <- trace.take(trace.length - cut)) {
      val clsName     = elem.getClassName
      val resource    = clsName.replace('.', '/') + ".class"
      val resourceUrl = loader.getResource(resource)
      val highlight   = resourceUrl != null && resourceUrl.getProtocol == "file"
      if (highlight) {
        val location =
          if (elem.isNativeMethod) "Native Method"
          else if (elem.getFileName == null) "Unknown Source"
          else if (elem.getLineNumber >= 0) s"${elem.getFileName}:${elem.getLineNumber}"
          else elem.getFileName
        val str = s"$bold${elem.getClassName}.${elem.getMethodName}$reset" +
          s"$gray($reset$location$gray)$reset"
        System.err.println(s"\t${gray}at$reset $str")
      }
      else
        System.err.println(s"\t${gray}at $elem")
    }
    if (cut > 0)
      System.err.println(s"\t$gray... $cut more$reset")
  }

  def printException(ex: Throwable, verbosity: Int): Unit = {
    val q          = "\""
    val threadName = Thread.currentThread().getName
    truncateStackTrace(ex)
    System.err.println(s"Exception in thread $q$threadName$q $ex")
    printStackTrace(ex.getStackTrace)
    printCause(ex.getCause, ex.getStackTrace, verbosity)
  }
}

object StackTracePrinter {

  lazy val coloredStackTraces: Boolean =
    sys.props.get("scala.colored-stack-traces")
      .map(_.toLowerCase(Locale.ROOT))
      .forall(_ == "true")

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy