mirror of https://github.com/sbt/sbt.git
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:
parent
6e2f77f852
commit
a9377ce4a6
|
|
@ -99,12 +99,12 @@ lazy val utilComplete = (project in internalPath / "util-complete").
|
||||||
// logging
|
// logging
|
||||||
lazy val utilLogging = (project in internalPath / "util-logging").
|
lazy val utilLogging = (project in internalPath / "util-logging").
|
||||||
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
|
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
|
||||||
dependsOn(utilInterface, utilTesting % Test).
|
dependsOn(utilInterface, utilCollection, utilTesting % Test).
|
||||||
settings(
|
settings(
|
||||||
commonSettings,
|
commonSettings,
|
||||||
crossScalaVersions := Seq(scala210, scala211, scala212),
|
crossScalaVersions := Seq(scala210, scala211, scala212),
|
||||||
name := "Util Logging",
|
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"
|
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ abstract class EvaluateSettings[Scope] {
|
||||||
keyString
|
keyString
|
||||||
|
|
||||||
private[this] def 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 {
|
final def get: T = synchronized {
|
||||||
assert(value != null, toString + " not evaluated")
|
assert(value != null, toString + " not evaluated")
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package sbt.internal.util
|
||||||
import scala.language.existentials
|
import scala.language.existentials
|
||||||
|
|
||||||
import Types._
|
import Types._
|
||||||
|
import sbt.util.Show
|
||||||
|
|
||||||
sealed trait Settings[Scope] {
|
sealed trait Settings[Scope] {
|
||||||
def data: Map[Scope, AttributeMap]
|
def data: Map[Scope, AttributeMap]
|
||||||
|
|
@ -119,7 +120,7 @@ trait Init[Scope] {
|
||||||
def mapScope(f: Scope => Scope): MapScoped = new MapScoped {
|
def mapScope(f: Scope => Scope): MapScoped = new MapScoped {
|
||||||
def apply[T](k: ScopedKey[T]): ScopedKey[T] = k.copy(scope = f(k.scope))
|
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[_]] =
|
private[this] def applyDefaults(ss: Seq[Setting[_]]): Seq[Setting[_]] =
|
||||||
{
|
{
|
||||||
|
|
@ -215,11 +216,11 @@ trait Init[Scope] {
|
||||||
{
|
{
|
||||||
val guessed = guessIntendedScope(validKeys, delegates, u.referencedKey)
|
val guessed = guessIntendedScope(validKeys, delegates, u.referencedKey)
|
||||||
val derived = u.defining.isDerived
|
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 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 ""
|
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 =
|
private[this] def parenPosString(s: Setting[_]): String =
|
||||||
s.positionString match { case None => ""; case Some(s) => " (" + s + ")" }
|
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 ")
|
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]]) {
|
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[_]])
|
final class Flattened(val key: ScopedKey[_], val dependencies: Iterable[ScopedKey[_]])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package sbt.internal.util
|
package sbt.util
|
||||||
|
|
||||||
trait ShowLines[A] {
|
trait ShowLines[A] {
|
||||||
def showLines(a: A): Seq[String]
|
def showLines(a: A): Seq[String]
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package sbt.internal.util
|
package sbt.internal.util
|
||||||
|
|
||||||
|
import sbt.util.Show
|
||||||
|
|
||||||
/** Define our settings system */
|
/** Define our settings system */
|
||||||
|
|
||||||
// A basic scope indexed by an integer.
|
// 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.)
|
// That would be a general pain.)
|
||||||
case class SettingsExample() extends Init[Scope] {
|
case class SettingsExample() extends Init[Scope] {
|
||||||
// Provides a way of showing a Scope+AttributeKey[_]
|
// Provides a way of showing a Scope+AttributeKey[_]
|
||||||
val showFullKey: Show[ScopedKey[_]] = new Show[ScopedKey[_]] {
|
val showFullKey: Show[ScopedKey[_]] = Show[ScopedKey[_]]((key: ScopedKey[_]) =>
|
||||||
def apply(key: ScopedKey[_]) = s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}"
|
{
|
||||||
}
|
s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}"
|
||||||
|
})
|
||||||
|
|
||||||
// A sample delegation function that delegates to a Scope with a lower index.
|
// A sample delegation function that delegates to a Scope with a lower index.
|
||||||
val delegates: Scope => Seq[Scope] = {
|
val delegates: Scope => Seq[Scope] = {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,10 @@ import java.io.{ PrintStream, PrintWriter }
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import org.apache.logging.log4j.{ Level => XLevel }
|
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.{ LogEvent => XLogEvent }
|
||||||
import org.apache.logging.log4j.core.appender.AbstractAppender
|
import org.apache.logging.log4j.core.appender.AbstractAppender
|
||||||
import org.apache.logging.log4j.core.layout.PatternLayout
|
import org.apache.logging.log4j.core.layout.PatternLayout
|
||||||
import org.apache.logging.log4j.core.async.RingBufferLogEvent
|
|
||||||
|
|
||||||
import ConsoleAppender._
|
import ConsoleAppender._
|
||||||
|
|
||||||
|
|
@ -246,25 +245,30 @@ class ConsoleAppender private[ConsoleAppender] (
|
||||||
{
|
{
|
||||||
val level = ConsoleAppender.toLevel(event.getLevel)
|
val level = ConsoleAppender.toLevel(event.getLevel)
|
||||||
val message = event.getMessage
|
val message = event.getMessage
|
||||||
val str = messageToString(message)
|
// val str = messageToString(message)
|
||||||
appendLog(level, str)
|
appendMessage(level, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
def messageToString(msg: Message): String =
|
def appendMessage(level: Level.Value, msg: Message): Unit =
|
||||||
msg match {
|
msg match {
|
||||||
case p: ParameterizedMessage => p.getFormattedMessage
|
case o: ObjectMessage => objectToLines(o.getParameter) foreach { appendLog(level, _) }
|
||||||
case r: RingBufferLogEvent => r.getFormattedMessage
|
case o: ReusableObjectMessage => objectToLines(o.getParameter) foreach { appendLog(level, _) }
|
||||||
case o: ObjectMessage => objectToString(o.getParameter)
|
case _ => appendLog(level, msg.getFormattedMessage)
|
||||||
case o: ReusableObjectMessage => objectToString(o.getParameter)
|
|
||||||
case _ => msg.getFormattedMessage
|
|
||||||
}
|
}
|
||||||
def objectToString(o: AnyRef): String =
|
def objectToLines(o: AnyRef): Vector[String] =
|
||||||
o match {
|
o match {
|
||||||
case x: StringEvent => x.message
|
case x: StringEvent => Vector(x.message)
|
||||||
case x: ObjectEvent[_] => x.message.toString
|
case x: ObjectEvent[_] => objectEventToLines(x)
|
||||||
case _ => o.toString
|
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 messageColor(level: Level.Value) = RESET
|
||||||
def labelColor(level: Level.Value) =
|
def labelColor(level: Level.Value) =
|
||||||
level match {
|
level match {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import sbt.util._
|
||||||
import org.apache.logging.log4j.{ Logger => XLogger }
|
import org.apache.logging.log4j.{ Logger => XLogger }
|
||||||
import org.apache.logging.log4j.message.ObjectMessage
|
import org.apache.logging.log4j.message.ObjectMessage
|
||||||
import sjsonnew.JsonFormat
|
import sjsonnew.JsonFormat
|
||||||
|
import scala.reflect.runtime.universe.TypeTag
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegates log events to the associated LogExchange.
|
* Delegates log events to the associated LogExchange.
|
||||||
|
|
@ -24,16 +25,24 @@ class ManagedLogger(
|
||||||
}
|
}
|
||||||
override def success(message: => String): Unit = xlogger.info(message)
|
override def success(message: => String): Unit = xlogger.info(message)
|
||||||
|
|
||||||
final def debugEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Debug, event)
|
def registerStringCodec[A: ShowLines: TypeTag]: Unit =
|
||||||
final def infoEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Info, event)
|
{
|
||||||
final def warnEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Warn, event)
|
val tag = StringTypeTag[A]
|
||||||
final def errorEvent[A: JsonFormat](event: => A): Unit = logEvent(Level.Error, event)
|
val ev = implicitly[ShowLines[A]]
|
||||||
def logEvent[A: JsonFormat](level: Level.Value, event: => A): Unit =
|
// 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 v: A = event
|
||||||
val clazz: Class[A] = v.getClass.asInstanceOf[Class[A]]
|
val tag = StringTypeTag[A]
|
||||||
val ev = LogExchange.getOrElseUpdateJsonCodec(clazz, implicitly[JsonFormat[A]])
|
LogExchange.getOrElseUpdateJsonCodec(tag.key, implicitly[JsonFormat[A]])
|
||||||
val entry: ObjectEvent[A] = new ObjectEvent(level, v, channelName, execId, ev, clazz)
|
// println("logEvent " + tag.key)
|
||||||
|
val entry: ObjectEvent[A] = new ObjectEvent(level, v, channelName, execId, tag.key)
|
||||||
xlogger.log(
|
xlogger.log(
|
||||||
ConsoleAppender.toXLevel(level),
|
ConsoleAppender.toXLevel(level),
|
||||||
new ObjectMessage(entry)
|
new ObjectMessage(entry)
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ final class ObjectEvent[A](
|
||||||
val message: A,
|
val message: A,
|
||||||
val channelName: Option[String],
|
val channelName: Option[String],
|
||||||
val execId: Option[String],
|
val execId: Option[String],
|
||||||
val ev: JsonFormat[A],
|
val tag: String
|
||||||
val clazz: Class[A]
|
|
||||||
) extends Serializable {
|
) extends Serializable {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,8 @@ import sjsonnew.JsonFormat
|
||||||
sealed abstract class LogExchange {
|
sealed abstract class LogExchange {
|
||||||
private[sbt] lazy val context: LoggerContext = init()
|
private[sbt] lazy val context: LoggerContext = init()
|
||||||
private[sbt] lazy val asyncStdout: AsyncAppender = buildAsyncStdout
|
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): ManagedLogger = logger(name, None, None)
|
||||||
def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger = {
|
def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger = {
|
||||||
|
|
@ -46,12 +47,18 @@ sealed abstract class LogExchange {
|
||||||
val config = ctx.getConfiguration
|
val config = ctx.getConfiguration
|
||||||
config.getLoggerConfig(loggerName)
|
config.getLoggerConfig(loggerName)
|
||||||
}
|
}
|
||||||
def jsonCodec[A](clazz: Class[A]): Option[JsonFormat[A]] =
|
def jsonCodec[A](tag: String): Option[JsonFormat[A]] =
|
||||||
jsonCodecs.get(clazz) map { _.asInstanceOf[JsonFormat[A]] }
|
jsonCodecs.get(tag) map { _.asInstanceOf[JsonFormat[A]] }
|
||||||
def hasJsonCodec[A](clazz: Class[A]): Boolean =
|
def hasJsonCodec(tag: String): Boolean =
|
||||||
jsonCodecs.contains(clazz)
|
jsonCodecs.contains(tag)
|
||||||
def getOrElseUpdateJsonCodec[A](clazz: Class[A], v: JsonFormat[A]): JsonFormat[A] =
|
def getOrElseUpdateJsonCodec[A](tag: String, v: JsonFormat[A]): JsonFormat[A] =
|
||||||
jsonCodecs.getOrElseUpdate(clazz, v).asInstanceOf[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 = {
|
private[sbt] def buildAsyncStdout: AsyncAppender = {
|
||||||
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
|
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,33 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
||||||
log.infoEvent(1)
|
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 {
|
"global logging" should "log immediately after initialization" in {
|
||||||
// this is passed into State normally
|
// this is passed into State normally
|
||||||
val global0 = initialGlobalLogging
|
val global0 = initialGlobalLogging
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue