From a9377ce4a6b9d87b771b8668044478c26d73da48 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 30 Jan 2017 19:20:06 -0500 Subject: [PATCH] Implements registerStringCodec Uses TypeTag to recover the full name of type parameter, which is calculated by StringTypeTag. This is sent along in ObjectEvent. On the other end, we can lookup typeclass instances using the tag key. --- build.sbt | 4 +-- .../main/scala/sbt/internal/util/INode.scala | 2 +- .../scala/sbt/internal/util/Settings.scala | 11 +++--- .../main/scala/sbt/internal/util/Show.scala | 8 ----- .../src/main/scala/sbt/util/Show.scala | 12 +++++++ .../sbt/{internal => }/util/ShowLines.scala | 2 +- .../src/test/scala/SettingsExample.scala | 9 +++-- .../sbt/internal/util/ConsoleAppender.scala | 34 +++++++++++-------- .../sbt/internal/util/ManagedLogger.scala | 25 +++++++++----- .../scala/sbt/internal/util/ObjectEvent.scala | 4 +-- .../sbt/internal/util/StringTypeTag.scala | 29 ++++++++++++++++ .../src/main/scala/sbt/util/LogExchange.scala | 21 ++++++++---- .../src/test/scala/ManagedLoggerSpec.scala | 27 +++++++++++++++ 13 files changed, 135 insertions(+), 53 deletions(-) delete mode 100644 internal/util-collection/src/main/scala/sbt/internal/util/Show.scala create mode 100644 internal/util-collection/src/main/scala/sbt/util/Show.scala rename internal/util-collection/src/main/scala/sbt/{internal => }/util/ShowLines.scala (92%) create mode 100644 internal/util-logging/src/main/scala/sbt/internal/util/StringTypeTag.scala diff --git a/build.sbt b/build.sbt index becdb12cf..86898c559 100644 --- a/build.sbt +++ b/build.sbt @@ -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" ) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala b/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala index d85cadf3f..3c159ec11 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/INode.scala @@ -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") diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala index 97117e2dc..afa02c150 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Settings.scala @@ -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[_]]) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Show.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Show.scala deleted file mode 100644 index 4a0343ed7..000000000 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Show.scala +++ /dev/null @@ -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) } -} \ No newline at end of file diff --git a/internal/util-collection/src/main/scala/sbt/util/Show.scala b/internal/util-collection/src/main/scala/sbt/util/Show.scala new file mode 100644 index 000000000..20ac0565d --- /dev/null +++ b/internal/util-collection/src/main/scala/sbt/util/Show.scala @@ -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 + } +} diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/ShowLines.scala b/internal/util-collection/src/main/scala/sbt/util/ShowLines.scala similarity index 92% rename from internal/util-collection/src/main/scala/sbt/internal/util/ShowLines.scala rename to internal/util-collection/src/main/scala/sbt/util/ShowLines.scala index f99a1394c..65729d747 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/ShowLines.scala +++ b/internal/util-collection/src/main/scala/sbt/util/ShowLines.scala @@ -1,4 +1,4 @@ -package sbt.internal.util +package sbt.util trait ShowLines[A] { def showLines(a: A): Seq[String] diff --git a/internal/util-collection/src/test/scala/SettingsExample.scala b/internal/util-collection/src/test/scala/SettingsExample.scala index cf65d6c68..20536a5ef 100644 --- a/internal/util-collection/src/test/scala/SettingsExample.scala +++ b/internal/util-collection/src/test/scala/SettingsExample.scala @@ -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] = { diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala index 540f84436..98f404d34 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala @@ -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._ @@ -246,25 +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: StringEvent => x.message - case x: ObjectEvent[_] => x.message.toString - 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 { diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ManagedLogger.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ManagedLogger.scala index 1564b224e..a7b84990a 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ManagedLogger.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ManagedLogger.scala @@ -4,6 +4,7 @@ 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. @@ -24,16 +25,24 @@ class ManagedLogger( } override def success(message: => String): Unit = xlogger.info(message) - final def debugEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Debug, event) - final def infoEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Info, event) - final def warnEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Warn, event) - final def errorEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Error, event) - def logEvent[A: JsonFormat](level: Level.Value, event: => A): Unit = + 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 clazz: Class[A] = v.getClass.asInstanceOf[Class[A]] - val ev = LogExchange.getOrElseUpdateJsonCodec(clazz, implicitly[JsonFormat[A]]) - val entry: ObjectEvent[A] = new ObjectEvent(level, v, channelName, execId, ev, clazz) + 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) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ObjectEvent.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ObjectEvent.scala index 611af321b..13cddfb83 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ObjectEvent.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ObjectEvent.scala @@ -10,8 +10,6 @@ final class ObjectEvent[A]( val message: A, val channelName: Option[String], val execId: Option[String], - val ev: JsonFormat[A], - val clazz: Class[A] + val tag: String ) extends Serializable { - } diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/StringTypeTag.scala b/internal/util-logging/src/main/scala/sbt/internal/util/StringTypeTag.scala new file mode 100644 index 000000000..20d226884 --- /dev/null +++ b/internal/util-logging/src/main/scala/sbt/internal/util/StringTypeTag.scala @@ -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 + } +} diff --git a/internal/util-logging/src/main/scala/sbt/util/LogExchange.scala b/internal/util-logging/src/main/scala/sbt/util/LogExchange.scala index 8914b4998..1dc17804d 100644 --- a/internal/util-logging/src/main/scala/sbt/util/LogExchange.scala +++ b/internal/util-logging/src/main/scala/sbt/util/LogExchange.scala @@ -15,7 +15,8 @@ import sjsonnew.JsonFormat sealed abstract class LogExchange { private[sbt] lazy val context: LoggerContext = init() private[sbt] lazy val asyncStdout: AsyncAppender = buildAsyncStdout - private[sbt] val jsonCodecs: concurrent.Map[Class[_], JsonFormat[_]] = concurrent.TrieMap() + 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 = { @@ -46,12 +47,18 @@ sealed abstract class LogExchange { val config = ctx.getConfiguration config.getLoggerConfig(loggerName) } - def jsonCodec[A](clazz: Class[A]): Option[JsonFormat[A]] = - jsonCodecs.get(clazz) map { _.asInstanceOf[JsonFormat[A]] } - def hasJsonCodec[A](clazz: Class[A]): Boolean = - jsonCodecs.contains(clazz) - def getOrElseUpdateJsonCodec[A](clazz: Class[A], v: JsonFormat[A]): JsonFormat[A] = - jsonCodecs.getOrElseUpdate(clazz, v).asInstanceOf[JsonFormat[A]] + 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 } diff --git a/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala b/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala index f7871053f..80e42a64d 100644 --- a/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala +++ b/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala @@ -20,6 +20,33 @@ class ManagedLoggerSpec extends FlatSpec with Matchers { 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