Merge pull request #71 from eed3si9n/wip/eventlogging

event logging
This commit is contained in:
Dale Wijnand 2017-02-08 16:37:10 +00:00 committed by GitHub
commit b6cad50327
31 changed files with 524 additions and 142 deletions

View File

@ -99,12 +99,12 @@ lazy val utilComplete = (project in internalPath / "util-complete").
// logging
lazy val utilLogging = (project in internalPath / "util-logging").
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
dependsOn(utilInterface, utilTesting % Test).
dependsOn(utilInterface, utilCollection, utilTesting % Test).
settings(
commonSettings,
crossScalaVersions := Seq(scala210, scala211, scala212),
name := "Util Logging",
libraryDependencies ++= Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson),
libraryDependencies ++= Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson, scalaReflect.value),
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala"
)

View File

@ -95,7 +95,7 @@ abstract class EvaluateSettings[Scope] {
keyString
private[this] def keyString =
(static.toSeq.flatMap { case (key, value) => if (value eq this) init.showFullKey(key) :: Nil else Nil }).headOption getOrElse "non-static"
(static.toSeq.flatMap { case (key, value) => if (value eq this) init.showFullKey.show(key) :: Nil else Nil }).headOption getOrElse "non-static"
final def get: T = synchronized {
assert(value != null, toString + " not evaluated")

View File

@ -6,6 +6,7 @@ package sbt.internal.util
import scala.language.existentials
import Types._
import sbt.util.Show
sealed trait Settings[Scope] {
def data: Map[Scope, AttributeMap]
@ -119,7 +120,7 @@ trait Init[Scope] {
def mapScope(f: Scope => Scope): MapScoped = new MapScoped {
def apply[T](k: ScopedKey[T]): ScopedKey[T] = k.copy(scope = f(k.scope))
}
private final class InvalidReference(val key: ScopedKey[_]) extends RuntimeException("Internal settings error: invalid reference to " + showFullKey(key))
private final class InvalidReference(val key: ScopedKey[_]) extends RuntimeException("Internal settings error: invalid reference to " + showFullKey.show(key))
private[this] def applyDefaults(ss: Seq[Setting[_]]): Seq[Setting[_]] =
{
@ -215,11 +216,11 @@ trait Init[Scope] {
{
val guessed = guessIntendedScope(validKeys, delegates, u.referencedKey)
val derived = u.defining.isDerived
val refString = display(u.defining.key)
val refString = display.show(u.defining.key)
val sourceString = if (derived) "" else parenPosString(u.defining)
val guessedString = if (derived) "" else guessed.map(g => "\n Did you mean " + display(g) + " ?").toList.mkString
val guessedString = if (derived) "" else guessed.map(g => "\n Did you mean " + display.show(g) + " ?").toList.mkString
val derivedString = if (derived) ", which is a derived setting that needs this key to be defined in this scope." else ""
display(u.referencedKey) + " from " + refString + sourceString + derivedString + guessedString
display.show(u.referencedKey) + " from " + refString + sourceString + derivedString + guessedString
}
private[this] def parenPosString(s: Setting[_]): String =
s.positionString match { case None => ""; case Some(s) => " (" + s + ")" }
@ -255,7 +256,7 @@ trait Init[Scope] {
new Uninitialized(keys, prefix + suffix + " to undefined setting" + suffix + ": " + keysString + "\n ")
}
final class Compiled[T](val key: ScopedKey[T], val dependencies: Iterable[ScopedKey[_]], val settings: Seq[Setting[T]]) {
override def toString = showFullKey(key)
override def toString = showFullKey.show(key)
}
final class Flattened(val key: ScopedKey[_], val dependencies: Iterable[ScopedKey[_]])

View File

@ -1,8 +0,0 @@
package sbt.internal.util
trait Show[T] {
def apply(t: T): String
}
object Show {
def apply[T](f: T => String): Show[T] = new Show[T] { def apply(t: T): String = f(t) }
}

View File

@ -0,0 +1,12 @@
package sbt.util
trait Show[A] {
def show(a: A): String
}
object Show {
def apply[A](f: A => String): Show[A] = new Show[A] { def show(a: A): String = f(a) }
def fromToString[A]: Show[A] = new Show[A] {
def show(a: A): String = a.toString
}
}

View File

@ -1,4 +1,4 @@
package sbt.internal.util
package sbt.util
trait ShowLines[A] {
def showLines(a: A): Seq[String]

View File

@ -1,5 +1,7 @@
package sbt.internal.util
import sbt.util.Show
/** Define our settings system */
// A basic scope indexed by an integer.
@ -12,9 +14,10 @@ final case class Scope(nestIndex: Int, idAtIndex: Int = 0)
// That would be a general pain.)
case class SettingsExample() extends Init[Scope] {
// Provides a way of showing a Scope+AttributeKey[_]
val showFullKey: Show[ScopedKey[_]] = new Show[ScopedKey[_]] {
def apply(key: ScopedKey[_]) = s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}"
}
val showFullKey: Show[ScopedKey[_]] = Show[ScopedKey[_]]((key: ScopedKey[_]) =>
{
s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}"
})
// A sample delegation function that delegates to a Scope with a lower index.
val delegates: Scope => Seq[Scope] = {

View File

@ -3,16 +3,19 @@
*/
package xsbti;
import java.io.File;
import java.util.Optional;
public interface Position
{
Maybe<Integer> line();
Optional<Integer> line();
String lineContent();
Maybe<Integer> offset();
Optional<Integer> offset();
// pointer to the column position of the error/warning
Maybe<Integer> pointer();
Maybe<String> pointerSpace();
Optional<Integer> pointer();
Optional<String> pointerSpace();
Maybe<String> sourcePath();
Maybe<java.io.File> sourceFile();
}
Optional<String> sourcePath();
Optional<File> sourceFile();
}

View File

@ -1,5 +1,5 @@
/**
* This code is generated using sbt-datatype.
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY

View File

@ -1,51 +0,0 @@
/**
* This code is generated using sbt-datatype.
*/
// DO NOT EDIT MANUALLY
package sbt.internal.util
final class ChannelLogEntry private (
val level: String,
val message: String,
channelName: Option[String],
execId: Option[String]) extends sbt.internal.util.AbstractEntry(channelName, execId) with Serializable {
override def equals(o: Any): Boolean = o match {
case x: ChannelLogEntry => (this.level == x.level) && (this.message == x.message) && (this.channelName == x.channelName) && (this.execId == x.execId)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (37 * (17 + level.##) + message.##) + channelName.##) + execId.##)
}
override def toString: String = {
"ChannelLogEntry(" + level + ", " + message + ", " + channelName + ", " + execId + ")"
}
protected[this] def copy(level: String = level, message: String = message, channelName: Option[String] = channelName, execId: Option[String] = execId): ChannelLogEntry = {
new ChannelLogEntry(level, message, channelName, execId)
}
def withLevel(level: String): ChannelLogEntry = {
copy(level = level)
}
def withMessage(message: String): ChannelLogEntry = {
copy(message = message)
}
def withChannelName(channelName: Option[String]): ChannelLogEntry = {
copy(channelName = channelName)
}
def withChannelName(channelName: String): ChannelLogEntry = {
copy(channelName = Option(channelName))
}
def withExecId(execId: Option[String]): ChannelLogEntry = {
copy(execId = execId)
}
def withExecId(execId: String): ChannelLogEntry = {
copy(execId = Option(execId))
}
}
object ChannelLogEntry {
def apply(level: String, message: String, channelName: Option[String], execId: Option[String]): ChannelLogEntry = new ChannelLogEntry(level, message, channelName, execId)
def apply(level: String, message: String, channelName: String, execId: String): ChannelLogEntry = new ChannelLogEntry(level, message, Option(channelName), Option(execId))
}

View File

@ -0,0 +1,51 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.util
final class StringEvent private (
val level: String,
val message: String,
channelName: Option[String],
execId: Option[String]) extends sbt.internal.util.AbstractEntry(channelName, execId) with Serializable {
override def equals(o: Any): Boolean = o match {
case x: StringEvent => (this.level == x.level) && (this.message == x.message) && (this.channelName == x.channelName) && (this.execId == x.execId)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (37 * (17 + level.##) + message.##) + channelName.##) + execId.##)
}
override def toString: String = {
"StringEvent(" + level + ", " + message + ", " + channelName + ", " + execId + ")"
}
protected[this] def copy(level: String = level, message: String = message, channelName: Option[String] = channelName, execId: Option[String] = execId): StringEvent = {
new StringEvent(level, message, channelName, execId)
}
def withLevel(level: String): StringEvent = {
copy(level = level)
}
def withMessage(message: String): StringEvent = {
copy(message = message)
}
def withChannelName(channelName: Option[String]): StringEvent = {
copy(channelName = channelName)
}
def withChannelName(channelName: String): StringEvent = {
copy(channelName = Option(channelName))
}
def withExecId(execId: Option[String]): StringEvent = {
copy(execId = execId)
}
def withExecId(execId: String): StringEvent = {
copy(execId = Option(execId))
}
}
object StringEvent {
def apply(level: String, message: String, channelName: Option[String], execId: Option[String]): StringEvent = new StringEvent(level, message, channelName, execId)
def apply(level: String, message: String, channelName: String, execId: String): StringEvent = new StringEvent(level, message, Option(channelName), Option(execId))
}

View File

@ -1,10 +1,10 @@
/**
* This code is generated using sbt-datatype.
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder }
trait AbstractEntryFormats { self: sjsonnew.BasicJsonProtocol with sbt.internal.util.codec.ChannelLogEntryFormats =>
implicit lazy val AbstractEntryFormat: JsonFormat[sbt.internal.util.AbstractEntry] = flatUnionFormat1[sbt.internal.util.AbstractEntry, sbt.internal.util.ChannelLogEntry]("type")
trait AbstractEntryFormats { self: sjsonnew.BasicJsonProtocol with sbt.internal.util.codec.StringEventFormats =>
implicit lazy val AbstractEntryFormat: JsonFormat[sbt.internal.util.AbstractEntry] = flatUnionFormat1[sbt.internal.util.AbstractEntry, sbt.internal.util.StringEvent]("type")
}

View File

@ -1,10 +1,10 @@
/**
* This code is generated using sbt-datatype.
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.util.codec
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.internal.util.codec.ChannelLogEntryFormats
with sbt.internal.util.codec.StringEventFormats
with sbt.internal.util.codec.AbstractEntryFormats
object JsonProtocol extends JsonProtocol

View File

@ -1,13 +1,13 @@
/**
* This code is generated using sbt-datatype.
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder }
trait ChannelLogEntryFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val ChannelLogEntryFormat: JsonFormat[sbt.internal.util.ChannelLogEntry] = new JsonFormat[sbt.internal.util.ChannelLogEntry] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.util.ChannelLogEntry = {
trait StringEventFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val StringEventFormat: JsonFormat[sbt.internal.util.StringEvent] = new JsonFormat[sbt.internal.util.StringEvent] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.util.StringEvent = {
jsOpt match {
case Some(js) =>
unbuilder.beginObject(js)
@ -16,12 +16,12 @@ implicit lazy val ChannelLogEntryFormat: JsonFormat[sbt.internal.util.ChannelLog
val channelName = unbuilder.readField[Option[String]]("channelName")
val execId = unbuilder.readField[Option[String]]("execId")
unbuilder.endObject()
sbt.internal.util.ChannelLogEntry(level, message, channelName, execId)
sbt.internal.util.StringEvent(level, message, channelName, execId)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.util.ChannelLogEntry, builder: Builder[J]): Unit = {
override def write[J](obj: sbt.internal.util.StringEvent, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("level", obj.level)
builder.addField("message", obj.message)

View File

@ -0,0 +1,26 @@
package sbt.internal.util
@target(Java)
@codecPackage("sbt.internal.util.codec")
@fullCodec("JsonProtocol")
enum Severity
{
Info, Warn, Error
}
type Position {
line: Int
lineContent: String!
offset: Int
pointer: Int
pointerSpace: String
sourcePath: String
sourceFile: java.io.File
}
type Problem {
category: String!
severity: Severity!
message: String!
position: Position!
}

View File

@ -8,7 +8,7 @@ interface AbstractEntry {
execId: String
}
type ChannelLogEntry implements sbt.internal.util.AbstractEntry {
type StringEvent implements sbt.internal.util.AbstractEntry {
level: String!
message: String!
channelName: String

View File

@ -5,6 +5,76 @@ package sbt.internal.util
import sbt.util._
import scala.collection.mutable.ListBuffer
import org.apache.logging.log4j.core.{ LogEvent => XLogEvent, Appender }
import org.apache.logging.log4j.core.appender.AbstractAppender
import org.apache.logging.log4j.core.layout.PatternLayout
import java.util.concurrent.atomic.AtomicInteger
object BufferedAppender {
def generateName: String =
"buffered-" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger
def apply(delegate: Appender): BufferedAppender =
apply(generateName, delegate)
def apply(name: String, delegate: Appender): BufferedAppender =
{
val appender = new BufferedAppender(name, delegate)
appender.start
appender
}
}
/**
* Am appender that can buffer the logging done on it and then can flush the buffer
* to the delegate appender provided in the constructor. Use 'record()' to
* start buffering and then 'play' to flush the buffer to the backing appender.
* The logging level set at the time a message is originally logged is used, not
* the level at the time 'play' is called.
*/
class BufferedAppender private[BufferedAppender] (name: String, delegate: Appender) extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
private[this] val buffer = new ListBuffer[XLogEvent]
private[this] var recording = false
def append(event: XLogEvent): Unit =
{
if (recording) {
buffer += event
} else delegate.append(event)
}
/** Enables buffering. */
def record() = synchronized { recording = true }
def buffer[T](f: => T): T = {
record()
try { f }
finally { stopQuietly() }
}
def bufferQuietly[T](f: => T): T = {
record()
try {
val result = f
clearBuffer()
result
} catch { case e: Throwable => stopQuietly(); throw e }
}
def stopQuietly() = synchronized { try { stopBuffer() } catch { case e: Exception => () } }
/**
* Flushes the buffer to the delegate logger. This method calls logAll on the delegate
* so that the messages are written consecutively. The buffer is cleared in the process.
*/
def play(): Unit =
synchronized {
buffer.toList foreach {
delegate.append
}
buffer.clear()
}
/** Clears buffered events and disables buffering. */
def clearBuffer(): Unit = synchronized { buffer.clear(); recording = false }
/** Plays buffered events and disables buffering. */
def stopBuffer(): Unit = synchronized { play(); clearBuffer() }
}
/**
* A logger that can buffer the logging done on it and then can flush the buffer

View File

@ -5,11 +5,10 @@ import java.io.{ PrintStream, PrintWriter }
import java.util.Locale
import java.util.concurrent.atomic.AtomicInteger
import org.apache.logging.log4j.{ Level => XLevel }
import org.apache.logging.log4j.message.{ Message, ParameterizedMessage, ObjectMessage, ReusableObjectMessage }
import org.apache.logging.log4j.message.{ Message, ObjectMessage, ReusableObjectMessage }
import org.apache.logging.log4j.core.{ LogEvent => XLogEvent }
import org.apache.logging.log4j.core.appender.AbstractAppender
import org.apache.logging.log4j.core.layout.PatternLayout
import org.apache.logging.log4j.core.async.RingBufferLogEvent
import ConsoleAppender._
@ -149,7 +148,7 @@ object ConsoleAppender {
}
}
val formatEnabled =
val formatEnabled: Boolean =
{
import java.lang.Boolean.{ getBoolean, parseBoolean }
val value = System.getProperty("sbt.log.format")
@ -180,8 +179,16 @@ object ConsoleAppender {
def apply(out: PrintStream): ConsoleAppender = apply(generateName, ConsoleOut.printStreamOut(out))
def apply(out: PrintWriter): ConsoleAppender = apply(generateName, ConsoleOut.printWriterOut(out))
def apply(name: String = generateName, out: ConsoleOut = ConsoleOut.systemOut, ansiCodesSupported: Boolean = formatEnabled,
useColor: Boolean = formatEnabled, suppressedMessage: SuppressedTraceContext => Option[String] = noSuppressedMessage): ConsoleAppender =
def apply(): ConsoleAppender = apply(generateName, ConsoleOut.systemOut)
def apply(name: String): ConsoleAppender = apply(name, ConsoleOut.systemOut, formatEnabled, formatEnabled, noSuppressedMessage)
def apply(out: ConsoleOut): ConsoleAppender = apply(generateName, out, formatEnabled, formatEnabled, noSuppressedMessage)
def apply(name: String, out: ConsoleOut): ConsoleAppender = apply(name, out, formatEnabled, formatEnabled, noSuppressedMessage)
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
@ -238,24 +245,30 @@ class ConsoleAppender private[ConsoleAppender] (
{
val level = ConsoleAppender.toLevel(event.getLevel)
val message = event.getMessage
val str = messageToString(message)
appendLog(level, str)
// val str = messageToString(message)
appendMessage(level, message)
}
def messageToString(msg: Message): String =
def appendMessage(level: Level.Value, msg: Message): Unit =
msg match {
case p: ParameterizedMessage => p.getFormattedMessage
case r: RingBufferLogEvent => r.getFormattedMessage
case o: ObjectMessage => objectToString(o.getParameter)
case o: ReusableObjectMessage => objectToString(o.getParameter)
case _ => msg.getFormattedMessage
case o: ObjectMessage => objectToLines(o.getParameter) foreach { appendLog(level, _) }
case o: ReusableObjectMessage => objectToLines(o.getParameter) foreach { appendLog(level, _) }
case _ => appendLog(level, msg.getFormattedMessage)
}
def objectToString(o: AnyRef): String =
def objectToLines(o: AnyRef): Vector[String] =
o match {
case x: ChannelLogEntry => x.message
case _ => o.toString
case x: StringEvent => Vector(x.message)
case x: ObjectEvent[_] => objectEventToLines(x)
case _ => Vector(o.toString)
}
def objectEventToLines(oe: ObjectEvent[_]): Vector[String] =
{
val tag = oe.tag
LogExchange.stringCodec[AnyRef](tag) match {
case Some(codec) => codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector
case _ => Vector(oe.message.toString)
}
}
def messageColor(level: Level.Value) = RESET
def labelColor(level: Level.Value) =
level match {

View File

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

View File

@ -3,6 +3,8 @@ package sbt.internal.util
import sbt.util._
import org.apache.logging.log4j.{ Logger => XLogger }
import org.apache.logging.log4j.message.ObjectMessage
import sjsonnew.JsonFormat
import scala.reflect.runtime.universe.TypeTag
/**
* Delegates log events to the associated LogExchange.
@ -18,8 +20,32 @@ class ManagedLogger(
{
xlogger.log(
ConsoleAppender.toXLevel(level),
new ObjectMessage(ChannelLogEntry(level.toString, message, channelName, execId))
new ObjectMessage(StringEvent(level.toString, message, channelName, execId))
)
}
override def success(message: => String): Unit = xlogger.info(message)
def registerStringCodec[A: ShowLines: TypeTag]: Unit =
{
val tag = StringTypeTag[A]
val ev = implicitly[ShowLines[A]]
// println(s"registerStringCodec ${tag.key}")
val _ = LogExchange.getOrElseUpdateStringCodec(tag.key, ev)
}
final def debugEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Debug, event)
final def infoEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Info, event)
final def warnEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Warn, event)
final def errorEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Error, event)
def logEvent[A: JsonFormat: TypeTag](level: Level.Value, event: => A): Unit =
{
val v: A = event
val tag = StringTypeTag[A]
LogExchange.getOrElseUpdateJsonCodec(tag.key, implicitly[JsonFormat[A]])
// println("logEvent " + tag.key)
val entry: ObjectEvent[A] = new ObjectEvent(level, v, channelName, execId, tag.key)
xlogger.log(
ConsoleAppender.toXLevel(level),
new ObjectMessage(entry)
)
}
}

View File

@ -0,0 +1,15 @@
package sbt
package internal
package util
import sbt.util.Level
import sjsonnew.JsonFormat
final class ObjectEvent[A](
val level: Level.Value,
val message: A,
val channelName: Option[String],
val execId: Option[String],
val tag: String
) extends Serializable {
}

View File

@ -0,0 +1,29 @@
package sbt.internal.util
import scala.reflect.runtime.universe._
/** This is used to carry type information in JSON. */
final case class StringTypeTag[A](key: String) {
override def toString: String = key
}
object StringTypeTag {
def apply[A: TypeTag]: StringTypeTag[A] =
{
val tag = implicitly[TypeTag[A]]
val tpe = tag.tpe
val k = typeToString(tpe)
// println(tpe.getClass.toString + " " + k)
StringTypeTag[A](k)
}
def typeToString(tpe: Type): String =
tpe match {
case ref: TypeRef =>
if (ref.args.nonEmpty) {
val typeCon = ref.typeConstructor.typeSymbol.asType.fullName
val typeArgs = ref.typeArgs map typeToString
s"""$typeCon[${typeArgs.mkString(",")}]"""
} else tpe.toString
case _ => tpe.toString
}
}

View File

@ -0,0 +1,49 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import xsbti.Position
import java.util.Optional
trait PositionFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val PositionFormat: JsonFormat[Position] = new JsonFormat[Position] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Position = {
jsOpt match {
case Some(js) =>
unbuilder.beginObject(js)
val line0 = unbuilder.readField[Optional[java.lang.Integer]]("line")
val lineContent0 = unbuilder.readField[String]("lineContent")
val offset0 = unbuilder.readField[Optional[java.lang.Integer]]("offset")
val pointer0 = unbuilder.readField[Optional[java.lang.Integer]]("pointer")
val pointerSpace0 = unbuilder.readField[Optional[String]]("pointerSpace")
val sourcePath0 = unbuilder.readField[Optional[String]]("sourcePath")
val sourceFile0 = unbuilder.readField[Optional[java.io.File]]("sourceFile")
unbuilder.endObject()
new Position() {
override val line = line0
override val lineContent = lineContent0
override val offset = offset0
override val pointer = pointer0
override val pointerSpace = pointerSpace0
override val sourcePath = sourcePath0
override val sourceFile = sourceFile0
}
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: Position, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("line", obj.line)
builder.addField("lineContent", obj.lineContent)
builder.addField("offset", obj.offset)
builder.addField("pointer", obj.pointer)
builder.addField("pointerSpace", obj.pointerSpace)
builder.addField("sourcePath", obj.sourcePath)
builder.addField("sourceFile", obj.sourceFile)
builder.endObject()
}
}
}

View File

@ -0,0 +1,36 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
package sbt.internal.util.codec
import xsbti.{ Problem, Severity, Position }
import sbt.util.InterfaceUtil.problem
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
trait ProblemFormats { self: SeverityFormats with PositionFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val ProblemFormat: JsonFormat[Problem] = new JsonFormat[Problem] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Problem = {
jsOpt match {
case Some(js) =>
unbuilder.beginObject(js)
val category = unbuilder.readField[String]("category")
val severity = unbuilder.readField[Severity]("severity")
val message = unbuilder.readField[String]("message")
val position = unbuilder.readField[Position]("position")
unbuilder.endObject()
problem(category, position, message, severity)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: Problem, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("category", obj.category)
builder.addField("severity", obj.severity)
builder.addField("message", obj.message)
builder.addField("position", obj.position)
builder.endObject()
}
}
}

View File

@ -0,0 +1,33 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import xsbti.Severity;
trait SeverityFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val SeverityFormat: JsonFormat[Severity] = new JsonFormat[Severity] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Severity = {
jsOpt match {
case Some(js) =>
unbuilder.readString(js) match {
case "Info" => Severity.Info
case "Warn" => Severity.Warn
case "Error" => Severity.Error
}
case None =>
deserializationError("Expected JsString but found None")
}
}
override def write[J](obj: Severity, builder: Builder[J]): Unit = {
val str = obj match {
case Severity.Info => "Info"
case Severity.Warn => "Warn"
case Severity.Error => "Error"
}
builder.writeString(str)
}
}
}

View File

@ -2,6 +2,7 @@ package sbt.util
import xsbti.{ Maybe, F0, F1, T2, Position, Problem, Severity }
import java.io.File
import java.util.Optional
object InterfaceUtil {
def f0[A](a: => A): F0[A] = new ConcreteF0[A](a)
@ -18,6 +19,16 @@ object InterfaceUtil {
case None => Maybe.nothing()
}
def jo2o[A](o: Optional[A]): Option[A] =
if (o.isPresent) Some(o.get)
else None
def o2jo[A](o: Option[A]): Optional[A] =
o match {
case Some(v) => Optional.ofNullable(v)
case None => Optional.empty[A]()
}
def position(line0: Option[Integer], content: String, offset0: Option[Integer], pointer0: Option[Integer],
pointerSpace0: Option[String], sourcePath0: Option[String], sourceFile0: Option[File]): Position =
new ConcretePosition(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0)
@ -61,13 +72,13 @@ object InterfaceUtil {
sourcePath0: Option[String],
sourceFile0: Option[File]
) extends Position {
val line = o2m(line0)
val line = o2jo(line0)
val lineContent = content
val offset = o2m(offset0)
val pointer = o2m(pointer0)
val pointerSpace = o2m(pointerSpace0)
val sourcePath = o2m(sourcePath0)
val sourceFile = o2m(sourceFile0)
val offset = o2jo(offset0)
val pointer = o2jo(pointer0)
val pointerSpace = o2jo(pointerSpace0)
val sourcePath = o2jo(sourcePath0)
val sourceFile = o2jo(sourceFile0)
}
private final class ConcreteProblem(

View File

@ -6,6 +6,8 @@ import org.apache.logging.log4j.core._
import org.apache.logging.log4j.core.appender.AsyncAppender
import org.apache.logging.log4j.core.config.{ AppenderRef, LoggerConfig }
import scala.collection.JavaConverters._
import scala.collection.concurrent
import sjsonnew.JsonFormat
// http://logging.apache.org/log4j/2.x/manual/customconfig.html
// https://logging.apache.org/log4j/2.x/log4j-core/apidocs/index.html
@ -13,6 +15,8 @@ import scala.collection.JavaConverters._
sealed abstract class LogExchange {
private[sbt] lazy val context: LoggerContext = init()
private[sbt] lazy val asyncStdout: AsyncAppender = buildAsyncStdout
private[sbt] val jsonCodecs: concurrent.Map[String, JsonFormat[_]] = concurrent.TrieMap()
private[sbt] val stringCodecs: concurrent.Map[String, ShowLines[_]] = concurrent.TrieMap()
def logger(name: String): ManagedLogger = logger(name, None, None)
def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger = {
@ -43,6 +47,19 @@ sealed abstract class LogExchange {
val config = ctx.getConfiguration
config.getLoggerConfig(loggerName)
}
def jsonCodec[A](tag: String): Option[JsonFormat[A]] =
jsonCodecs.get(tag) map { _.asInstanceOf[JsonFormat[A]] }
def hasJsonCodec(tag: String): Boolean =
jsonCodecs.contains(tag)
def getOrElseUpdateJsonCodec[A](tag: String, v: JsonFormat[A]): JsonFormat[A] =
jsonCodecs.getOrElseUpdate(tag, v).asInstanceOf[JsonFormat[A]]
def stringCodec[A](tag: String): Option[ShowLines[A]] =
stringCodecs.get(tag) map { _.asInstanceOf[ShowLines[A]] }
def hasStringCodec(tag: String): Boolean =
stringCodecs.contains(tag)
def getOrElseUpdateStringCodec[A](tag: String, v: ShowLines[A]): ShowLines[A] =
stringCodecs.getOrElseUpdate(tag, v).asInstanceOf[ShowLines[A]]
private[sbt] def buildAsyncStdout: AsyncAppender = {
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
val config = ctx.getConfiguration

View File

@ -9,6 +9,7 @@ import sys.process.ProcessLogger
import sbt.internal.util.{ BufferedLogger, FullLogger }
import java.io.File
import java.util.Optional
/**
* This is intended to be the simplest logging interface for use by code that wants to log.
@ -88,6 +89,8 @@ object Logger {
def f0[A](a: => A): F0[A] = InterfaceUtil.f0[A](a)
def m2o[A](m: Maybe[A]): Option[A] = InterfaceUtil.m2o(m)
def o2m[A](o: Option[A]): Maybe[A] = InterfaceUtil.o2m(o)
def jo2o[A](o: Optional[A]): Option[A] = InterfaceUtil.jo2o(o)
def o2jo[A](o: Option[A]): Optional[A] = InterfaceUtil.o2jo(o)
def position(line0: Option[Integer], content: String, offset0: Option[Integer], pointer0: Option[Integer],
pointerSpace0: Option[String], sourcePath0: Option[String], sourceFile0: Option[File]): Position =
InterfaceUtil.position(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0)

View File

@ -13,6 +13,40 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
log.debug("test")
}
it should "support event logging" in {
import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo")
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
log.infoEvent(1)
}
it should "allow registering Show[Int]" in {
import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo")
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
implicit val intShow: ShowLines[Int] = ShowLines({ (x: Int) => Vector(s"String representation of $x") })
log.registerStringCodec[Int]
log.infoEvent(1)
}
it should "allow registering Show[Array[Int]]" in {
import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo")
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
implicit val intArrayShow: ShowLines[Array[Int]] = ShowLines({ (x: Array[Int]) => Vector(s"String representation of ${x.mkString}") })
log.registerStringCodec[Array[Int]]
log.infoEvent(Array(1, 2, 3))
}
it should "allow registering Show[Vector[Vector[Int]]]" in {
import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo")
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
implicit val intVectorShow: ShowLines[Vector[Vector[Int]]] = ShowLines({ (xss: Vector[Vector[Int]]) => Vector(s"String representation of $xss") })
log.registerStringCodec[Vector[Vector[Int]]]
log.infoEvent(Vector(Vector(1, 2, 3)))
}
"global logging" should "log immediately after initialization" in {
// this is passed into State normally
val global0 = initialGlobalLogging

View File

@ -3,18 +3,18 @@ package internal
package scripted
import java.io.File
import sbt.util.Logger
import sbt.internal.util.{ ConsoleLogger, BufferedLogger, FullLogger }
import sbt.util.{ Logger, LogExchange, Level }
import sbt.internal.util.{ ManagedLogger, ConsoleOut, MainAppender, ConsoleAppender, BufferedAppender }
import sbt.io.IO.wrapNull
import sbt.io.{ DirectoryFilter, HiddenFileFilter }
import sbt.io.syntax._
import sbt.internal.io.Resources
import java.util.concurrent.atomic.AtomicInteger
object ScriptedRunnerImpl {
def run(resourceBaseDirectory: File, bufferLog: Boolean, tests: Array[String], handlersProvider: HandlersProvider): Unit = {
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, handlersProvider)
val logger = ConsoleLogger()
logger.setLevel(sbt.util.Level.Debug)
val logger = newLogger
val allTests = get(tests, resourceBaseDirectory, logger) flatMap {
case ScriptedTest(group, name) =>
runner.scriptedTest(group, name, logger)
@ -26,29 +26,36 @@ object ScriptedRunnerImpl {
if (errors.nonEmpty)
sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n"))
}
def get(tests: Seq[String], baseDirectory: File, log: Logger): Seq[ScriptedTest] =
def get(tests: Seq[String], baseDirectory: File, log: ManagedLogger): Seq[ScriptedTest] =
if (tests.isEmpty) listTests(baseDirectory, log) else parseTests(tests)
def listTests(baseDirectory: File, log: Logger): Seq[ScriptedTest] =
def listTests(baseDirectory: File, log: ManagedLogger): Seq[ScriptedTest] =
(new ListTests(baseDirectory, _ => true, log)).listTests
def parseTests(in: Seq[String]): Seq[ScriptedTest] =
for (testString <- in) yield {
val Array(group, name) = testString.split("/").map(_.trim)
ScriptedTest(group, name)
}
private[sbt] val generateId: AtomicInteger = new AtomicInteger
private[sbt] def newLogger: ManagedLogger =
{
val loggerName = "scripted-" + generateId.incrementAndGet
val x = LogExchange.logger(loggerName)
x
}
}
final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handlersProvider: HandlersProvider) {
// import ScriptedTests._
private val testResources = new Resources(resourceBaseDirectory)
private val consoleAppender: ConsoleAppender = ConsoleAppender()
val ScriptFilename = "test"
val PendingScriptFilename = "pending"
def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[() => Option[String]] =
scriptedTest(group, name, Logger.xlog2Log(log))
def scriptedTest(group: String, name: String, log: Logger): Seq[() => Option[String]] =
def scriptedTest(group: String, name: String, log: ManagedLogger): Seq[() => Option[String]] =
scriptedTest(group, name, { _ => () }, log)
def scriptedTest(group: String, name: String, prescripted: File => Unit, log: Logger): Seq[() => Option[String]] = {
def scriptedTest(group: String, name: String, prescripted: File => Unit, log: ManagedLogger): Seq[() => Option[String]] = {
for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield {
val g = groupDir.getName
val n = nme.getName
@ -69,19 +76,20 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
}
}
private def scriptedTest(label: String, testDirectory: File, prescripted: File => Unit, log: Logger): Unit =
private def scriptedTest(label: String, testDirectory: File, prescripted: File => Unit, log: ManagedLogger): Unit =
{
val buffered = new BufferedLogger(new FullLogger(log))
buffered.setLevel(sbt.util.Level.Debug)
if (bufferLog)
val buffered = BufferedAppender(consoleAppender)
LogExchange.unbindLoggerAppenders(log.name)
LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil)
if (bufferLog) {
buffered.record()
}
def createParser() =
{
// val fileHandler = new FileCommands(testDirectory)
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
val scriptConfig = new ScriptConfig(label, testDirectory, buffered)
val scriptConfig = new ScriptConfig(label, testDirectory, log)
new TestScriptParser(handlersProvider getHandlers scriptConfig)
}
val (file, pending) = {
@ -98,31 +106,31 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
run(parser.parse(file))
}
def testFailed(): Unit = {
if (pending) buffered.clear() else buffered.stop()
buffered.error("x " + label + pendingString)
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
log.error("x " + label + pendingString)
}
try {
prescripted(testDirectory)
runTest()
buffered.info("+ " + label + pendingString)
log.info("+ " + label + pendingString)
if (pending) throw new PendingTestSuccessException(label)
} catch {
case e: TestException =>
testFailed()
e.getCause match {
case null | _: java.net.SocketException => buffered.error(" " + e.getMessage)
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
case _ => if (!pending) e.printStackTrace
}
if (!pending) throw e
case e: PendingTestSuccessException =>
testFailed()
buffered.error(" Mark as passing to remove this failure.")
log.error(" Mark as passing to remove this failure.")
throw e
case e: Exception =>
testFailed()
if (!pending) throw e
} finally { buffered.clear() }
} finally { buffered.clearBuffer() }
}
}

View File

@ -1 +1 @@
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0-M3")
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0-M4")