Cleanup `ConsoleAppender`

This commit is contained in:
Martin Duhem 2017-07-07 11:25:45 +02:00
parent 88395e18a4
commit f55a509fdd
No known key found for this signature in database
GPG Key ID: CC9CE9656E10C555
6 changed files with 397 additions and 233 deletions

View File

@ -1,5 +1,6 @@
package sbt.internal.util
import scala.compat.Platform.EOL
import sbt.util._
import java.io.{ PrintStream, PrintWriter }
import java.util.Locale
@ -14,146 +15,220 @@ import ConsoleAppender._
object ConsoleLogger {
// These are provided so other modules do not break immediately.
@deprecated("Use ConsoleAppender.", "0.13.x")
final val ESC = ConsoleAppender.ESC
@deprecated("Use ConsoleAppender.", "0.13.x")
private[sbt] def isEscapeTerminator(c: Char): Boolean = ConsoleAppender.isEscapeTerminator(c)
@deprecated("Use ConsoleAppender.", "0.13.x")
def hasEscapeSequence(s: String): Boolean = ConsoleAppender.hasEscapeSequence(s)
@deprecated("Use ConsoleAppender.", "0.13.x")
def removeEscapeSequences(s: String): String = ConsoleAppender.removeEscapeSequences(s)
@deprecated("Use ConsoleAppender.", "0.13.x")
val formatEnabled = ConsoleAppender.formatEnabled
@deprecated("Use EscHelpers.", "0.13.x")
final val ESC = EscHelpers.ESC
@deprecated("Use EscHelpers.", "0.13.x")
private[sbt] def isEscapeTerminator(c: Char): Boolean = EscHelpers.isEscapeTerminator(c)
@deprecated("Use EscHelpers.", "0.13.x")
def hasEscapeSequence(s: String): Boolean = EscHelpers.hasEscapeSequence(s)
@deprecated("Use EscHelpers.", "0.13.x")
def removeEscapeSequences(s: String): String = EscHelpers.removeEscapeSequences(s)
@deprecated("Use ConsoleAppenders.formatEnabledInEnv", "0.13.x")
val formatEnabled = ConsoleAppender.formatEnabledInEnv
@deprecated("Use ConsoleAppender.", "0.13.x")
val noSuppressedMessage = ConsoleAppender.noSuppressedMessage
/**
* A new `ConsoleLogger` that logs to `out`.
*
* @param out Where to log the messages.
* @return A new `ConsoleLogger` that logs to `out`.
*/
def apply(out: PrintStream): ConsoleLogger = apply(ConsoleOut.printStreamOut(out))
/**
* A new `ConsoleLogger` that logs to `out`.
*
* @param out Where to log the messages.
* @return A new `ConsoleLogger` that logs to `out`.
*/
def apply(out: PrintWriter): ConsoleLogger = apply(ConsoleOut.printWriterOut(out))
def apply(out: ConsoleOut = ConsoleOut.systemOut, ansiCodesSupported: Boolean = ConsoleAppender.formatEnabled,
useColor: Boolean = ConsoleAppender.formatEnabled, suppressedMessage: SuppressedTraceContext => Option[String] = ConsoleAppender.noSuppressedMessage): ConsoleLogger =
new ConsoleLogger(out, ansiCodesSupported, useColor, suppressedMessage)
/**
* A new `ConsoleLogger` that logs to `out`.
*
* @param out Where to log the messages.
* @param ansiCodesSupported `true` if `out` supported ansi codes, `false` otherwise.
* @param useFormat `true` to show formatting, `false` to remove it from messages.
* @param suppressedMessage How to show suppressed stack traces.
* @return A new `ConsoleLogger` that logs to `out`.
*/
def apply(out: ConsoleOut = ConsoleOut.systemOut,
ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv,
useFormat: Boolean = ConsoleAppender.formatEnabledInEnv,
suppressedMessage: SuppressedTraceContext => Option[String] = ConsoleAppender.noSuppressedMessage): ConsoleLogger =
new ConsoleLogger(out, ansiCodesSupported, useFormat, suppressedMessage)
}
/**
* A logger that logs to the console. On supported systems, the level labels are
* colored.
*/
class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val ansiCodesSupported: Boolean, val useColor: Boolean, val suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger {
private[sbt] val appender = ConsoleAppender(generateName, out, ansiCodesSupported, useColor, suppressedMessage)
class ConsoleLogger private[ConsoleLogger] (out: ConsoleOut,
override val ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger {
private[sbt] val appender: ConsoleAppender =
ConsoleAppender(generateName(), out, ansiCodesSupported, useFormat, suppressedMessage)
override def control(event: ControlEvent.Value, message: => String): Unit =
appender.control(event, message)
override def log(level: Level.Value, message: => String): Unit =
{
if (atLevel(level)) {
appender.appendLog(level, message)
}
if (atLevel(level)) {
appender.appendLog(level, message)
}
override def success(message: => String): Unit =
{
if (successEnabled) {
appender.success(message)
}
if (successEnabled) {
appender.success(message)
}
override def trace(t: => Throwable): Unit =
appender.trace(t, getTrace)
override def logAll(events: Seq[LogEvent]) = out.lockObject.synchronized { events.foreach(log) }
override def logAll(events: Seq[LogEvent]) =
out.lockObject.synchronized { events.foreach(log) }
}
object ConsoleAppender {
/** Escape character, used to introduce an escape sequence. */
final val ESC = '\u001B'
/**
* An escape terminator is a character in the range `@` (decimal value 64) to `~` (decimal value 126).
* It is the final character in an escape sequence.
*
* cf. http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
*/
private[sbt] def isEscapeTerminator(c: Char): Boolean =
c >= '@' && c <= '~'
/** Hide stack trace altogether. */
val noSuppressedMessage = (_: SuppressedTraceContext) => None
/**
* Test if the character AFTER an ESC is the ANSI CSI.
*
* see: http://en.wikipedia.org/wiki/ANSI_escape_code
*
* The CSI (control sequence instruction) codes start with ESC + '['. This is for testing the second character.
*
* There is an additional CSI (one character) that we could test for, but is not frequnetly used, and we don't
* check for it.
*
* cf. http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
*/
private def isCSI(c: Char): Boolean = c == '['
/**
* Tests whether or not a character needs to immediately terminate the ANSI sequence.
*
* c.f. http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
*/
private def isAnsiTwoCharacterTerminator(c: Char): Boolean =
(c >= '@') && (c <= '_')
/**
* Returns true if the string contains the ESC character.
*
* TODO - this should handle raw CSI (not used much)
*/
def hasEscapeSequence(s: String): Boolean =
s.indexOf(ESC) >= 0
/**
* Returns the string `s` with escape sequences removed.
* An escape sequence starts with the ESC character (decimal value 27) and ends with an escape terminator.
* @see isEscapeTerminator
*/
def removeEscapeSequences(s: String): String =
if (s.isEmpty || !hasEscapeSequence(s))
s
else {
val sb = new java.lang.StringBuilder
nextESC(s, 0, sb)
sb.toString
}
private[this] def nextESC(s: String, start: Int, sb: java.lang.StringBuilder): Unit = {
val escIndex = s.indexOf(ESC, start)
if (escIndex < 0) {
sb.append(s, start, s.length)
()
} else {
sb.append(s, start, escIndex)
val next: Int =
// If it's a CSI we skip past it and then look for a terminator.
if (isCSI(s.charAt(escIndex + 1))) skipESC(s, escIndex + 2)
else if (isAnsiTwoCharacterTerminator(s.charAt(escIndex + 1))) escIndex + 2
else {
// There could be non-ANSI character sequences we should make sure we handle here.
skipESC(s, escIndex + 1)
}
nextESC(s, next, sb)
}
/** Indicates whether formatting has been disabled in environment variables. */
val formatEnabledInEnv: Boolean = {
import java.lang.Boolean.{ getBoolean, parseBoolean }
val value = System.getProperty("sbt.log.format")
if (value eq null) (ansiSupported && !getBoolean("sbt.log.noformat")) else parseBoolean(value)
}
/** Skips the escape sequence starting at `i-1`. `i` should be positioned at the character after the ESC that starts the sequence. */
private[this] def skipESC(s: String, i: Int): Int = {
if (i >= s.length) {
i
} else if (isEscapeTerminator(s.charAt(i))) {
i + 1
} else {
skipESC(s, i + 1)
}
private[this] val generateId: AtomicInteger = new AtomicInteger
/**
* A new `ConsoleAppender` that writes to standard output.
*
* @return A new `ConsoleAppender` that writes to standard output.
*/
def apply(): ConsoleAppender = apply(ConsoleOut.systemOut)
/**
* A new `ConsoleAppender` that appends log message to `out`.
*
* @param out Where to write messages.
* @return A new `ConsoleAppender`.
*/
def apply(out: PrintStream): ConsoleAppender = apply(ConsoleOut.printStreamOut(out))
/**
* A new `ConsoleAppender` that appends log messages to `out`.
*
* @param out Where to write messages.
* @return A new `ConsoleAppender`.
*/
def apply(out: PrintWriter): ConsoleAppender = apply(ConsoleOut.printWriterOut(out))
/**
* A new `ConsoleAppender` that writes to `out`.
*
* @param out Where to write messages.
* @return A new `ConsoleAppender that writes to `out`.
*/
def apply(out: ConsoleOut): ConsoleAppender = apply(generateName(), out)
/**
* A new `ConsoleAppender` identified by `name`, and that writes to standard output.
*
* @param name An identifier for the `ConsoleAppender`.
* @return A new `ConsoleAppender` that writes to standard output.
*/
def apply(name: String): ConsoleAppender = apply(name, ConsoleOut.systemOut)
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
*
* @param name An identifier for the `ConsoleAppender`.
* @param out Where to write messages.
* @return A new `ConsoleAppender` that writes to `out`.
*/
def apply(name: String, out: ConsoleOut): ConsoleAppender = apply(name, out, formatEnabledInEnv)
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
*
* @param name An identifier for the `ConsoleAppender`.
* @param out Where to write messages.
* @param suppressedMessage How to handle stack traces.
* @return A new `ConsoleAppender` that writes to `out`.
*/
def apply(name: String, out: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender =
apply(name, out, formatEnabledInEnv, formatEnabledInEnv, suppressedMessage)
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
*
* @param name An identifier for the `ConsoleAppender`.
* @param out Where to write messages.
* @param useFormat `true` to enable format (color, bold, etc.), `false` to remove formatting.
* @return A new `ConsoleAppender` that writes to `out`.
*/
def apply(name: String, out: ConsoleOut, useFormat: Boolean): ConsoleAppender =
apply(name, out, formatEnabledInEnv, useFormat, noSuppressedMessage)
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
*
* @param name An identifier for the `ConsoleAppender`.
* @param out Where to write messages.
* @param ansiCodesSupported `true` if the output stream supports ansi codes, `false` otherwise.
* @param useFormat `true` to enable format (color, bold, etc.), `false` to remove
* formatting.
* @return A new `ConsoleAppender` that writes to `out`.
*/
def apply(name: String,
out: ConsoleOut,
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender = {
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage)
appender.start
appender
}
val formatEnabled: Boolean =
{
import java.lang.Boolean.{ getBoolean, parseBoolean }
val value = System.getProperty("sbt.log.format")
if (value eq null) (ansiSupported && !getBoolean("sbt.log.noformat")) else parseBoolean(value)
/**
* Converts the Log4J `level` to the corresponding sbt level.
*
* @param level A level, as represented by Log4J.
* @return The corresponding level in sbt's world.
*/
def toLevel(level: XLevel): Level.Value =
level match {
case XLevel.OFF => Level.Debug
case XLevel.FATAL => Level.Error
case XLevel.ERROR => Level.Error
case XLevel.WARN => Level.Warn
case XLevel.INFO => Level.Info
case XLevel.DEBUG => Level.Debug
case _ => Level.Debug
}
/**
* Converts the sbt `level` to the corresponding Log4J level.
*
* @param level A level, as represented by sbt.
* @return The corresponding level in Log4J's world.
*/
def toXLevel(level: Level.Value): XLevel =
level match {
case Level.Error => XLevel.ERROR
case Level.Warn => XLevel.WARN
case Level.Info => XLevel.INFO
case Level.Debug => XLevel.DEBUG
}
private[sbt] def generateName(): String = "out-" + generateId.incrementAndGet
private[this] def jline1to2CompatMsg = "Found class jline.Terminal, but interface was expected"
private[this] def ansiSupported =
@ -172,57 +247,9 @@ object ConsoleAppender {
throw new IncompatibleClassChangeError("JLine incompatibility detected. Check that the sbt launcher is version 0.13.x or later.")
}
val noSuppressedMessage = (_: SuppressedTraceContext) => None
private[this] def os = System.getProperty("os.name")
private[this] def isWindows = os.toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
def apply(out: PrintStream): ConsoleAppender = apply(ConsoleOut.printStreamOut(out))
def apply(out: PrintWriter): ConsoleAppender = apply(ConsoleOut.printWriterOut(out))
def apply(): ConsoleAppender = apply(ConsoleOut.systemOut)
def apply(name: String): ConsoleAppender = apply(name, ConsoleOut.systemOut)
def apply(out: ConsoleOut): ConsoleAppender = apply(generateName, out)
def apply(name: String, out: ConsoleOut): ConsoleAppender = apply(name, out, formatEnabled)
def apply(name: String, out: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender =
apply(name, out, formatEnabled, formatEnabled, suppressedMessage)
def apply(name: String, out: ConsoleOut, useColor: Boolean): ConsoleAppender =
apply(name, out, formatEnabled, useColor, noSuppressedMessage)
def apply(name: String, out: ConsoleOut, ansiCodesSupported: Boolean,
useColor: Boolean, suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender =
{
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useColor, suppressedMessage)
appender.start
appender
}
def generateName: String = "out-" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger
private[this] val EscapeSequence = (27.toChar + "[^@-~]*[@-~]").r
def stripEscapeSequences(s: String): String =
EscapeSequence.pattern.matcher(s).replaceAll("")
def toLevel(level: XLevel): Level.Value =
level match {
case XLevel.OFF => Level.Debug
case XLevel.FATAL => Level.Error
case XLevel.ERROR => Level.Error
case XLevel.WARN => Level.Warn
case XLevel.INFO => Level.Info
case XLevel.DEBUG => Level.Debug
case _ => Level.Debug
}
def toXLevel(level: Level.Value): XLevel =
level match {
case Level.Error => XLevel.ERROR
case Level.Warn => XLevel.WARN
case Level.Info => XLevel.INFO
case Level.Debug => XLevel.DEBUG
}
}
// See http://stackoverflow.com/questions/24205093/how-to-create-a-custom-appender-in-log4j2
@ -237,35 +264,135 @@ object ConsoleAppender {
* This logger is not thread-safe.
*/
class ConsoleAppender private[ConsoleAppender] (
val name: String,
val out: ConsoleOut,
val ansiCodesSupported: Boolean,
val useColor: Boolean,
val suppressedMessage: SuppressedTraceContext => Option[String]
name: String,
out: ConsoleOut,
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]
) extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
import scala.Console.{ BLUE, GREEN, RED, RESET, YELLOW }
def append(event: XLogEvent): Unit =
{
val level = ConsoleAppender.toLevel(event.getLevel)
val message = event.getMessage
// val str = messageToString(message)
appendMessage(level, message)
private final val SUCCESS_LABEL_COLOR = GREEN
private final val SUCCESS_MESSAGE_COLOR = RESET
private final val NO_COLOR = RESET
override def append(event: XLogEvent): Unit = {
val level = ConsoleAppender.toLevel(event.getLevel)
val message = event.getMessage
appendMessage(level, message)
}
// TODO:
// success is called by ConsoleLogger.
// This should turn into an event.
private[sbt] def success(message: => String): Unit = {
appendLog(SUCCESS_LABEL_COLOR, Level.SuccessLabel, SUCCESS_MESSAGE_COLOR, message)
}
/**
* Logs the stack trace of `t`, possibly shortening it.
*
* The `traceLevel` parameter configures how the stack trace will be shortened.
* See `StackTrace.trimmed`.
*
* @param t The `Throwable` whose stack trace to log.
* @param traceLevel How to shorten the stack trace.
*/
def trace(t: => Throwable, traceLevel: Int): Unit =
out.lockObject.synchronized {
if (traceLevel >= 0)
write(StackTrace.trimmed(t, traceLevel))
if (traceLevel <= 2)
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)))
appendLog(NO_COLOR, "trace", NO_COLOR, msg)
}
def appendMessage(level: Level.Value, msg: Message): Unit =
/**
* Logs a `ControlEvent` to the log.
*
* @param event The kind of `ControlEvent`.
* @param message The message to log.
*/
def control(event: ControlEvent.Value, message: => String): Unit =
appendLog(labelColor(Level.Info), Level.Info.toString, BLUE, message)
/**
* Appends the message `message` to the to the log at level `level`.
*
* @param level The importance level of the message.
* @param message The message to log.
*/
def appendLog(level: Level.Value, message: => String): Unit = {
appendLog(labelColor(level), level.toString, NO_COLOR, message)
}
/**
* Formats `msg` with `format, wrapped between `RESET`s
*
* @param format The format to use
* @param msg The message to format
* @return The formatted message.
*/
private def formatted(format: String, msg: String): String =
s"${RESET}${format}${msg}${RESET}"
/**
* Select the right color for the label given `level`.
*
* @param level The label to consider to select the color.
* @return The color to use to color the label.
*/
private def labelColor(level: Level.Value): String =
level match {
case Level.Error => RED
case Level.Warn => YELLOW
case _ => NO_COLOR
}
/**
* Appends a full message to the log. Each line is prefixed with `[$label]`, written in
* `labelColor` if formatting is enabled. The lines of the messages are colored with
* `messageColor` if formatting is enabled.
*
* @param labelColor The color to use to format the label.
* @param label The label to prefix each line with. The label is shown between square
* brackets.
* @param messageColor The color to use to format the message.
* @param message The message to write.
*/
private def appendLog(labelColor: String, label: String, messageColor: String, message: String): Unit =
out.lockObject.synchronized {
message.lines.foreach { line =>
val labeledLine = s"[${formatted(labelColor, label)}] ${formatted(messageColor, line)}"
writeLine(labeledLine)
}
}
private def write(msg: String): Unit = {
val cleanedMsg =
if (!useFormat) EscHelpers.removeEscapeSequences(msg)
else msg
out.println(cleanedMsg)
}
private def writeLine(line: String): Unit =
write(line + EOL)
private def appendMessage(level: Level.Value, msg: Message): Unit =
msg match {
case o: ObjectMessage => objectToLines(o.getParameter) foreach { appendLog(level, _) }
case o: ReusableObjectMessage => objectToLines(o.getParameter) foreach { appendLog(level, _) }
case _ => appendLog(level, msg.getFormattedMessage)
}
def objectToLines(o: AnyRef): Vector[String] =
private def objectToLines(o: AnyRef): Vector[String] =
o match {
case x: StringEvent => Vector(x.message)
case x: ObjectEvent[_] => objectEventToLines(x)
case _ => Vector(o.toString)
}
def objectEventToLines(oe: ObjectEvent[_]): Vector[String] =
private def objectEventToLines(oe: ObjectEvent[_]): Vector[String] =
{
val contentType = oe.contentType
LogExchange.stringCodec[AnyRef](contentType) match {
@ -273,61 +400,7 @@ class ConsoleAppender private[ConsoleAppender] (
case _ => Vector(oe.message.toString)
}
}
def messageColor(level: Level.Value) = RESET
def labelColor(level: Level.Value) =
level match {
case Level.Error => RED
case Level.Warn => YELLOW
case _ => RESET
}
// success is called by ConsoleLogger.
// This should turn into an event.
private[sbt] def success(message: => String): Unit = {
appendLog(successLabelColor, Level.SuccessLabel, successMessageColor, message)
}
private[sbt] def successLabelColor = GREEN
private[sbt] def successMessageColor = RESET
def trace(t: => Throwable, traceLevel: Int): Unit =
out.lockObject.synchronized {
if (traceLevel >= 0)
out.print(StackTrace.trimmed(t, traceLevel))
if (traceLevel <= 2)
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useColor)))
printLabeledLine(labelColor(Level.Error), "trace", messageColor(Level.Error), msg)
}
def control(event: ControlEvent.Value, message: => String): Unit =
appendLog(labelColor(Level.Info), Level.Info.toString, BLUE, message)
def appendLog(level: Level.Value, message: => String): Unit = {
appendLog(labelColor(level), level.toString, messageColor(level), message)
}
private def reset(): Unit = setColor(RESET)
private def setColor(color: String): Unit = {
if (ansiCodesSupported && useColor)
out.lockObject.synchronized { out.print(color) }
}
private def appendLog(labelColor: String, label: String, messageColor: String, message: String): Unit =
out.lockObject.synchronized {
for (line <- message.split("""\n"""))
printLabeledLine(labelColor, label, messageColor, line)
}
private def printLabeledLine(labelColor: String, label: String, messageColor: String, line: String): Unit =
{
reset()
out.print("[")
setColor(labelColor)
out.print(label)
reset()
out.print("] ")
setColor(messageColor)
out.print(line)
reset()
out.println()
}
}
final class SuppressedTraceContext(val traceLevel: Int, val useColor: Boolean)
final class SuppressedTraceContext(val traceLevel: Int, val useFormat: Boolean)

View File

@ -33,7 +33,7 @@ object ConsoleOut {
def println(s: String): Unit = synchronized { current.append(s); println() }
def println(): Unit = synchronized {
val s = current.toString
if (ConsoleAppender.formatEnabled && last.exists(lmsg => f(s, lmsg)))
if (ConsoleAppender.formatEnabledInEnv && last.exists(lmsg => f(s, lmsg)))
lockObject.print(OverwriteLine)
lockObject.println(s)
last = Some(s)

View File

@ -0,0 +1,91 @@
package sbt.internal.util
object EscHelpers {
/** Escape character, used to introduce an escape sequence. */
final val ESC = '\u001B'
/**
* An escape terminator is a character in the range `@` (decimal value 64) to `~` (decimal value 126).
* It is the final character in an escape sequence.
*
* cf. http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
*/
private[sbt] def isEscapeTerminator(c: Char): Boolean =
c >= '@' && c <= '~'
/**
* Test if the character AFTER an ESC is the ANSI CSI.
*
* see: http://en.wikipedia.org/wiki/ANSI_escape_code
*
* The CSI (control sequence instruction) codes start with ESC + '['. This is for testing the second character.
*
* There is an additional CSI (one character) that we could test for, but is not frequnetly used, and we don't
* check for it.
*
* cf. http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
*/
private def isCSI(c: Char): Boolean = c == '['
/**
* Tests whether or not a character needs to immediately terminate the ANSI sequence.
*
* c.f. http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
*/
private def isAnsiTwoCharacterTerminator(c: Char): Boolean =
(c >= '@') && (c <= '_')
/**
* Returns true if the string contains the ESC character.
*
* TODO - this should handle raw CSI (not used much)
*/
def hasEscapeSequence(s: String): Boolean =
s.indexOf(ESC) >= 0
/**
* Returns the string `s` with escape sequences removed.
* An escape sequence starts with the ESC character (decimal value 27) and ends with an escape terminator.
* @see isEscapeTerminator
*/
def removeEscapeSequences(s: String): String =
if (s.isEmpty || !hasEscapeSequence(s))
s
else {
val sb = new java.lang.StringBuilder
nextESC(s, 0, sb)
sb.toString
}
private[this] def nextESC(s: String, start: Int, sb: java.lang.StringBuilder): Unit = {
val escIndex = s.indexOf(ESC, start)
if (escIndex < 0) {
sb.append(s, start, s.length)
()
} else {
sb.append(s, start, escIndex)
val next: Int =
// If it's a CSI we skip past it and then look for a terminator.
if (isCSI(s.charAt(escIndex + 1))) skipESC(s, escIndex + 2)
else if (isAnsiTwoCharacterTerminator(s.charAt(escIndex + 1))) escIndex + 2
else {
// There could be non-ANSI character sequences we should make sure we handle here.
skipESC(s, escIndex + 1)
}
nextESC(s, next, sb)
}
}
/** Skips the escape sequence starting at `i-1`. `i` should be positioned at the character after the ESC that starts the sequence. */
private[this] def skipESC(s: String, i: Int): Int = {
if (i >= s.length) {
i
} else if (isEscapeTerminator(s.charAt(i))) {
i + 1
} else {
skipESC(s, i + 1)
}
}
}

View File

@ -48,15 +48,15 @@ object MainAppender {
def defaultScreen(name: String, console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): Appender =
ConsoleAppender(name, console, suppressedMessage = suppressedMessage)
def defaultBacked: PrintWriter => Appender = defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabled)
def defaultBacked(loggerName: String): PrintWriter => Appender = defaultBacked(loggerName, ConsoleAppender.formatEnabled)
def defaultBacked(useColor: Boolean): PrintWriter => Appender = defaultBacked(generateGlobalBackingName, useColor)
def defaultBacked(loggerName: String, useColor: Boolean): PrintWriter => Appender =
def defaultBacked: PrintWriter => Appender = defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabledInEnv)
def defaultBacked(loggerName: String): PrintWriter => Appender = defaultBacked(loggerName, ConsoleAppender.formatEnabledInEnv)
def defaultBacked(useFormat: Boolean): PrintWriter => Appender = defaultBacked(generateGlobalBackingName, useFormat)
def defaultBacked(loggerName: String, useFormat: Boolean): PrintWriter => Appender =
to => {
ConsoleAppender(
ConsoleAppender.generateName,
ConsoleOut.printWriterOut(to),
useColor = useColor
useFormat = useFormat
)
}

View File

@ -41,7 +41,7 @@ class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger {
private[this] def removeEscapes(event: LogEvent): LogEvent =
{
import ConsoleAppender.{ removeEscapeSequences => rm }
import EscHelpers.{ removeEscapeSequences => rm }
event match {
case s: Success => new Success(rm(s.msg))
case l: Log => new Log(l.level, rm(l.msg))

View File

@ -4,7 +4,7 @@ import org.scalacheck._
import Prop._
import Gen.{ listOf, oneOf }
import ConsoleAppender.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequences }
import EscHelpers.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequences }
object Escapes extends Properties("Escapes") {
property("genTerminator only generates terminators") =