2
\$\begingroup\$

Notice: The original library has since been replaced by:

https://github.com/JohnReedLOL/scala-trace-debug


See my Scala print trace debugger. This project is used for lightweight debugging sort of like the Haskell Debug.Trace module. The README, the documentation, and the test cases need work.

To test it out, see the code below or do:

git clone https://github.com/JohnReedLOL/debugtrace

And open it up in IntelliJ as an SBT Project. To run:

IntelliJ > Run > Edit Configurations > Add New Configuration > Application (or "SBT Task")

Code to be reviewed:

package info.collaboration_station.debug

/**
  * Created by johnreed on 3/12/16.
  */
object Debug {

  /**
    * Stack offset is 2 because the first row in the stack trace is Thread and the second row is internal call.
    */
  protected[debug] val stackOffset = 2

  private var _traceOut_? = true

  /**
    * Tells you whether tracing to standard out is on or off.
    * Note that disabling the "traceStdOut" feature does not disable the "assertStdOut" feature.
    */
  def traceOutOn_? = _traceOut_?

  private var _traceErr_? = true

  /**
    * Tells you whether tracing to standard error is on or off.
    * Note that disabling the "trace" feature does not disable the "assert" feature.
    */
  def traceErrOn_? = _traceErr_?

  private var _fatalAssert_? = true

  /**
    * Tells you whether fatal asserts are on or off.
    */
  def fatalAssertOn_? = _fatalAssert_?

  private var _nonFatalAssert_? = true

  /**
    * Tells you whether non-fatal asserts are on or off.
    */
  def nonFatalAssertOn_? = _nonFatalAssert_?

  // these lines disable and enable particular features.

  def traceErrOn_!() = { _traceErr_? = true }

  def traceErrOff_!() = { _traceErr_? = false }

  def traceOutOn_!() = { _traceOut_? = true }

  def traceOutOff_!() = { _traceOut_? = false }

  def fatalAssertOn_!() = { _fatalAssert_? = true }

  def fatalAssertOff_!() = { _fatalAssert_? = false }

  def nonFatalAssertOn_!() = { _nonFatalAssert_? = true }

  def nonFatalAssertOff_!() = { _nonFatalAssert_? = false }

  /**
    * Enables tracing and asserts, including fatal assertions.
    */
  def enableEverything_!() = {
    traceErrOn_!()
    traceOutOn_!()
    fatalAssertOn_!()
    nonFatalAssertOn_!()
  }

  /**
    * Disables tracing and asserts. Both fatal and non-fatal assertions are disabled. Does not disable print or println.
    */
  def disableEverything_!() = {
    traceErrOff_!()
    traceOutOff_!()
    fatalAssertOff_!()
    nonFatalAssertOff_!()
  }

  /**
    * A fatal assertion. Debug.assert( 1 + 2 = 3 )
    * Terminates the program with exit code "7"
    *
    * @param assertion the assertion that must be true for the program to run. Can be a value or a function.
    * @param message the message to be printed to standard error on assertion failure
    */
  final def assert(assertion: => Boolean, message: String) = {
    if(!assertion && Debug.fatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(message, Int.MaxValue) // trace the max number of lines of stack trace to std error
    }
    System.exit(7)
  }

  /**
    * A fatal assertion. Debug.assert( 1 + 2 = 3 )
    * Terminates the program with exit code "7"
    *
    * @param assertion the assertion that must be true for the program to run. Can be a value or a function.
    * @param message the message to be printed to standard out on assertion failure
    */
  final def assertStdOut(assertion: => Boolean, message: String) = {
    if(!assertion && Debug.fatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(message, Int.MaxValue, useStdOut_? = true) // trace the max number of lines of stack trace to std out
    }
    System.exit(7)
  }

  /**
    * Like Debug.assert, but does not terminate the application.
    */
  final def assertNonFatal(assertion: => Boolean, message: String) = {
    if(!assertion && Debug.nonFatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(message, Int.MaxValue) // trace the max number of lines of stack trace to std error
    }
  }

  /**
    * Like Debug.assertStdOut, but does not terminate the application.
    */
  final def assertNonFatalStdOut(assertion: => Boolean, message: String) = {
    if(!assertion && Debug.nonFatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(message, Int.MaxValue, useStdOut_? = true) // trace the max number of lines of stack trace to std out
    }
  }
}

File where ImplicitTraceObject is located:

The below file (where ImplicitTraceObject is located) is what does the actual printing:

package info.collaboration_station.debug

/**
  * Wrapper class for a value that can be traced
  * @param me the original value inside the wrapper
  * @tparam MyType the original type of the value inside the wrapper
  */
