From 8c9dfda0895c352bd823b338dcb2882eafd9c99b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 13 Jan 2017 03:00:02 -0500 Subject: [PATCH 1/5] Split log output per channel LogManager implementation is modified to use ManagedLogger, which can swap out backing Appenders without re-creating the log instance. The State was also changed to track `currentCommand: Option[Exec]`. `Exec` knows the origin of the command invocation, and using that we can now send the network-originated events only to the network clients. Combined together, this implements log splitting between the sbt clients (channels). --- .../src/main/scala/sbt/BasicCommands.scala | 4 +- main-command/src/main/scala/sbt/Command.scala | 4 +- .../src/main/scala/sbt/Highlight.scala | 6 +- .../src/main/scala/sbt/MainLoop.scala | 23 ++-- main-command/src/main/scala/sbt/State.scala | 17 ++- .../scala/sbt/internal/CommandChannel.scala | 4 +- .../scala/sbt/internal/CommandExchange.scala | 71 +++++++++-- .../scala/sbt/internal/ConsoleChannel.scala | 5 +- .../scala/sbt/internal/RelayAppender.scala | 35 ++++++ .../sbt/internal/client/NetworkClient.scala | 19 ++- .../internal/client/ServerConnection.scala | 7 +- .../sbt/internal/server/NetworkChannel.scala | 11 +- main/src/main/scala/sbt/Keys.scala | 5 +- main/src/main/scala/sbt/Main.scala | 7 +- main/src/main/scala/sbt/Project.scala | 5 +- .../scala/sbt/{ => internal}/LogManager.scala | 112 ++++++++++++------ project/plugins.sbt | 2 +- .../scala/sbt/protocol/Serialization.scala | 54 ++++++++- sbt/src/main/scala/Import.scala | 7 -- 19 files changed, 292 insertions(+), 106 deletions(-) create mode 100644 main-command/src/main/scala/sbt/internal/RelayAppender.scala rename main/src/main/scala/sbt/{ => internal}/LogManager.scala (52%) diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index e0f744758..087ab6351 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -196,10 +196,10 @@ object BasicCommands { def server = Command.command(Server, Help.more(Server, ServerDetailed)) { s0 => val exchange = State.exchange val s1 = exchange.run(s0) - exchange.publishEvent(ConsolePromptEvent(s0)) + exchange.publishEventMessage(ConsolePromptEvent(s0)) val exec: Exec = exchange.blockUntilNextExec val newState = s1.copy(onFailure = Some(Exec(Server, None)), remainingCommands = exec +: Exec(Server, None) +: s1.remainingCommands).setInteractive(true) - exchange.publishEvent(ConsoleUnpromptEvent(exec.source)) + exchange.publishEventMessage(ConsoleUnpromptEvent(exec.source)) if (exec.commandLine.trim.isEmpty) newState else newState.clearGlobalLog } diff --git a/main-command/src/main/scala/sbt/Command.scala b/main-command/src/main/scala/sbt/Command.scala index 13321ab2a..1bed5a9be 100644 --- a/main-command/src/main/scala/sbt/Command.scala +++ b/main-command/src/main/scala/sbt/Command.scala @@ -92,7 +92,7 @@ object Command { def process(exec: Exec, state: State): State = { val channelName = exec.source map { _.channelName } - State.exchange.publishEvent(ExecStatusEvent("Processing", channelName, exec.execId, Vector())) + State.exchange.publishEventMessage(ExecStatusEvent("Processing", channelName, exec.execId, Vector())) val parser = combine(state.definedCommands) val newState = parse(exec.commandLine, parser(state)) match { case Right(s) => s() // apply command. command side effects happen here @@ -100,7 +100,7 @@ object Command { state.log.error(errMsg) state.fail } - State.exchange.publishEvent(ExecStatusEvent("Done", channelName, exec.execId, newState.remainingCommands.toVector map { _.commandLine })) + State.exchange.publishEventMessage(ExecStatusEvent("Done", channelName, exec.execId, newState.remainingCommands.toVector map { _.commandLine })) newState } def invalidValue(label: String, allowed: Iterable[String])(value: String): String = diff --git a/main-command/src/main/scala/sbt/Highlight.scala b/main-command/src/main/scala/sbt/Highlight.scala index 6cb7eb8f4..8a2005941 100644 --- a/main-command/src/main/scala/sbt/Highlight.scala +++ b/main-command/src/main/scala/sbt/Highlight.scala @@ -3,14 +3,14 @@ package sbt import java.util.regex.Pattern import scala.Console.{ BOLD, RESET } -import sbt.internal.util.ConsoleLogger +import sbt.internal.util.ConsoleAppender object Highlight { def showMatches(pattern: Pattern)(line: String): Option[String] = { val matcher = pattern.matcher(line) - if (ConsoleLogger.formatEnabled) { + if (ConsoleAppender.formatEnabled) { // ANSI codes like \033[39m (normal text color) don't work on Windows val highlighted = matcher.replaceAll(scala.Console.RED + "$0" + RESET) if (highlighted == line) None else Some(highlighted) @@ -19,5 +19,5 @@ object Highlight { else None } - def bold(s: String) = if (ConsoleLogger.formatEnabled) BOLD + s.replace(RESET, RESET + BOLD) + RESET else s + def bold(s: String) = if (ConsoleAppender.formatEnabled) BOLD + s.replace(RESET, RESET + BOLD) + RESET else s } diff --git a/main-command/src/main/scala/sbt/MainLoop.scala b/main-command/src/main/scala/sbt/MainLoop.scala index 62aa403d1..db07d48c7 100644 --- a/main-command/src/main/scala/sbt/MainLoop.scala +++ b/main-command/src/main/scala/sbt/MainLoop.scala @@ -66,21 +66,22 @@ object MainLoop { def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext = Using.fileWriter(append = true)(logBacking.file) { writer => val out = new java.io.PrintWriter(writer) - val newLogging = state.globalLogging.newLogger(out, logBacking) - transferLevels(state, newLogging) + val full = state.globalLogging.full + val newLogging = state.globalLogging.newAppender(full, out, logBacking) + // transferLevels(state, newLogging) val loggedState = state.copy(globalLogging = newLogging) try run(loggedState) finally out.close() } - /** Transfers logging and trace levels from the old global loggers to the new ones. */ - private[this] def transferLevels(state: State, logging: GlobalLogging): Unit = { - val old = state.globalLogging - Logger.transferLevels(old.backed, logging.backed) - (old.full, logging.full) match { // well, this is a hack - case (oldLog: AbstractLogger, newLog: AbstractLogger) => Logger.transferLevels(oldLog, newLog) - case _ => () - } - } + // /** Transfers logging and trace levels from the old global loggers to the new ones. */ + // private[this] def transferLevels(state: State, logging: GlobalLogging): Unit = { + // val old = state.globalLogging + // Logger.transferLevels(old.backed, logging.backed) + // (old.full, logging.full) match { // well, this is a hack + // case (oldLog: AbstractLogger, newLog: AbstractLogger) => Logger.transferLevels(oldLog, newLog) + // case _ => () + // } + // } sealed trait RunNext final class ClearGlobalLog(val state: State) extends RunNext diff --git a/main-command/src/main/scala/sbt/State.scala b/main-command/src/main/scala/sbt/State.scala index 733d07a5b..e08bbc5b6 100644 --- a/main-command/src/main/scala/sbt/State.scala +++ b/main-command/src/main/scala/sbt/State.scala @@ -32,10 +32,16 @@ final case class State( history: State.History, attributes: AttributeMap, globalLogging: GlobalLogging, - source: Option[CommandSource], + currentCommand: Option[Exec], next: State.Next ) extends Identity { lazy val combinedParser = Command.combine(definedCommands)(this) + + def source: Option[CommandSource] = + currentCommand match { + case Some(x) => x.source + case _ => None + } } trait Identity { @@ -76,11 +82,6 @@ trait StateOps { /** Sets the next command processing action to do.*/ def setNext(n: State.Next): State - /** Sets the current command source channel.*/ - def setSource(source: CommandSource): State - - @deprecated("Use setNext", "0.11.0") def setResult(ro: Option[xsbti.MainResult]): State - /** * Restarts sbt without dropping loaded Scala classes. It is a shallower restart than `reboot`. * This method takes a snapshot of the remaining commands and will resume executing those commands after reload. @@ -203,7 +204,7 @@ object State { case List() => exit(true) case x :: xs => log.debug(s"> $x") - f(x, s.copy(remainingCommands = xs, history = x :: s.history)) + f(x, s.copy(remainingCommands = xs, currentCommand = Some(x), history = x :: s.history)) } def :::(newCommands: List[String]): State = ++:(newCommands map { Exec(_, s.source) }) @@ -214,8 +215,6 @@ object State { def +(newCommand: Command): State = this ++ (newCommand :: Nil) def baseDir: File = s.configuration.baseDirectory def setNext(n: Next) = s.copy(next = n) - def setSource(x: CommandSource): State = s.copy(source = Some(x)) - def setResult(ro: Option[xsbti.MainResult]) = ro match { case None => continue; case Some(r) => setNext(new Return(r)) } def continue = setNext(Continue) def reboot(full: Boolean) = { runExitHooks(); throw new xsbti.FullReload((s.remainingCommands map { case e: Exec => e.commandLine }).toArray, full) } def reload = runExitHooks().setNext(new Return(defaultReload(s))) diff --git a/main-command/src/main/scala/sbt/internal/CommandChannel.scala b/main-command/src/main/scala/sbt/internal/CommandChannel.scala index b18965987..49709f0fb 100644 --- a/main-command/src/main/scala/sbt/internal/CommandChannel.scala +++ b/main-command/src/main/scala/sbt/internal/CommandChannel.scala @@ -3,6 +3,7 @@ package internal import java.util.concurrent.ConcurrentLinkedQueue import sbt.protocol.EventMessage +import sjsonnew.JsonFormat /** * A command channel represents an IO device such as network socket or human @@ -15,7 +16,8 @@ abstract class CommandChannel { commandQueue.add(exec) def poll: Option[Exec] = Option(commandQueue.poll) - def publishEvent(event: EventMessage): Unit + def publishEvent[A: JsonFormat](event: A): Unit + def publishEventMessage(event: EventMessage): Unit def publishBytes(bytes: Array[Byte]): Unit def shutdown(): Unit def name: String diff --git a/main-command/src/main/scala/sbt/internal/CommandExchange.scala b/main-command/src/main/scala/sbt/internal/CommandExchange.scala index f4b9b5358..737903071 100644 --- a/main-command/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main-command/src/main/scala/sbt/internal/CommandExchange.scala @@ -5,11 +5,13 @@ import java.net.SocketException import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger import sbt.internal.server._ +import sbt.internal.util.ChannelLogEntry import sbt.protocol.{ EventMessage, Serialization, ChannelAcceptedEvent } import scala.collection.mutable.ListBuffer import scala.annotation.tailrec import BasicKeys.serverPort import java.net.Socket +import sjsonnew.JsonFormat /** * The command exchange merges multiple command channels (e.g. network and console), @@ -74,7 +76,7 @@ private[sbt] final class CommandExchange { s.log.info(s"new client connected from: ${socket.getPort}") val channel = new NetworkChannel(newChannelName, socket) subscribe(channel) - channel.publishEvent(ChannelAcceptedEvent(channel.name)) + channel.publishEventMessage(ChannelAcceptedEvent(channel.name)) } server match { case Some(x) => // do nothing @@ -94,23 +96,28 @@ private[sbt] final class CommandExchange { server = None } - // fanout publisEvent - def publishEvent(event: EventMessage): Unit = + def publishEvent[A: JsonFormat](event: A): Unit = { val toDel: ListBuffer[CommandChannel] = ListBuffer.empty + val bytes = Serialization.serializeEvent(event) event match { - // Special treatment for ConsolePromptEvent since it's hand coded without codec. - case e: ConsolePromptEvent => - channels collect { - case c: ConsoleChannel => c.publishEvent(e) - } - case e: ConsoleUnpromptEvent => - channels collect { - case c: ConsoleChannel => c.publishEvent(e) + case entry: ChannelLogEntry => + channels.foreach { + case c: ConsoleChannel => + if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) { + c.publishEvent(event) + } + case c: NetworkChannel => + try { + if (entry.channelName == Some(c.name)) { + c.publishBytes(bytes) + } + } catch { + case e: SocketException => + toDel += c + } } case _ => - // TODO do not do this on the calling thread - val bytes = Serialization.serializeEvent(event) channels.foreach { case c: ConsoleChannel => c.publishEvent(event) @@ -131,4 +138,42 @@ private[sbt] final class CommandExchange { } } } + + // fanout publisEvent + def publishEventMessage(event: EventMessage): Unit = + { + val toDel: ListBuffer[CommandChannel] = ListBuffer.empty + event match { + // Special treatment for ConsolePromptEvent since it's hand coded without codec. + case e: ConsolePromptEvent => + channels collect { + case c: ConsoleChannel => c.publishEventMessage(e) + } + case e: ConsoleUnpromptEvent => + channels collect { + case c: ConsoleChannel => c.publishEventMessage(e) + } + case _ => + // TODO do not do this on the calling thread + val bytes = Serialization.serializeEventMessage(event) + channels.foreach { + case c: ConsoleChannel => + c.publishEventMessage(event) + case c: NetworkChannel => + try { + c.publishBytes(bytes) + } catch { + case e: SocketException => + toDel += c + } + } + } + toDel.toList match { + case Nil => // do nothing + case xs => + lock.synchronized { + channelBuffer --= xs + } + } + } } diff --git a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala index 2a22b5b46..b4a32270c 100644 --- a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala +++ b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala @@ -5,6 +5,7 @@ import sbt.internal.util._ import BasicKeys._ import java.io.File import sbt.protocol.EventMessage +import sjsonnew.JsonFormat private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel { private var askUserThread: Option[Thread] = None @@ -30,7 +31,9 @@ private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel def publishBytes(bytes: Array[Byte]): Unit = () - def publishEvent(event: EventMessage): Unit = + def publishEvent[A: JsonFormat](event: A): Unit = () + + def publishEventMessage(event: EventMessage): Unit = event match { case e: ConsolePromptEvent => askUserThread match { diff --git a/main-command/src/main/scala/sbt/internal/RelayAppender.scala b/main-command/src/main/scala/sbt/internal/RelayAppender.scala new file mode 100644 index 000000000..201c1aad5 --- /dev/null +++ b/main-command/src/main/scala/sbt/internal/RelayAppender.scala @@ -0,0 +1,35 @@ +package sbt +package internal + +import org.apache.logging.log4j.message._ +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 sbt.util.Level +import sbt.internal.util._ +import sbt.protocol.LogEvent +import sbt.internal.util.codec._ + +class RelayAppender(name: String) extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) { + import JsonProtocol._ + + def append(event: XLogEvent): Unit = + { + val level = ConsoleAppender.toLevel(event.getLevel) + val message = event.getMessage + message match { + case o: ObjectMessage => appendEvent(level, o.getParameter) + case p: ParameterizedMessage => appendLog(level, p.getFormattedMessage) + case r: RingBufferLogEvent => appendLog(level, r.getFormattedMessage) + case _ => appendLog(level, message.toString) + } + } + def appendLog(level: Level.Value, message: => String): Unit = { + State.exchange.publishEventMessage(LogEvent(level.toString, message)) + } + def appendEvent(level: Level.Value, event: AnyRef): Unit = + event match { + case x: ChannelLogEntry => State.exchange.publishEvent(x: AbstractEntry) + } +} diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index c66ad05e5..4df971c13 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -8,10 +8,11 @@ package client import java.net.{ URI, Socket, InetAddress, SocketException } import java.util.UUID import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } -import sbt.protocol._ -import sbt.internal.util.JLine import scala.collection.mutable.ListBuffer import scala.util.control.NonFatal +import sbt.protocol._ +import sbt.internal.util.{ JLine, ChannelLogEntry, ConsoleAppender } +import sbt.util.Level class NetworkClient(arguments: List[String]) { self => private val channelName = new AtomicReference("_") @@ -20,6 +21,8 @@ class NetworkClient(arguments: List[String]) { self => private val running = new AtomicBoolean(true) private val pendingExecIds = ListBuffer.empty[String] + private val console = ConsoleAppender("thin1") + def usageError = sys.error("Expecting: sbt client 127.0.0.1:port") val connection = init() start() @@ -44,6 +47,7 @@ class NetworkClient(arguments: List[String]) { self => val socket = new Socket(InetAddress.getByName(host), port) new ServerConnection(socket) { override def onEvent(event: EventMessage): Unit = self.onEvent(event) + override def onLogEntry(event: ChannelLogEntry): Unit = self.onLogEntry(event) override def onShutdown(): Unit = { running.set(false) @@ -51,6 +55,17 @@ class NetworkClient(arguments: List[String]) { self => } } + def onLogEntry(event: ChannelLogEntry): Unit = + { + val level = event.level match { + case "debug" => Level.Debug + case "info" => Level.Info + case "warn" => Level.Warn + case "error" => Level.Error + } + console.appendLog(level, event.message) + } + def onEvent(event: EventMessage): Unit = event match { case e: ChannelAcceptedEvent => diff --git a/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala b/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala index 5cecc2aef..793fbf04f 100644 --- a/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala +++ b/main-command/src/main/scala/sbt/internal/client/ServerConnection.scala @@ -8,6 +8,7 @@ package client import java.net.{ SocketTimeoutException, Socket } import java.util.concurrent.atomic.AtomicBoolean import sbt.protocol._ +import sbt.internal.util.ChannelLogEntry abstract class ServerConnection(connection: Socket) { @@ -39,7 +40,10 @@ abstract class ServerConnection(connection: Socket) { val s = new String(chunk.toArray, "UTF-8") println(s"Got invalid chunk from server: $s \n" + errorDesc) }, - onEvent + _ match { + case event: EventMessage => onEvent(event) + case event: ChannelLogEntry => onLogEntry(event) + } ) } @@ -61,6 +65,7 @@ abstract class ServerConnection(connection: Socket) { } def onEvent(event: EventMessage): Unit + def onLogEntry(event: ChannelLogEntry): Unit def onShutdown(): Unit diff --git a/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala index acdde4c50..aa69e5c54 100644 --- a/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -8,6 +8,7 @@ package server import java.net.{ Socket, SocketTimeoutException } import java.util.concurrent.atomic.AtomicBoolean import sbt.protocol.{ Serialization, CommandMessage, ExecCommand, EventMessage } +import sjsonnew.JsonFormat final class NetworkChannel(val name: String, connection: Socket) extends CommandChannel { private val running = new AtomicBoolean(true) @@ -50,9 +51,15 @@ final class NetworkChannel(val name: String, connection: Socket) extends Command } thread.start() - def publishEvent(event: EventMessage): Unit = + def publishEvent[A: JsonFormat](event: A): Unit = { - val bytes = Serialization.serializeEvent(event) + // val bytes = Serialization.serializeEvent(event) + // publishBytes(bytes) + } + + def publishEventMessage(event: EventMessage): Unit = + { + val bytes = Serialization.serializeEventMessage(event) publishBytes(bytes) } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 3666d01f5..091801642 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -28,7 +28,7 @@ import descriptor.ModuleDescriptor, id.ModuleRevisionId import testing.Framework import KeyRanks._ -import sbt.internal.{ BuildStructure, LoadedBuild, PluginDiscovery, BuildDependencies, SessionSettings } +import sbt.internal.{ BuildStructure, LoadedBuild, PluginDiscovery, BuildDependencies, SessionSettings, LogManager } import sbt.io.FileFilter import sbt.internal.io.WatchState import sbt.internal.util.{ AttributeKey, CacheStore, SourcePosition } @@ -73,6 +73,7 @@ import sbt.internal.librarymanagement.{ UnresolvedWarningConfiguration } import sbt.util.{ AbstractLogger, Level, Logger } +import org.apache.logging.log4j.core.Appender object Keys { val TraceValues = "-1 to disable, 0 for up to the first sbt frame, or a positive number to set the maximum number of frames shown." @@ -87,7 +88,7 @@ object Keys { val showSuccess = SettingKey[Boolean]("show-success", "If true, displays a success message after running a command successfully.", CSetting) val showTiming = SettingKey[Boolean]("show-timing", "If true, the command success message includes the completion time.", CSetting) val timingFormat = SettingKey[java.text.DateFormat]("timing-format", "The format used for displaying the completion time.", CSetting) - val extraLoggers = SettingKey[ScopedKey[_] => Seq[AbstractLogger]]("extra-loggers", "A function that provides additional loggers for a given setting.", DSetting) + val extraLoggers = SettingKey[ScopedKey[_] => Seq[Appender]]("extra-loggers", "A function that provides additional loggers for a given setting.", DSetting) val logManager = SettingKey[LogManager]("log-manager", "The log manager, which creates Loggers for different contexts.", DSetting) val logBuffered = SettingKey[Boolean]("log-buffered", "True if logging should be buffered until work completes.", CSetting) val sLog = SettingKey[Logger]("setting-logger", "Logger usable by settings during project loading.", CSetting) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 868811a09..8c550b030 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -19,9 +19,10 @@ import sbt.internal.{ ProjectNavigation, Script, SessionSettings, - SettingCompletions + SettingCompletions, + LogManager } -import sbt.internal.util.{ AttributeKey, AttributeMap, complete, ConsoleOut, GlobalLogging, LineRange, MainLogging, SimpleReader, Types } +import sbt.internal.util.{ AttributeKey, AttributeMap, complete, ConsoleOut, GlobalLogging, LineRange, MainAppender, SimpleReader, Types } import sbt.util.{ Level, Logger } import complete.{ DefaultParsers, Parser } @@ -87,7 +88,7 @@ object StandardMain { /** The common interface to standard output, used for all built-in ConsoleLoggers. */ val console = ConsoleOut.systemOutOverwrite(ConsoleOut.overwriteContaining("Resolving ")) - def initialGlobalLogging: GlobalLogging = GlobalLogging.initial(MainLogging.globalDefault(console), File.createTempFile("sbt", ".log"), console) + def initialGlobalLogging: GlobalLogging = GlobalLogging.initial(MainAppender.globalDefault(console), File.createTempFile("sbt", ".log"), console) def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State = { diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 038c066bc..a12aedc67 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -10,7 +10,7 @@ import Project._ import Keys.{ appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, serverPort, thisProject, thisProjectRef, watch } import Scope.{ GlobalScope, ThisScope } import Def.{ Flattened, Initialize, ScopedKey, Setting } -import sbt.internal.{ Load, BuildStructure, LoadedBuild, LoadedBuildUnit, SettingGraph, SettingCompletions, AddSettings, SessionSettings } +import sbt.internal.{ Load, BuildStructure, LoadedBuild, LoadedBuildUnit, SettingGraph, SettingCompletions, AddSettings, SessionSettings, LogManager } import sbt.internal.util.{ AttributeKey, AttributeMap, Dag, Relation, Settings, Show, ~> } import sbt.internal.util.Types.{ const, idFun } import sbt.internal.util.complete.DefaultParsers @@ -398,7 +398,8 @@ object Project extends ProjectExtra { val (onLoad, onUnload) = getHooks(structure.data) val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload) val newState = unloaded.copy(attributes = newAttrs) - onLoad(LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)) + // TODO: Fix this + onLoad(newState /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/ ) } def orIdentity[T](opt: Option[T => T]): T => T = opt getOrElse idFun diff --git a/main/src/main/scala/sbt/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala similarity index 52% rename from main/src/main/scala/sbt/LogManager.scala rename to main/src/main/scala/sbt/internal/LogManager.scala index 8d67b84ee..2bcf8de01 100644 --- a/main/src/main/scala/sbt/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -2,46 +2,67 @@ * Copyright 2010 Mark Harrah */ package sbt +package internal import java.io.PrintWriter import Def.ScopedKey import Scope.GlobalScope import Keys.{ logLevel, logManager, persistLogLevel, persistTraceLevel, sLog, traceLevel } import scala.Console.{ BLUE, RESET } -import sbt.internal.util.{ AttributeKey, ConsoleOut, MultiLoggerConfig, Settings, SuppressedTraceContext } -import sbt.internal.util.MainLogging._ -import sbt.util.{ AbstractLogger, Level, Logger } +import sbt.internal.util.{ AttributeKey, ConsoleOut, Settings, SuppressedTraceContext, MainAppender } +import MainAppender._ +import sbt.util.{ AbstractLogger, Level, Logger, LogExchange } +import org.apache.logging.log4j.core.Appender + +sealed abstract class LogManager { + def apply(data: Settings[Scope], state: State, task: ScopedKey[_], writer: PrintWriter): Logger +} object LogManager { + import java.util.concurrent.atomic.AtomicInteger + private val generateId: AtomicInteger = new AtomicInteger + + // This is called by mkStreams def construct(data: Settings[Scope], state: State) = (task: ScopedKey[_], to: PrintWriter) => { val manager = logManager in task.scope get data getOrElse defaultManager(state.globalLogging.console) manager(data, state, task, to) } - @deprecated("Use defaultManager to explicitly specify standard out.", "0.13.0") - lazy val default: LogManager = defaultManager(StandardMain.console) def defaultManager(console: ConsoleOut): LogManager = withLoggers((sk, s) => defaultScreen(console)) - @deprecated("Explicitly specify standard out.", "0.13.0") - def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = defaults(extra, StandardMain.console) - - def defaults(extra: ScopedKey[_] => Seq[AbstractLogger], console: ConsoleOut): LogManager = + // This is called by Defaults. + def defaults(extra: ScopedKey[_] => Seq[Appender], console: ConsoleOut): LogManager = withLoggers((task, state) => defaultScreen(console, suppressedMessage(task, state)), extra = extra) - def withScreenLogger(mk: (ScopedKey[_], State) => AbstractLogger): LogManager = withLoggers(screen = mk) + def withScreenLogger(mk: (ScopedKey[_], State) => Appender): LogManager = withLoggers(screen = mk) def withLoggers( - screen: (ScopedKey[_], State) => AbstractLogger = (sk, s) => defaultScreen(s.globalLogging.console), - backed: PrintWriter => AbstractLogger = defaultBacked(), - extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil - ): LogManager = new LogManager { + screen: (ScopedKey[_], State) => Appender = (sk, s) => defaultScreen(s.globalLogging.console), + backed: PrintWriter => Appender = defaultBacked(), + relay: Unit => Appender = defaultRelay, + extra: ScopedKey[_] => Seq[Appender] = _ => Nil + ): LogManager = new DefaultLogManager(screen, backed, relay, extra) + + private class DefaultLogManager( + screen: (ScopedKey[_], State) => Appender, + backed: PrintWriter => Appender, + relay: Unit => Appender, + extra: ScopedKey[_] => Seq[Appender] + ) extends LogManager { def apply(data: Settings[Scope], state: State, task: ScopedKey[_], to: PrintWriter): Logger = - defaultLogger(data, state, task, screen(task, state), backed(to), extra(task).toList) + defaultLogger(data, state, task, screen(task, state), backed(to), relay(()), extra(task).toList) } - def defaultLogger(data: Settings[Scope], state: State, task: ScopedKey[_], console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger]): Logger = + // This is the main function that is used to generat the logger for tasks. + def defaultLogger(data: Settings[Scope], state: State, task: ScopedKey[_], + console: Appender, backed: Appender, relay: Appender, extra: List[Appender]): Logger = { + val execOpt = state.currentCommand + val loggerName: String = s"${task.key.label}-${generateId.incrementAndGet}" + val channelName: Option[String] = execOpt flatMap { e => e.source map { _.channelName } } + val execId: Option[String] = execOpt flatMap { _.execId } + val log = LogExchange.logger(loggerName, channelName, execId) val scope = task.scope // to change from global being the default to overriding, switch the order of state.get and data.get def getOr[T](key: AttributeKey[T], default: T): T = data.get(scope, key) orElse state.get(key) getOrElse default @@ -49,8 +70,20 @@ object LogManager { val backingLevel = getOr(persistLogLevel.key, Level.Debug) val screenTrace = getOr(traceLevel.key, defaultTraceLevel(state)) val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue) - val extraBacked = state.globalLogging.backed :: Nil - multiLogger(MultiLoggerConfig(console, backed, extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace)) + val extraBacked = state.globalLogging.backed :: relay :: Nil + val consoleOpt = + execOpt match { + case Some(x: Exec) => + x.source match { + // TODO: Fix this stringliness + case Some(x: CommandSource) if x.channelName == "console0" => Option(console) + case Some(x: CommandSource) => None + case _ => Option(console) + } + case _ => Option(console) + } + multiLogger(log, MainAppender.MainAppenderConfig(consoleOpt, backed, + extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace)) } def defaultTraceLevel(state: State): Int = if (state.interactive) -1 else Int.MaxValue @@ -66,24 +99,25 @@ object LogManager { case _ => key // should never get here } + // TODO: Fix this // if global logging levels are not explicitly set, set them from project settings - private[sbt] def setGlobalLogLevels(s: State, data: Settings[Scope]): State = - if (hasExplicitGlobalLogLevels(s)) - s - else { - val logging = s.globalLogging - def get[T](key: SettingKey[T]) = key in GlobalScope get data - def transfer(l: AbstractLogger, traceKey: SettingKey[Int], levelKey: SettingKey[Level.Value]): Unit = { - get(traceKey).foreach(l.setTrace) - get(levelKey).foreach(l.setLevel) - } - logging.full match { - case a: AbstractLogger => transfer(a, traceLevel, logLevel) - case _ => () - } - transfer(logging.backed, persistTraceLevel, persistLogLevel) - s - } + // private[sbt] def setGlobalLogLevels(s: State, data: Settings[Scope]): State = + // if (hasExplicitGlobalLogLevels(s)) + // s + // else { + // val logging = s.globalLogging + // def get[T](key: SettingKey[T]) = key in GlobalScope get data + // def transfer(l: AbstractLogger, traceKey: SettingKey[Int], levelKey: SettingKey[Level.Value]): Unit = { + // get(traceKey).foreach(l.setTrace) + // get(levelKey).foreach(l.setLevel) + // } + // logging.full match { + // case a: AbstractLogger => transfer(a, traceLevel, logLevel) + // case _ => () + // } + // transfer(logging.backed, persistTraceLevel, persistLogLevel) + // s + // } def setGlobalLogLevel(s: State, level: Level.Value): State = { s.globalLogging.full match { @@ -93,6 +127,10 @@ object LogManager { s.put(BasicKeys.explicitGlobalLogLevels, true).put(Keys.logLevel.key, level) } + // This is the default implementation for the relay appender + val defaultRelay: Unit => Appender = _ => defaultRelayImpl + private[this] lazy val defaultRelayImpl: RelayAppender = new RelayAppender("Relay0") + private[this] def hasExplicitGlobalLogLevels(s: State): Boolean = State.getBoolean(s, BasicKeys.explicitGlobalLogLevels, default = false) @@ -114,7 +152,3 @@ object LogManager { } } } - -trait LogManager { - def apply(data: Settings[Scope], state: State, task: ScopedKey[_], writer: PrintWriter): Logger -} diff --git a/project/plugins.sbt b/project/plugins.sbt index c0f2daf70..6ac2f3ed0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ scalaVersion := "2.10.6" addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.8") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.11") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.4") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.5") addSbtPlugin("com.typesafe.sbt" % "sbt-javaversioncheck" % "0.1.0") diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index 0ca01cb56..72cce8124 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -4,13 +4,20 @@ package sbt package protocol -import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter } -import scala.json.ast.unsafe.JValue -import sjsonnew.support.scalajson.unsafe.Parser +import sjsonnew.JsonFormat +import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter } +import scala.json.ast.unsafe.{ JValue, JObject, JString } import java.nio.ByteBuffer import scala.util.{ Success, Failure } +import sbt.internal.util.ChannelLogEntry object Serialization { + def serializeEvent[A: JsonFormat](event: A): Array[Byte] = + { + val json: JValue = Converter.toJson[A](event).get + CompactPrinter(json).getBytes("UTF-8") + } + def serializeCommand(command: CommandMessage): Array[Byte] = { import codec.JsonProtocol._ @@ -18,7 +25,7 @@ object Serialization { CompactPrinter(json).getBytes("UTF-8") } - def serializeEvent(event: EventMessage): Array[Byte] = + def serializeEventMessage(event: EventMessage): Array[Byte] = { import codec.JsonProtocol._ val json: JValue = Converter.toJson[EventMessage](event).get @@ -46,7 +53,44 @@ object Serialization { /** * @return A command or an invalid input description */ - def deserializeEvent(bytes: Seq[Byte]): Either[String, EventMessage] = + def deserializeEvent(bytes: Seq[Byte]): Either[String, Any] = + { + val buffer = ByteBuffer.wrap(bytes.toArray) + Parser.parseFromByteBuffer(buffer) match { + case Success(json) => + detectType(json) match { + case Some("ChannelLogEntry") => + import sbt.internal.util.codec.JsonProtocol._ + Converter.fromJson[ChannelLogEntry](json) match { + case Success(event) => Right(event) + case Failure(e) => Left(e.getMessage) + } + case _ => + import codec.JsonProtocol._ + Converter.fromJson[EventMessage](json) match { + case Success(event) => Right(event) + case Failure(e) => Left(e.getMessage) + } + } + case Failure(e) => + Left(s"Parse error: ${e.getMessage}") + } + } + + def detectType(json: JValue): Option[String] = + json match { + case JObject(fields) => + (fields find { _.field == "type" } map { _.value }) match { + case Some(JString(value)) => Some(value) + case _ => None + } + case _ => None + } + + /** + * @return A command or an invalid input description + */ + def deserializeEventMessage(bytes: Seq[Byte]): Either[String, EventMessage] = { val buffer = ByteBuffer.wrap(bytes.toArray) Parser.parseFromByteBuffer(buffer) match { diff --git a/sbt/src/main/scala/Import.scala b/sbt/src/main/scala/Import.scala index 79b857cb4..e0b8db19d 100644 --- a/sbt/src/main/scala/Import.scala +++ b/sbt/src/main/scala/Import.scala @@ -98,10 +98,6 @@ trait Import { type FullLogger = sbt.internal.util.FullLogger val FullReader = sbt.internal.util.FullReader type FullReader = sbt.internal.util.FullReader - val GlobalLogBacking = sbt.internal.util.GlobalLogBacking - type GlobalLogBacking = sbt.internal.util.GlobalLogBacking - val GlobalLogging = sbt.internal.util.GlobalLogging - type GlobalLogging = sbt.internal.util.GlobalLogging val HCons = sbt.internal.util.HCons type HCons[H, T <: HList] = sbt.internal.util.HCons[H, T] val HList = sbt.internal.util.HList @@ -128,12 +124,9 @@ trait Import { type LineReader = sbt.internal.util.LineReader val LoggerWriter = sbt.internal.util.LoggerWriter type LoggerWriter = sbt.internal.util.LoggerWriter - val MainLogging = sbt.internal.util.MainLogging type MessageOnlyException = sbt.internal.util.MessageOnlyException type ModifiedFileInfo = sbt.internal.util.ModifiedFileInfo type MultiLogger = sbt.internal.util.MultiLogger - val MultiLoggerConfig = sbt.internal.util.MultiLoggerConfig - type MultiLoggerConfig = sbt.internal.util.MultiLoggerConfig val NoPosition = sbt.internal.util.NoPosition val PMap = sbt.internal.util.PMap type PMap[K[_], V[_]] = sbt.internal.util.PMap[K, V] From b34e182d21abbaa48f22f4e721ad57a6ae2168cc Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 14 Jan 2017 00:06:58 -0500 Subject: [PATCH 2/5] Bump util, lm, and zinc --- main/src/test/scala/PluginCommandTest.scala | 4 ++-- project/Dependencies.scala | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/main/src/test/scala/PluginCommandTest.scala b/main/src/test/scala/PluginCommandTest.scala index dd5348d9b..262619463 100644 --- a/main/src/test/scala/PluginCommandTest.scala +++ b/main/src/test/scala/PluginCommandTest.scala @@ -5,7 +5,7 @@ import java.io._ import org.specs2.mutable.Specification import sbt.internal._ -import sbt.internal.util.{ AttributeEntry, AttributeMap, ConsoleOut, GlobalLogging, MainLogging, Settings } +import sbt.internal.util.{ AttributeEntry, AttributeMap, ConsoleOut, GlobalLogging, MainAppender, Settings } object PluginCommandTestPlugin0 extends AutoPlugin { override def requires = empty } @@ -109,7 +109,7 @@ object FakeState { List(), State.newHistory, attributes, - GlobalLogging.initial(MainLogging.globalDefault(ConsoleOut.systemOut), File.createTempFile("sbt", ".log"), ConsoleOut.systemOut), + GlobalLogging.initial(MainAppender.globalDefault(ConsoleOut.systemOut), File.createTempFile("sbt", ".log"), ConsoleOut.systemOut), None, State.Continue ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 8f930abb1..42ce4cc75 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -10,9 +10,9 @@ object Dependencies { // sbt modules private val ioVersion = "1.0.0-M9" - private val utilVersion = "1.0.0-M17" - private val lmVersion = "0.1.0-X3" - private val zincVersion = "1.0.0-X7" + private val utilVersion = "1.0.0-M18" + private val lmVersion = "1.0.0-X4" + private val zincVersion = "1.0.0-X8" private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From 9a33550c79c85a3d90a1e7eed17bf608812d6be8 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 14 Jan 2017 02:26:23 -0500 Subject: [PATCH 3/5] Fix the scripted actions/set --- main/src/main/scala/sbt/Project.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index a12aedc67..7aba1435d 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -399,7 +399,7 @@ object Project extends ProjectExtra { val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload) val newState = unloaded.copy(attributes = newAttrs) // TODO: Fix this - onLoad(newState /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/ ) + onLoad(updateCurrent(newState) /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/ ) } def orIdentity[T](opt: Option[T => T]): T => T = opt getOrElse idFun From 0365d7cb119dc6d9a8757948eb9cb49fc50fb69c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 14 Jan 2017 02:30:59 -0500 Subject: [PATCH 4/5] Uncomment publishEvent --- .../src/main/scala/sbt/internal/server/NetworkChannel.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala index aa69e5c54..9ed98a6e3 100644 --- a/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -53,8 +53,8 @@ final class NetworkChannel(val name: String, connection: Socket) extends Command def publishEvent[A: JsonFormat](event: A): Unit = { - // val bytes = Serialization.serializeEvent(event) - // publishBytes(bytes) + val bytes = Serialization.serializeEvent(event) + publishBytes(bytes) } def publishEventMessage(event: EventMessage): Unit = From 2b6253cc4a3dd58aab23ccbc63a913de209cb6c3 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 14 Jan 2017 03:00:07 -0500 Subject: [PATCH 5/5] Fix scripted project/extra --- sbt/src/sbt-test/project/extra/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt/src/sbt-test/project/extra/build.sbt b/sbt/src/sbt-test/project/extra/build.sbt index 03087c247..113584958 100644 --- a/sbt/src/sbt-test/project/extra/build.sbt +++ b/sbt/src/sbt-test/project/extra/build.sbt @@ -38,6 +38,6 @@ def addExtra2(s: State, extra: Seq[File]): State = else { val newID = ApplicationID(currentID).copy(extra = extra) - s.setResult(Some(reload.copy(app = newID))) + s.setNext(new State.Return(reload.copy(app = newID))) } }