Merge pull request #2907 from eed3si9n/topic/logging

Split log output per channel
This commit is contained in:
eugene yokota 2017-01-14 08:34:44 -05:00 committed by GitHub
commit fd36a20183
22 changed files with 297 additions and 111 deletions

View File

@ -196,10 +196,10 @@ object BasicCommands {
def server = Command.command(Server, Help.more(Server, ServerDetailed)) { s0 => def server = Command.command(Server, Help.more(Server, ServerDetailed)) { s0 =>
val exchange = State.exchange val exchange = State.exchange
val s1 = exchange.run(s0) val s1 = exchange.run(s0)
exchange.publishEvent(ConsolePromptEvent(s0)) exchange.publishEventMessage(ConsolePromptEvent(s0))
val exec: Exec = exchange.blockUntilNextExec val exec: Exec = exchange.blockUntilNextExec
val newState = s1.copy(onFailure = Some(Exec(Server, None)), remainingCommands = exec +: Exec(Server, None) +: s1.remainingCommands).setInteractive(true) 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 if (exec.commandLine.trim.isEmpty) newState
else newState.clearGlobalLog else newState.clearGlobalLog
} }

View File

@ -92,7 +92,7 @@ object Command {
def process(exec: Exec, state: State): State = def process(exec: Exec, state: State): State =
{ {
val channelName = exec.source map { _.channelName } 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 parser = combine(state.definedCommands)
val newState = parse(exec.commandLine, parser(state)) match { val newState = parse(exec.commandLine, parser(state)) match {
case Right(s) => s() // apply command. command side effects happen here case Right(s) => s() // apply command. command side effects happen here
@ -100,7 +100,7 @@ object Command {
state.log.error(errMsg) state.log.error(errMsg)
state.fail 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 newState
} }
def invalidValue(label: String, allowed: Iterable[String])(value: String): String = def invalidValue(label: String, allowed: Iterable[String])(value: String): String =

View File

@ -3,14 +3,14 @@ package sbt
import java.util.regex.Pattern import java.util.regex.Pattern
import scala.Console.{ BOLD, RESET } import scala.Console.{ BOLD, RESET }
import sbt.internal.util.ConsoleLogger import sbt.internal.util.ConsoleAppender
object Highlight { object Highlight {
def showMatches(pattern: Pattern)(line: String): Option[String] = def showMatches(pattern: Pattern)(line: String): Option[String] =
{ {
val matcher = pattern.matcher(line) val matcher = pattern.matcher(line)
if (ConsoleLogger.formatEnabled) { if (ConsoleAppender.formatEnabled) {
// ANSI codes like \033[39m (normal text color) don't work on Windows // ANSI codes like \033[39m (normal text color) don't work on Windows
val highlighted = matcher.replaceAll(scala.Console.RED + "$0" + RESET) val highlighted = matcher.replaceAll(scala.Console.RED + "$0" + RESET)
if (highlighted == line) None else Some(highlighted) if (highlighted == line) None else Some(highlighted)
@ -19,5 +19,5 @@ object Highlight {
else else
None 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
} }

View File

@ -66,21 +66,22 @@ object MainLoop {
def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext = def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext =
Using.fileWriter(append = true)(logBacking.file) { writer => Using.fileWriter(append = true)(logBacking.file) { writer =>
val out = new java.io.PrintWriter(writer) val out = new java.io.PrintWriter(writer)
val newLogging = state.globalLogging.newLogger(out, logBacking) val full = state.globalLogging.full
transferLevels(state, newLogging) val newLogging = state.globalLogging.newAppender(full, out, logBacking)
// transferLevels(state, newLogging)
val loggedState = state.copy(globalLogging = newLogging) val loggedState = state.copy(globalLogging = newLogging)
try run(loggedState) finally out.close() try run(loggedState) finally out.close()
} }
/** Transfers logging and trace levels from the old global loggers to the new ones. */ // /** Transfers logging and trace levels from the old global loggers to the new ones. */
private[this] def transferLevels(state: State, logging: GlobalLogging): Unit = { // private[this] def transferLevels(state: State, logging: GlobalLogging): Unit = {
val old = state.globalLogging // val old = state.globalLogging
Logger.transferLevels(old.backed, logging.backed) // Logger.transferLevels(old.backed, logging.backed)
(old.full, logging.full) match { // well, this is a hack // (old.full, logging.full) match { // well, this is a hack
case (oldLog: AbstractLogger, newLog: AbstractLogger) => Logger.transferLevels(oldLog, newLog) // case (oldLog: AbstractLogger, newLog: AbstractLogger) => Logger.transferLevels(oldLog, newLog)
case _ => () // case _ => ()
} // }
} // }
sealed trait RunNext sealed trait RunNext
final class ClearGlobalLog(val state: State) extends RunNext final class ClearGlobalLog(val state: State) extends RunNext

View File

@ -32,10 +32,16 @@ final case class State(
history: State.History, history: State.History,
attributes: AttributeMap, attributes: AttributeMap,
globalLogging: GlobalLogging, globalLogging: GlobalLogging,
source: Option[CommandSource], currentCommand: Option[Exec],
next: State.Next next: State.Next
) extends Identity { ) extends Identity {
lazy val combinedParser = Command.combine(definedCommands)(this) lazy val combinedParser = Command.combine(definedCommands)(this)
def source: Option[CommandSource] =
currentCommand match {
case Some(x) => x.source
case _ => None
}
} }
trait Identity { trait Identity {
@ -76,11 +82,6 @@ trait StateOps {
/** Sets the next command processing action to do.*/ /** Sets the next command processing action to do.*/
def setNext(n: State.Next): State 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`. * 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. * 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 List() => exit(true)
case x :: xs => case x :: xs =>
log.debug(s"> $x") 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) }) def :::(newCommands: List[String]): State = ++:(newCommands map { Exec(_, s.source) })
@ -214,8 +215,6 @@ object State {
def +(newCommand: Command): State = this ++ (newCommand :: Nil) def +(newCommand: Command): State = this ++ (newCommand :: Nil)
def baseDir: File = s.configuration.baseDirectory def baseDir: File = s.configuration.baseDirectory
def setNext(n: Next) = s.copy(next = n) 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 continue = setNext(Continue)
def reboot(full: Boolean) = { runExitHooks(); throw new xsbti.FullReload((s.remainingCommands map { case e: Exec => e.commandLine }).toArray, full) } 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))) def reload = runExitHooks().setNext(new Return(defaultReload(s)))

View File

@ -3,6 +3,7 @@ package internal
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import sbt.protocol.EventMessage import sbt.protocol.EventMessage
import sjsonnew.JsonFormat
/** /**
* A command channel represents an IO device such as network socket or human * A command channel represents an IO device such as network socket or human
@ -15,7 +16,8 @@ abstract class CommandChannel {
commandQueue.add(exec) commandQueue.add(exec)
def poll: Option[Exec] = Option(commandQueue.poll) 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 publishBytes(bytes: Array[Byte]): Unit
def shutdown(): Unit def shutdown(): Unit
def name: String def name: String

View File

@ -5,11 +5,13 @@ import java.net.SocketException
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import sbt.internal.server._ import sbt.internal.server._
import sbt.internal.util.ChannelLogEntry
import sbt.protocol.{ EventMessage, Serialization, ChannelAcceptedEvent } import sbt.protocol.{ EventMessage, Serialization, ChannelAcceptedEvent }
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import scala.annotation.tailrec import scala.annotation.tailrec
import BasicKeys.serverPort import BasicKeys.serverPort
import java.net.Socket import java.net.Socket
import sjsonnew.JsonFormat
/** /**
* The command exchange merges multiple command channels (e.g. network and console), * 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}") s.log.info(s"new client connected from: ${socket.getPort}")
val channel = new NetworkChannel(newChannelName, socket) val channel = new NetworkChannel(newChannelName, socket)
subscribe(channel) subscribe(channel)
channel.publishEvent(ChannelAcceptedEvent(channel.name)) channel.publishEventMessage(ChannelAcceptedEvent(channel.name))
} }
server match { server match {
case Some(x) => // do nothing case Some(x) => // do nothing
@ -94,23 +96,28 @@ private[sbt] final class CommandExchange {
server = None server = None
} }
// fanout publisEvent def publishEvent[A: JsonFormat](event: A): Unit =
def publishEvent(event: EventMessage): Unit =
{ {
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
val bytes = Serialization.serializeEvent(event)
event match { event match {
// Special treatment for ConsolePromptEvent since it's hand coded without codec. case entry: ChannelLogEntry =>
case e: ConsolePromptEvent => channels.foreach {
channels collect { case c: ConsoleChannel =>
case c: ConsoleChannel => c.publishEvent(e) if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) {
} c.publishEvent(event)
case e: ConsoleUnpromptEvent => }
channels collect { case c: NetworkChannel =>
case c: ConsoleChannel => c.publishEvent(e) try {
if (entry.channelName == Some(c.name)) {
c.publishBytes(bytes)
}
} catch {
case e: SocketException =>
toDel += c
}
} }
case _ => case _ =>
// TODO do not do this on the calling thread
val bytes = Serialization.serializeEvent(event)
channels.foreach { channels.foreach {
case c: ConsoleChannel => case c: ConsoleChannel =>
c.publishEvent(event) 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
}
}
}
} }

View File

@ -5,6 +5,7 @@ import sbt.internal.util._
import BasicKeys._ import BasicKeys._
import java.io.File import java.io.File
import sbt.protocol.EventMessage import sbt.protocol.EventMessage
import sjsonnew.JsonFormat
private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel { private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel {
private var askUserThread: Option[Thread] = None 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 publishBytes(bytes: Array[Byte]): Unit = ()
def publishEvent(event: EventMessage): Unit = def publishEvent[A: JsonFormat](event: A): Unit = ()
def publishEventMessage(event: EventMessage): Unit =
event match { event match {
case e: ConsolePromptEvent => case e: ConsolePromptEvent =>
askUserThread match { askUserThread match {

View File

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

View File

@ -8,10 +8,11 @@ package client
import java.net.{ URI, Socket, InetAddress, SocketException } import java.net.{ URI, Socket, InetAddress, SocketException }
import java.util.UUID import java.util.UUID
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import sbt.protocol._
import sbt.internal.util.JLine
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import scala.util.control.NonFatal 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 => class NetworkClient(arguments: List[String]) { self =>
private val channelName = new AtomicReference("_") private val channelName = new AtomicReference("_")
@ -20,6 +21,8 @@ class NetworkClient(arguments: List[String]) { self =>
private val running = new AtomicBoolean(true) private val running = new AtomicBoolean(true)
private val pendingExecIds = ListBuffer.empty[String] private val pendingExecIds = ListBuffer.empty[String]
private val console = ConsoleAppender("thin1")
def usageError = sys.error("Expecting: sbt client 127.0.0.1:port") def usageError = sys.error("Expecting: sbt client 127.0.0.1:port")
val connection = init() val connection = init()
start() start()
@ -44,6 +47,7 @@ class NetworkClient(arguments: List[String]) { self =>
val socket = new Socket(InetAddress.getByName(host), port) val socket = new Socket(InetAddress.getByName(host), port)
new ServerConnection(socket) { new ServerConnection(socket) {
override def onEvent(event: EventMessage): Unit = self.onEvent(event) override def onEvent(event: EventMessage): Unit = self.onEvent(event)
override def onLogEntry(event: ChannelLogEntry): Unit = self.onLogEntry(event)
override def onShutdown(): Unit = override def onShutdown(): Unit =
{ {
running.set(false) 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 = def onEvent(event: EventMessage): Unit =
event match { event match {
case e: ChannelAcceptedEvent => case e: ChannelAcceptedEvent =>

View File

@ -8,6 +8,7 @@ package client
import java.net.{ SocketTimeoutException, Socket } import java.net.{ SocketTimeoutException, Socket }
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import sbt.protocol._ import sbt.protocol._
import sbt.internal.util.ChannelLogEntry
abstract class ServerConnection(connection: Socket) { abstract class ServerConnection(connection: Socket) {
@ -39,7 +40,10 @@ abstract class ServerConnection(connection: Socket) {
val s = new String(chunk.toArray, "UTF-8") val s = new String(chunk.toArray, "UTF-8")
println(s"Got invalid chunk from server: $s \n" + errorDesc) 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 onEvent(event: EventMessage): Unit
def onLogEntry(event: ChannelLogEntry): Unit
def onShutdown(): Unit def onShutdown(): Unit

View File

@ -8,6 +8,7 @@ package server
import java.net.{ Socket, SocketTimeoutException } import java.net.{ Socket, SocketTimeoutException }
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import sbt.protocol.{ Serialization, CommandMessage, ExecCommand, EventMessage } import sbt.protocol.{ Serialization, CommandMessage, ExecCommand, EventMessage }
import sjsonnew.JsonFormat
final class NetworkChannel(val name: String, connection: Socket) extends CommandChannel { final class NetworkChannel(val name: String, connection: Socket) extends CommandChannel {
private val running = new AtomicBoolean(true) private val running = new AtomicBoolean(true)
@ -50,12 +51,18 @@ final class NetworkChannel(val name: String, connection: Socket) extends Command
} }
thread.start() 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) publishBytes(bytes)
} }
def publishEventMessage(event: EventMessage): Unit =
{
val bytes = Serialization.serializeEventMessage(event)
publishBytes(bytes)
}
def publishBytes(event: Array[Byte]): Unit = def publishBytes(event: Array[Byte]): Unit =
{ {
out.write(event) out.write(event)

View File

@ -28,7 +28,7 @@ import descriptor.ModuleDescriptor, id.ModuleRevisionId
import testing.Framework import testing.Framework
import KeyRanks._ 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.io.FileFilter
import sbt.internal.io.WatchState import sbt.internal.io.WatchState
import sbt.internal.util.{ AttributeKey, CacheStore, SourcePosition } import sbt.internal.util.{ AttributeKey, CacheStore, SourcePosition }
@ -73,6 +73,7 @@ import sbt.internal.librarymanagement.{
UnresolvedWarningConfiguration UnresolvedWarningConfiguration
} }
import sbt.util.{ AbstractLogger, Level, Logger } import sbt.util.{ AbstractLogger, Level, Logger }
import org.apache.logging.log4j.core.Appender
object Keys { 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." 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 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 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 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 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 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) val sLog = SettingKey[Logger]("setting-logger", "Logger usable by settings during project loading.", CSetting)

View File

@ -19,9 +19,10 @@ import sbt.internal.{
ProjectNavigation, ProjectNavigation,
Script, Script,
SessionSettings, 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 sbt.util.{ Level, Logger }
import complete.{ DefaultParsers, Parser } import complete.{ DefaultParsers, Parser }
@ -87,7 +88,7 @@ object StandardMain {
/** The common interface to standard output, used for all built-in ConsoleLoggers. */ /** The common interface to standard output, used for all built-in ConsoleLoggers. */
val console = ConsoleOut.systemOutOverwrite(ConsoleOut.overwriteContaining("Resolving ")) 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 = def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State =
{ {

View File

@ -10,7 +10,7 @@ import Project._
import Keys.{ appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, serverPort, thisProject, thisProjectRef, watch } import Keys.{ appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, serverPort, thisProject, thisProjectRef, watch }
import Scope.{ GlobalScope, ThisScope } import Scope.{ GlobalScope, ThisScope }
import Def.{ Flattened, Initialize, ScopedKey, Setting } 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.{ AttributeKey, AttributeMap, Dag, Relation, Settings, Show, ~> }
import sbt.internal.util.Types.{ const, idFun } import sbt.internal.util.Types.{ const, idFun }
import sbt.internal.util.complete.DefaultParsers import sbt.internal.util.complete.DefaultParsers
@ -398,7 +398,8 @@ object Project extends ProjectExtra {
val (onLoad, onUnload) = getHooks(structure.data) val (onLoad, onUnload) = getHooks(structure.data)
val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload) val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload)
val newState = unloaded.copy(attributes = newAttrs) val newState = unloaded.copy(attributes = newAttrs)
onLoad(LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)) // TODO: Fix this
onLoad(updateCurrent(newState) /*LogManager.setGlobalLogLevels(updateCurrent(newState), structure.data)*/ )
} }
def orIdentity[T](opt: Option[T => T]): T => T = opt getOrElse idFun def orIdentity[T](opt: Option[T => T]): T => T = opt getOrElse idFun

View File

@ -2,46 +2,67 @@
* Copyright 2010 Mark Harrah * Copyright 2010 Mark Harrah
*/ */
package sbt package sbt
package internal
import java.io.PrintWriter import java.io.PrintWriter
import Def.ScopedKey import Def.ScopedKey
import Scope.GlobalScope import Scope.GlobalScope
import Keys.{ logLevel, logManager, persistLogLevel, persistTraceLevel, sLog, traceLevel } import Keys.{ logLevel, logManager, persistLogLevel, persistTraceLevel, sLog, traceLevel }
import scala.Console.{ BLUE, RESET } import scala.Console.{ BLUE, RESET }
import sbt.internal.util.{ AttributeKey, ConsoleOut, MultiLoggerConfig, Settings, SuppressedTraceContext } import sbt.internal.util.{ AttributeKey, ConsoleOut, Settings, SuppressedTraceContext, MainAppender }
import sbt.internal.util.MainLogging._ import MainAppender._
import sbt.util.{ AbstractLogger, Level, Logger } 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 { 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) => def construct(data: Settings[Scope], state: State) = (task: ScopedKey[_], to: PrintWriter) =>
{ {
val manager = logManager in task.scope get data getOrElse defaultManager(state.globalLogging.console) val manager = logManager in task.scope get data getOrElse defaultManager(state.globalLogging.console)
manager(data, state, task, to) 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)) def defaultManager(console: ConsoleOut): LogManager = withLoggers((sk, s) => defaultScreen(console))
@deprecated("Explicitly specify standard out.", "0.13.0") // This is called by Defaults.
def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = defaults(extra, StandardMain.console) def defaults(extra: ScopedKey[_] => Seq[Appender], console: ConsoleOut): LogManager =
def defaults(extra: ScopedKey[_] => Seq[AbstractLogger], console: ConsoleOut): LogManager =
withLoggers((task, state) => defaultScreen(console, suppressedMessage(task, state)), extra = extra) 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( def withLoggers(
screen: (ScopedKey[_], State) => AbstractLogger = (sk, s) => defaultScreen(s.globalLogging.console), screen: (ScopedKey[_], State) => Appender = (sk, s) => defaultScreen(s.globalLogging.console),
backed: PrintWriter => AbstractLogger = defaultBacked(), backed: PrintWriter => Appender = defaultBacked(),
extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil relay: Unit => Appender = defaultRelay,
): LogManager = new LogManager { 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 = 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 val scope = task.scope
// to change from global being the default to overriding, switch the order of state.get and data.get // 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 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 backingLevel = getOr(persistLogLevel.key, Level.Debug)
val screenTrace = getOr(traceLevel.key, defaultTraceLevel(state)) val screenTrace = getOr(traceLevel.key, defaultTraceLevel(state))
val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue) val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue)
val extraBacked = state.globalLogging.backed :: Nil val extraBacked = state.globalLogging.backed :: relay :: Nil
multiLogger(MultiLoggerConfig(console, backed, extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace)) 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 = def defaultTraceLevel(state: State): Int =
if (state.interactive) -1 else Int.MaxValue if (state.interactive) -1 else Int.MaxValue
@ -66,24 +99,25 @@ object LogManager {
case _ => key // should never get here case _ => key // should never get here
} }
// TODO: Fix this
// if global logging levels are not explicitly set, set them from project settings // if global logging levels are not explicitly set, set them from project settings
private[sbt] def setGlobalLogLevels(s: State, data: Settings[Scope]): State = // private[sbt] def setGlobalLogLevels(s: State, data: Settings[Scope]): State =
if (hasExplicitGlobalLogLevels(s)) // if (hasExplicitGlobalLogLevels(s))
s // s
else { // else {
val logging = s.globalLogging // val logging = s.globalLogging
def get[T](key: SettingKey[T]) = key in GlobalScope get data // def get[T](key: SettingKey[T]) = key in GlobalScope get data
def transfer(l: AbstractLogger, traceKey: SettingKey[Int], levelKey: SettingKey[Level.Value]): Unit = { // def transfer(l: AbstractLogger, traceKey: SettingKey[Int], levelKey: SettingKey[Level.Value]): Unit = {
get(traceKey).foreach(l.setTrace) // get(traceKey).foreach(l.setTrace)
get(levelKey).foreach(l.setLevel) // get(levelKey).foreach(l.setLevel)
} // }
logging.full match { // logging.full match {
case a: AbstractLogger => transfer(a, traceLevel, logLevel) // case a: AbstractLogger => transfer(a, traceLevel, logLevel)
case _ => () // case _ => ()
} // }
transfer(logging.backed, persistTraceLevel, persistLogLevel) // transfer(logging.backed, persistTraceLevel, persistLogLevel)
s // s
} // }
def setGlobalLogLevel(s: State, level: Level.Value): State = { def setGlobalLogLevel(s: State, level: Level.Value): State = {
s.globalLogging.full match { s.globalLogging.full match {
@ -93,6 +127,10 @@ object LogManager {
s.put(BasicKeys.explicitGlobalLogLevels, true).put(Keys.logLevel.key, level) 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 = private[this] def hasExplicitGlobalLogLevels(s: State): Boolean =
State.getBoolean(s, BasicKeys.explicitGlobalLogLevels, default = false) 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
}

View File

@ -5,7 +5,7 @@ import java.io._
import org.specs2.mutable.Specification import org.specs2.mutable.Specification
import sbt.internal._ 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 } object PluginCommandTestPlugin0 extends AutoPlugin { override def requires = empty }
@ -109,7 +109,7 @@ object FakeState {
List(), List(),
State.newHistory, State.newHistory,
attributes, 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, None,
State.Continue State.Continue
) )

View File

@ -10,9 +10,9 @@ object Dependencies {
// sbt modules // sbt modules
private val ioVersion = "1.0.0-M9" private val ioVersion = "1.0.0-M9"
private val utilVersion = "1.0.0-M17" private val utilVersion = "1.0.0-M18"
private val lmVersion = "0.1.0-X3" private val lmVersion = "1.0.0-X4"
private val zincVersion = "1.0.0-X7" private val zincVersion = "1.0.0-X8"
private val sbtIO = "org.scala-sbt" %% "io" % ioVersion private val sbtIO = "org.scala-sbt" %% "io" % ioVersion

View File

@ -1,7 +1,7 @@
scalaVersion := "2.10.6" scalaVersion := "2.10.6"
addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5") 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-ghpages" % "0.5.4")
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.5") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.5")
addSbtPlugin("com.typesafe.sbt" % "sbt-javaversioncheck" % "0.1.0") addSbtPlugin("com.typesafe.sbt" % "sbt-javaversioncheck" % "0.1.0")

View File

@ -4,13 +4,20 @@
package sbt package sbt
package protocol package protocol
import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter } import sjsonnew.JsonFormat
import scala.json.ast.unsafe.JValue import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter }
import sjsonnew.support.scalajson.unsafe.Parser import scala.json.ast.unsafe.{ JValue, JObject, JString }
import java.nio.ByteBuffer import java.nio.ByteBuffer
import scala.util.{ Success, Failure } import scala.util.{ Success, Failure }
import sbt.internal.util.ChannelLogEntry
object Serialization { 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] = def serializeCommand(command: CommandMessage): Array[Byte] =
{ {
import codec.JsonProtocol._ import codec.JsonProtocol._
@ -18,7 +25,7 @@ object Serialization {
CompactPrinter(json).getBytes("UTF-8") CompactPrinter(json).getBytes("UTF-8")
} }
def serializeEvent(event: EventMessage): Array[Byte] = def serializeEventMessage(event: EventMessage): Array[Byte] =
{ {
import codec.JsonProtocol._ import codec.JsonProtocol._
val json: JValue = Converter.toJson[EventMessage](event).get val json: JValue = Converter.toJson[EventMessage](event).get
@ -46,7 +53,44 @@ object Serialization {
/** /**
* @return A command or an invalid input description * @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) val buffer = ByteBuffer.wrap(bytes.toArray)
Parser.parseFromByteBuffer(buffer) match { Parser.parseFromByteBuffer(buffer) match {

View File

@ -98,10 +98,6 @@ trait Import {
type FullLogger = sbt.internal.util.FullLogger type FullLogger = sbt.internal.util.FullLogger
val FullReader = sbt.internal.util.FullReader val FullReader = sbt.internal.util.FullReader
type 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 val HCons = sbt.internal.util.HCons
type HCons[H, T <: HList] = sbt.internal.util.HCons[H, T] type HCons[H, T <: HList] = sbt.internal.util.HCons[H, T]
val HList = sbt.internal.util.HList val HList = sbt.internal.util.HList
@ -128,12 +124,9 @@ trait Import {
type LineReader = sbt.internal.util.LineReader type LineReader = sbt.internal.util.LineReader
val LoggerWriter = sbt.internal.util.LoggerWriter val LoggerWriter = sbt.internal.util.LoggerWriter
type LoggerWriter = sbt.internal.util.LoggerWriter type LoggerWriter = sbt.internal.util.LoggerWriter
val MainLogging = sbt.internal.util.MainLogging
type MessageOnlyException = sbt.internal.util.MessageOnlyException type MessageOnlyException = sbt.internal.util.MessageOnlyException
type ModifiedFileInfo = sbt.internal.util.ModifiedFileInfo type ModifiedFileInfo = sbt.internal.util.ModifiedFileInfo
type MultiLogger = sbt.internal.util.MultiLogger 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 NoPosition = sbt.internal.util.NoPosition
val PMap = sbt.internal.util.PMap val PMap = sbt.internal.util.PMap
type PMap[K[_], V[_]] = sbt.internal.util.PMap[K, V] type PMap[K[_], V[_]] = sbt.internal.util.PMap[K, V]

View File

@ -38,6 +38,6 @@ def addExtra2(s: State, extra: Seq[File]): State =
else else
{ {
val newID = ApplicationID(currentID).copy(extra = extra) val newID = ApplicationID(currentID).copy(extra = extra)
s.setResult(Some(reload.copy(app = newID))) s.setNext(new State.Return(reload.copy(app = newID)))
} }
} }