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.
This commit is contained in:
Eugene Yokota 2017-01-30 19:20:06 -05:00
parent 6e2f77f852
commit a9377ce4a6
13 changed files with 135 additions and 53 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

@ -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 {

View File

@ -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)

View File

@ -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 {
}

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

@ -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 }

View File

@ -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