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 3c9bf0e9e..e46fea4b8 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 @@ -252,8 +252,9 @@ class ConsoleAppender private[ConsoleAppender] ( } def objectToString(o: AnyRef): String = o match { - case x: ChannelLogEntry => x.message - case _ => o.toString + case x: ChannelLogEntry => x.message + case x: ObjectLogEntry[_] => x.message.toString + case _ => o.toString } def messageColor(level: Level.Value) = RESET 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 90c84b6aa..e3c110e3b 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 @@ -3,6 +3,7 @@ package sbt.internal.util import sbt.util._ import org.apache.logging.log4j.{ Logger => XLogger } import org.apache.logging.log4j.message.ObjectMessage +import sjsonnew.JsonFormat /** * Delegates log events to the associated LogExchange. @@ -22,4 +23,20 @@ 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 = + { + val v: A = event + val clazz: Class[A] = v.getClass.asInstanceOf[Class[A]] + val ev = LogExchange.getOrElseUpdateJsonCodec(clazz, implicitly[JsonFormat[A]]) + val entry: ObjectLogEntry[A] = new ObjectLogEntry(level, v, channelName, execId, ev, clazz) + xlogger.log( + ConsoleAppender.toXLevel(level), + new ObjectMessage(entry) + ) + } } diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ObjectLogEntry.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ObjectLogEntry.scala new file mode 100644 index 000000000..64ee0db71 --- /dev/null +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ObjectLogEntry.scala @@ -0,0 +1,17 @@ +package sbt +package internal +package util + +import sbt.util.Level +import sjsonnew.JsonFormat + +final class ObjectLogEntry[A]( + val level: Level.Value, + val message: A, + val channelName: Option[String], + val execId: Option[String], + val ev: JsonFormat[A], + val clazz: Class[A] +) extends Serializable { + +} 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 0f967da61..8914b4998 100644 --- a/internal/util-logging/src/main/scala/sbt/util/LogExchange.scala +++ b/internal/util-logging/src/main/scala/sbt/util/LogExchange.scala @@ -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,7 @@ 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[Class[_], JsonFormat[_]] = concurrent.TrieMap() def logger(name: String): ManagedLogger = logger(name, None, None) def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger = { @@ -43,6 +46,13 @@ 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]] + private[sbt] def buildAsyncStdout: AsyncAppender = { val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x } val config = ctx.getConfiguration diff --git a/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala b/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala index d063fbf2c..f7871053f 100644 --- a/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala +++ b/internal/util-logging/src/test/scala/ManagedLoggerSpec.scala @@ -13,6 +13,13 @@ 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) + } + "global logging" should "log immediately after initialization" in { // this is passed into State normally val global0 = initialGlobalLogging