final class ImplicitTrace[MyType](val me: MyType) {
  /**
    * Same as System.out.print(this), but with the function name after the object.
    */
  final def print() = {
    if(me != null) {
      System.out.print(me.toString)
    } else {
      System.out.print("null")
    }
  }
  /**
    * Same as System.out.println(this), but with the function name after the object.
    */
  final def println() = {
    if(me != null) {
      System.out.println(me.toString)
    } else {
      System.out.println("null")
    }
  }
  /**
    * Same as System.err.print(this), but with the function name after the object.
    */
  final def printStdErr() = {
    if(me != null) {
      System.err.print(me.toString)
    } else {
      System.err.print("null")
    }
  }
  /**
    * Same as System.err.println(this), but with the function name after the object.
    */
  final def printlnStdErr() = {
    if(me != null) {
      System.err.println(me.toString)
    } else {
      System.err.println("null")
    }
  }
  /**
    * Prints out this object with 1 lines of stack trace to standard error.
    * @return The thing that was just printed.
    */
  final def trace: MyType = ImplicitTraceObject.traceInternal(me, 1)
  /**
    * Prints out this object to standard error with a user specified number of lines of stack trace.
    * @param linesOfStackTrace The number of lines of stack trace.
    * @return The thing that was just printed.
    */
  final def trace(linesOfStackTrace: Int): MyType = ImplicitTraceObject.traceInternal(me, linesOfStackTrace)
  /**
    * Prints out this object with 1 lines of stack trace to standard out.
    * @return The thing that was just printed.
    */
  final def traceStdOut: MyType = ImplicitTraceObject.traceInternal(me, 1, useStdOut_? = true)
  /**
    * Prints out this object to standard out with a user specified number of lines of stack trace.
    * @param linesOfStackTrace The number of lines of stack trace.
    * @return The thing that was just printed.
    */
  final def traceStdOut(linesOfStackTrace: Int): MyType = ImplicitTraceObject.traceInternal(me, linesOfStackTrace, useStdOut_? = true)
  /**
    * A fatal assertion, but with the function name after the object. 1.assert( _ + 2 = 3 )
    * Terminates the program with exit code "7"
    * @param assertion the assertion that must be true for the program to run
    * @param message the message to be printed to standard error on assertion failure
    */
  final def assert(assertion: (MyType) => Boolean, message: String) = {
    if(! assertion(me) && Debug.fatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue) // trace the max number of lines of stack trace to std error
    }
    System.exit(7)
  }
  /**
    * A fatal assertion, but with the function name after the object. 1.assert( _ + 2 = 3 )
    * Terminates the program with exit code "7"
    * @param assertion the assertion that must be true for the program to run
    * @param message the message to be printed  to standard out on assertion failure
    */
  final def assertStdOut(assertion: (MyType) => Boolean, message: String) = {
    if(! assertion(me) && Debug.fatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue, useStdOut_? = true) // trace the max number of lines of stack trace to std out
    }
    System.exit(7)
  }
  final def assertEquals(other: MyType, message: String) = {
    val assertionTrue_? = this == other
    if(! assertionTrue_? && Debug.fatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue) // trace the max number of lines of stack trace to std error
    }
    System.exit(7)
  }
  final def assertEqualsStdOut(other: MyType, message: String) = {
    val assertionTrue_? = this == other
    if(! assertionTrue_? && Debug.fatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue, useStdOut_? = true) // trace the max number of lines of stack trace to std out
    }
    System.exit(7)
  }
  final def assertNonFatal(assertion: (MyType) => Boolean, message: String) = {
    val assertionTrue_? = assertion(me)
    if(! assertionTrue_? && Debug.nonFatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue) // trace the max number of lines of stack trace to std error
    }
  }
  final def assertNonFatalStdOut(assertion: (MyType) => Boolean, message: String) = {
    val assertionTrue_? = assertion(me)
    if(! assertionTrue_? && Debug.nonFatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue, useStdOut_? = true) // trace the max number of lines of stack trace to std out
    }
  }
  final def assertNonFatalEquals(other: MyType, message: String) = {
    val assertionTrue_? = this == other
    if(! assertionTrue_? && Debug.nonFatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue) // trace the max number of lines of stack trace to std error
    }
  }
  final def assertNonFatalEqualsStdOut(other: MyType, message: String) = {
    val assertionTrue_? = this == other
    if(! assertionTrue_? && Debug.nonFatalAssertOn_?) {
      ImplicitTraceObject.traceInternalAssert(me, Int.MaxValue, useStdOut_? = true) // trace the max number of lines of stack trace to std out
    }
  }
}

/**
  * Contains static methods for ImplicitTraceObject
  */
object ImplicitTraceObject {
  /**
    * The offset of the first line from the base of the stack trace.
    * The +1 is necessary because the method call traceInternal adds one to the offset of the stack trace.
    */
  val newStackOffset = Debug.stackOffset + 1
  /**
    * Prints out the object with N lines of stack trace. Do not use with assertions.
    * @param toPrintOutNullable the object to print out. May be "null".
    * @param numStackLinesIntended N, the number of lines of stack trace intended. Defaults to zero actual lines of stack trace for negative values.
    * @param useStdOut_? Whether to use standard out for trace (as opposed to std error). Uses standard error by default.
    * @return The thing that was put into the first parameter
    */
  protected[debug] final def traceInternal[A](toPrintOutNullable: A, numStackLinesIntended: Int, useStdOut_? : Boolean = false): A = {
    if( !Debug.traceErrOn_? && !useStdOut_?) {
      return toPrintOutNullable // if tracing to standard error is off and we trace to standard error, return
    }
    if( !Debug.traceOutOn_? && useStdOut_?) {
      return toPrintOutNullable // if tracing to standard out is off and we trace to out, return
    }
    val numStackLines = if(numStackLinesIntended > 0) {
      numStackLinesIntended // the number of lines must be positive or zero
    } else {
      0
    }
    val stack = Thread.currentThread().getStackTrace
    val toPrintOut: String = if(toPrintOutNullable == null) {"null"} else {toPrintOutNullable.toString}
    var toPrint = "\"" + toPrintOut + "\"" + " in thread " + Thread.currentThread().getName + ":"
    for (row <- 0 to Math.min(numStackLines - 1, stack.length - 1 - newStackOffset)) {
      val lineNumber = newStackOffset + row
      val stackLine = stack(lineNumber)
      // The java stack traces use a tab character, not a space
      val tab = "\t"
      toPrint += "\n" + tab + "at " + stackLine
    }
    toPrint += "\n"
    if(useStdOut_?) {
      System.out.println(toPrint)
    } else {
      System.err.println(toPrint)
    }
    toPrintOutNullable // return the origional thing, even if it is null.
  }
  /**
    * Prints out the object with N lines of stack trace. Meant to be used only for asserts.
    * @param toPrintOutNullable the object to print out. May be "null".
    * @param numStackLinesIntended N, the number of lines of stack trace intended. Defaults to zero actual lines of stack trace for negative values.
    * @param useStdOut_? Whether to use standard out for trace (as opposed to std error). Uses standard error by default.
    * @return The thing that was put into the first parameter
    */
  protected[debug] final def traceInternalAssert[A](toPrintOutNullable: A, numStackLinesIntended: Int, useStdOut_? : Boolean = false): A = {
    // Disabling trace does not also disable assert. They are two separate things.
    //if( !Debug.traceErrOn_? && !useStdOut_?) {
    //  return toPrintOutNullable // if tracing to standard error is off and we trace to standard error, return
    //}
    //if( !Debug.traceOutOn_? && useStdOut_?) {
    //  return toPrintOutNullable // if tracing to standard out is off and we trace to out, return
    //}
    val numStackLines = if(numStackLinesIntended > 0) {
      numStackLinesIntended // the number of lines must be positive or zero
    } else {
      0
    }
    val stack = Thread.currentThread().getStackTrace
    val toPrintOut: String = if(toPrintOutNullable == null) {"null"} else {toPrintOutNullable.toString}
    var toPrint = "\"" + toPrintOut + "\"" + " in thread " + Thread.currentThread().getName + ":"
    for (row <- 0 to Math.min(numStackLines - 1, stack.length - 1 - newStackOffset)) {
      val lineNumber = newStackOffset + row
      val stackLine = stack(lineNumber)
      // The java stack traces use a tab character, not a space
      val tab = "\t"
      toPrint += "\n" + tab + "at " + stackLine
    }
    toPrint += "\n"
    if(useStdOut_?) {
      System.out.println(toPrint)
    } else {
      System.err.println(toPrint)
    }
    toPrintOutNullable // return the origional thing, even if it is null.
  }
}

This last file performs the implicit conversion to add functionality to objects in your code:

package info.collaboration_station
import info.collaboration_station.debug.ImplicitTrace

/**
  * Created by johnreed on 3/12/16.
  */
package object debug {
  import scala.language.implicitConversions // Warning: implicit conversions language feature

  /**
    * Converter method that puts the value "me" inside ImplicitTrace for additional functionality
    *
    * @param me the value to be put inside the wrapper
    * @tparam MyType the type of the value to be put inside the wrapper
    * @return A new ImplicitTrace wrapper which appends print trace related functionality to the value.
    */
  implicit def toTraceable[MyType](me: MyType) = new ImplicitTrace[MyType](me)
}
\$\endgroup\$
2
\$\begingroup\$

Here are some formal remarks that do not concern the design choices made in the code.

Unnecessary Code

All the repeated functions like

final def println() = {
  if(me != null) {
    System.out.println(me.toString)
  } else {
    System.out.println("null")
  }
}

Can be shortened to

final def println() = println(me)

toString() (or String.valueOf) is invoked behind the scenes on the args passed to System.out.print. If the arg is null, "null" will also be output.

Please note that in Scala, System.out.print* is implicitly imported by the compiler and print* methods may be used directly.

Naming

There are some oddities in Debug fields and function names: underscores in names, suffixes like _? or _!. I suppose they have useful meanings, but this sort of naming is against Scala naming conventions:

Underscores in names (_) are not actually forbidden by the compiler, but are strongly discouraged as they have special meaning within the Scala syntax.

System.exit !?

Be very careful with System.exit() calls. Are you sure they are necessary in this code? Wouldn't there be a risk to break the applications of the users of this code? System.exit(), if not in main method, is generally a bad practice.

\$\endgroup\$
  • \$\begingroup\$ I agree on the null check. \$\endgroup\$ – Michael Lafayette Mar 17 '16 at 14:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.