Permalink
Browse files

Improve tree logging

- A more capable TreePrinter (formerly TreeDump) can mark nodes in the
  AST and trim it down to the smallest subtree that contains all marked
  nodes.

- Add SlickTreeException which can log an AST (or other tree) as part of
  the exception message.
  • Loading branch information...
szeiger committed Jun 15, 2015
1 parent ae7e4d8 commit 325f20022630e352668bfca170cdb1ab0d47db57
@@ -12,7 +12,7 @@ import slick.ast.{Library, FunctionSymbol, ColumnOption}
import slick.ast.Util._
import slick.ast.TypeUtil._
import slick.compiler.CompilerState
import slick.util.{TreeDump, SQLBuilder}
import slick.util.{TreePrinter, SQLBuilder}
import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeRef
import scala.annotation.StaticAnnotation
@@ -413,7 +413,7 @@ class SlickBackend( val driver: JdbcDriver, mapper:Mapper ) extends QueryableBac
}
protected[slick] def dump( queryable:BaseQueryable[_] ) = {
val (_,query) = this.toQuery(queryable)
TreeDump(query.node)
TreePrinter.default.print(query.node)
}
import scala.collection.generic.CanBuildFrom
import slick.jdbc.{PositionedParameters, PositionedResult}
@@ -3,7 +3,7 @@ package slick.benchmark
import slick.ast._
import slick.jdbc._
import slick.relational._
import slick.util.TreeDump
import slick.util.TreePrinter
import com.typesafe.slick.testkit.util.DelegateResultSet
@deprecated("Using deprecated .simple API", "3.0")
@@ -55,7 +55,7 @@ object UnboxedBenchmark extends App {
def runTest(n: Node) {
val rsm = driver.queryCompiler.run(n).tree
TreeDump(rsm)
TreePrinter.default.print(rsm)
val ResultSetMapping(_, _, CompiledMapping(converter, _)) = rsm
for(i <- 1 to 5) {
val pr = createFakePR(10000000, converter.asInstanceOf[ResultConverter[JdbcResultConverterDomain, _]])
@@ -1,10 +1,31 @@
package slick
import slick.util.{GlobalConfig, DumpInfo, TreePrinter, Dumpable}
/** All Exceptions that are thrown directly by Slick are of type `SlickException`.
* Other Exceptions originating in non-Slick code are generally not wrapped but
* passed on directly.
*
* @param msg The error message
* @param parent An optional parent Exception or `null`
*/
class SlickException(msg: String, parent: Throwable = null) extends RuntimeException(msg, parent)
/** A SlickException that contains a `Dumpable` with more details. */
class SlickTreeException(msg: String, detail: Dumpable, parent: Throwable = null, mark: (Dumpable => Boolean) = null, removeUnmarked: Boolean = true)
extends SlickException(SlickTreeException.format(msg, detail, mark, removeUnmarked), parent)
private[slick] object SlickTreeException {
val treePrinter = new TreePrinter(prefix = DumpInfo.highlight(if(GlobalConfig.unicodeDump) "\u2503 " else "| "))
def format(msg: String, detail: Dumpable, _mark: (Dumpable => Boolean), removeUnmarked: Boolean): String =
if(detail eq null) msg else msg + {
try {
val mark = if(_mark eq null) ((_: Dumpable) => false) else _mark
val tp = treePrinter.copy(mark = mark)
val markedTop =
if(!removeUnmarked || (_mark eq null)) detail else tp.findMarkedTop(detail)
"\n" + tp.get(markedTop)
} catch { case t: Throwable => " <Error formatting detail: " + t + ">" }
}
}
@@ -319,10 +319,10 @@ trait DatabaseComponent { self =>
ctx.sequenceCounter += 1
val logA = a.nonFusedEquivalentAction
val aPrefix = if(a eq logA) "" else "[fused] "
val dump = TreeDump.get(logA, prefix = " ", firstPrefix = aPrefix, narrow = {
val dump = new TreePrinter(prefix = " ", firstPrefix = aPrefix, narrow = {
case a: DBIOAction[_, _, _] => a.nonFusedEquivalentAction
case o => o
})
}).get(logA)
val msg = DumpInfo.highlight("#" + ctx.sequenceCounter) + ": " + dump.substring(0, dump.length-1)
actionLogger.debug(msg)
}
@@ -5,7 +5,6 @@ import java.sql.{PreparedStatement, ResultSet}
import slick.relational._
import slick.SlickException
import slick.ast.ScalaBaseType
import slick.util.{TreeDump => Dump} //--
/** Specialized JDBC ResultConverter for non-`Option` values. */
class BaseResultConverter[@specialized(Byte, Short, Int, Long, Char, Float, Double, Boolean) T](val ti: JdbcType[T], val name: String, val idx: Int) extends ResultConverter[JdbcResultConverterDomain, T] {
@@ -2,9 +2,12 @@ package slick.util
/** Utilities for logging and creating tree & table dumps. */
private[slick] object LogUtil {
val (cNormal, cGreen, cYellow, cBlue, cCyan) =
if(GlobalConfig.ansiDump) ("\u001B[0m", "\u001B[32m", "\u001B[33m", "\u001B[34m", "\u001B[36m")
else ("", "", "", "", "")
val (cNormal, cBlack, cRed, cGreen, cYellow, cBlue, cMagenta, cCyan) =
if(GlobalConfig.ansiDump) ("\u001B[0m", "\u001B[30m", "\u001B[31m", "\u001B[32m", "\u001B[33m", "\u001B[34m", "\u001B[35m", "\u001B[36m")
else ("", "", "", "", "", "", "", "")
val (bRed, bGreen, bYellow, bBlue, bMagenta, bCyan) =
if(GlobalConfig.ansiDump) ("\u001B[41m", "\u001B[42m", "\u001B[43m", "\u001B[44m", "\u001B[45m", "\u001B[46m")
else ("", "", "", "", "", "")
private[this] val multi = if(GlobalConfig.unicodeDump) "\u2507 " else " "
private[this] val multilineBorderPrefix = cYellow + multi + cNormal
@@ -1,13 +1,12 @@
package slick.util
import org.slf4j.{ Logger => Slf4jLogger, LoggerFactory }
import org.slf4j.{Logger => Slf4jLogger, LoggerFactory}
import scala.reflect.ClassTag
final class SlickLogger(val slf4jLogger: Slf4jLogger) {
@inline
def debug(msg: => String, n: => Dumpable): Unit =
debug(msg+"\n"+TreeDump.get(n, prefix = DumpInfo.highlight(if(GlobalConfig.unicodeDump) "\u2503 " else "| ")))
def debug(msg: => String, n: => Dumpable): Unit = debug(msg+"\n"+SlickLogger.treePrinter.get(n))
@inline
def isDebugEnabled = slf4jLogger.isDebugEnabled()
@@ -44,6 +43,9 @@ final class SlickLogger(val slf4jLogger: Slf4jLogger) {
}
object SlickLogger {
private val treePrinter =
new TreePrinter(prefix = DumpInfo.highlight(if(GlobalConfig.unicodeDump) "\u2503 " else "| "))
def apply[T](implicit ct: ClassTag[T]): SlickLogger =
new SlickLogger(LoggerFactory.getLogger(ct.runtimeClass))
}
@@ -4,29 +4,30 @@ import java.io.{OutputStreamWriter, StringWriter, PrintWriter}
import LogUtil._
/** Create a readable printout of a tree. */
object TreeDump {
private[this] val (childPrefix1, childPrefix2, lastChildPrefix1, lastChildPrefix2, multi1, multi2) =
if(GlobalConfig.unicodeDump) ("\u2523 ", "\u2503 ", "\u2517 ", " ", "\u250f ", "\u2507 ")
else (" ", " ", " ", " ", ": ", ": ")
case class TreePrinter(name: String = "", prefix: String = "", firstPrefix: String = null,
narrow: (Dumpable => Dumpable) = identity, mark: (Dumpable => Boolean) = (_ => false)) {
import TreePrinter._
def get(n: Dumpable, name: String = "", prefix: String = "", firstPrefix: String = null, narrow: (Dumpable => Dumpable) = identity) = {
def get(n: Dumpable) = {
val buf = new StringWriter
apply(n, name, prefix, firstPrefix, new PrintWriter(buf), narrow)
print(n, new PrintWriter(buf))
buf.getBuffer.toString
}
def apply(n: Dumpable, name: String = "", prefix: String = "", firstPrefix: String = null, out: PrintWriter = new PrintWriter(new OutputStreamWriter(System.out)), narrow: (Dumpable => Dumpable) = identity) {
def print(n: Dumpable, out: PrintWriter = new PrintWriter(new OutputStreamWriter(System.out))) {
def dump(baseValue: Dumpable, prefix1: String, prefix2: String, name: String, level: Int) {
val value = narrow(baseValue)
val di =
if(value eq null) DumpInfo("<error: narrowed to null>", "baseValue = "+baseValue)
else value.getDumpInfo
val multiLine = di.mainInfo contains '\n'
val marked = mark(value)
val markedDiName = if(marked) "< " + di.name + " >" else di.name
out.print(
prefix1 +
cCyan + (if(name.nonEmpty) name + ": " else "") +
cYellow + (if(multiLine) multi1 else "") + di.name + (if(di.name.nonEmpty && di.mainInfo.nonEmpty) " " else "") +
cNormal
cCyan + (if(name.nonEmpty) name + ": " else "") +
(if(marked) cNormal+bYellow+cBlack else cYellow) + (if(multiLine) multi1 else "") + markedDiName +
cNormal + (if(di.name.nonEmpty && di.mainInfo.nonEmpty) " " else "")
)
if(multiLine) {
val lines = di.mainInfo.replace("\r", "").split('\n')
@@ -46,6 +47,28 @@ object TreeDump {
dump(n, if(firstPrefix ne null) firstPrefix else prefix, prefix, name, 0)
out.flush()
}
def findMarkedTop(n: Dumpable): Dumpable = {
def find(n: Dumpable): Option[Dumpable] = {
val value = narrow(n)
if(mark(value)) Some(n) else {
val children = value.getDumpInfo.children.map(_._2).toVector
val markedChildren = children.map(find).collect { case Some(d) => d }
if(markedChildren.length > 1) Some(n)
else if(markedChildren.length == 1) Some(markedChildren.head)
else None
}
}
find(n).getOrElse(n)
}
}
object TreePrinter {
def default = new TreePrinter
private[TreePrinter] val (childPrefix1, childPrefix2, lastChildPrefix1, lastChildPrefix2, multi1, multi2) =
if(GlobalConfig.unicodeDump) ("\u2523 ", "\u2503 ", "\u2517 ", " ", "\u250f ", "\u2507 ")
else (" ", " ", " ", " ", ": ", ": ")
}
/** Interface for types that can be used in a tree dump */
@@ -64,3 +87,18 @@ object DumpInfo {
def highlight(s: String) = cGreen + s + cNormal
}
/** Create a wrapper for a `Dumpable` to omit some nodes. */
object Ellipsis {
def apply(n: Dumpable, poss: List[Int]*): Dumpable = new Dumpable {
def getDumpInfo = {
val parent = n.getDumpInfo
if(poss.isEmpty) parent
else if(poss contains Nil) DumpInfo("...")
else parent.copy(children = parent.children.zipWithIndex.map { case ((name, ch), idx) =>
val chposs = poss.filter(_.head == idx).map(_.tail)
(name, apply(ch, chposs: _*))
})
}
}
}

0 comments on commit 325f200

Please sign in to comment.