rework ConsoleLogger

can send output to a PrintWriter
control over color, still need custom formatter
replace IvyLogger with normal Logger
This commit is contained in:
Mark Harrah 2010-09-04 08:19:58 -04:00
parent 9bcd68d28c
commit 050f9db501
11 changed files with 68 additions and 44 deletions

View File

@ -15,7 +15,7 @@ import java.util.concurrent.Callable
* This is used for compiled source jars so that the compilation need not be repeated for other projects on the same
* machine.
*/
class ComponentManager(globalLock: xsbti.GlobalLock, provider: xsbti.ComponentProvider, val log: IvyLogger) extends NotNull
class ComponentManager(globalLock: xsbti.GlobalLock, provider: xsbti.ComponentProvider, val log: Logger) extends NotNull
{
/** Get all of the files for component 'id', throwing an exception if no files exist for the component. */
def files(id: String)(ifMissing: IfMissing): Iterable[File] =

View File

@ -171,7 +171,7 @@ private object IvySbt
/** Sets the resolvers for 'settings' to 'resolvers'. This is done by creating a new chain and making it the default.
* 'other' is for resolvers that should be in a different chain. These are typically used for publishing or other actions. */
private def setResolvers(settings: IvySettings, resolvers: Seq[Resolver], other: Seq[Resolver], localOnly: Boolean, log: IvyLogger)
private def setResolvers(settings: IvySettings, resolvers: Seq[Resolver], other: Seq[Resolver], localOnly: Boolean, log: Logger)
{
def makeChain(label: String, name: String, rs: Seq[Resolver]) = {
log.debug(label + " repositories:")
@ -183,7 +183,7 @@ private object IvySbt
val mainChain = makeChain("Default", "sbt-chain", resolvers)
settings.setDefaultResolver(mainChain.getName)
}
private def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, log: IvyLogger): ChainResolver =
private def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, log: Logger): ChainResolver =
{
val newDefault = new ChainResolver
newDefault.setName(name)

View File

@ -32,7 +32,7 @@ object IvyCache
{
def lockFile = new File(System.getProperty("user.home"), ".sbt.cache.lock")
/** Caches the given 'file' with the given ID. It may be retrieved or cleared using this ID.*/
def cacheJar(moduleID: ModuleID, file: File, lock: Option[xsbti.GlobalLock], log: IvyLogger)
def cacheJar(moduleID: ModuleID, file: File, lock: Option[xsbti.GlobalLock], log: Logger)
{
val artifact = defaultArtifact(moduleID)
val resolved = new ResolvedResource(new FileResource(new IvyFileRepository, file), moduleID.revision)
@ -42,13 +42,13 @@ object IvyCache
}
}
/** Clears the cache of the jar for the given ID.*/
def clearCachedJar(id: ModuleID, lock: Option[xsbti.GlobalLock], log: IvyLogger)
def clearCachedJar(id: ModuleID, lock: Option[xsbti.GlobalLock], log: Logger)
{
try { withCachedJar(id, lock, log)(_.delete) }
catch { case e: Exception => log.debug("Error cleaning cached jar: " + e.toString) }
}
/** Copies the cached jar for the given ID to the directory 'toDirectory'. If the jar is not in the cache, NotInCache is thrown.*/
def retrieveCachedJar(id: ModuleID, toDirectory: File, lock: Option[xsbti.GlobalLock], log: IvyLogger) =
def retrieveCachedJar(id: ModuleID, toDirectory: File, lock: Option[xsbti.GlobalLock], log: Logger) =
withCachedJar(id, lock, log) { cachedFile =>
val copyTo = new File(toDirectory, cachedFile.getName)
FileUtil.copy(cachedFile, copyTo, null)
@ -56,7 +56,7 @@ object IvyCache
}
/** Get the location of the cached jar for the given ID in the Ivy cache. If the jar is not in the cache, NotInCache is thrown .*/
def withCachedJar[T](id: ModuleID, lock: Option[xsbti.GlobalLock], log: IvyLogger)(f: File => T): T =
def withCachedJar[T](id: ModuleID, lock: Option[xsbti.GlobalLock], log: Logger)(f: File => T): T =
{
val cachedFile =
try
@ -71,7 +71,7 @@ object IvyCache
if(cachedFile.exists) f(cachedFile) else throw new NotInCache(id)
}
/** Calls the given function with the default Ivy cache.*/
def withDefaultCache[T](lock: Option[xsbti.GlobalLock], log: IvyLogger)(f: DefaultRepositoryCacheManager => T): T =
def withDefaultCache[T](lock: Option[xsbti.GlobalLock], log: Logger)(f: DefaultRepositoryCacheManager => T): T =
{
val (ivy, local) = basicLocalIvy(lock, log)
ivy.withIvy { ivy =>
@ -82,7 +82,7 @@ object IvyCache
}
private def unknownOrigin(artifact: IvyArtifact) = ArtifactOrigin.unkwnown(artifact)
/** A minimal Ivy setup with only a local resolver and the current directory as the base directory.*/
private def basicLocalIvy(lock: Option[xsbti.GlobalLock], log: IvyLogger) =
private def basicLocalIvy(lock: Option[xsbti.GlobalLock], log: Logger) =
{
val local = Resolver.defaultLocal(None)
val paths = new IvyPaths(new File("."), None)

View File

@ -15,19 +15,19 @@ sealed trait IvyConfiguration extends NotNull
type This <: IvyConfiguration
def lock: Option[xsbti.GlobalLock]
def baseDirectory: File
def log: IvyLogger
def log: Logger
def withBase(newBaseDirectory: File): This
}
final class InlineIvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resolver], val otherResolvers: Seq[Resolver],
val moduleConfigurations: Seq[ModuleConfiguration], val localOnly: Boolean, val lock: Option[xsbti.GlobalLock],
val log: IvyLogger) extends IvyConfiguration
val log: Logger) extends IvyConfiguration
{
type This = InlineIvyConfiguration
def baseDirectory = paths.baseDirectory
def withBase(newBase: File) = new InlineIvyConfiguration(paths.withBase(newBase), resolvers, otherResolvers, moduleConfigurations, localOnly, lock, log)
def changeResolvers(newResolvers: Seq[Resolver]) = new InlineIvyConfiguration(paths, newResolvers, otherResolvers, moduleConfigurations, localOnly, lock, log)
}
final class ExternalIvyConfiguration(val baseDirectory: File, val file: File, val lock: Option[xsbti.GlobalLock], val log: IvyLogger) extends IvyConfiguration
final class ExternalIvyConfiguration(val baseDirectory: File, val file: File, val lock: Option[xsbti.GlobalLock], val log: Logger) extends IvyConfiguration
{
type This = ExternalIvyConfiguration
def withBase(newBase: File) = new ExternalIvyConfiguration(newBase, file, lock, log)
@ -37,7 +37,7 @@ object IvyConfiguration
{
/** Called to configure Ivy when inline resolvers are not specified.
* This will configure Ivy with an 'ivy-settings.xml' file if there is one or else use default resolvers.*/
def apply(paths: IvyPaths, lock: Option[xsbti.GlobalLock], localOnly: Boolean, log: IvyLogger): IvyConfiguration =
def apply(paths: IvyPaths, lock: Option[xsbti.GlobalLock], localOnly: Boolean, log: Logger): IvyConfiguration =
{
log.debug("Autodetecting configuration.")
val defaultIvyConfigFile = IvySbt.defaultIvyConfiguration(paths.baseDirectory)
@ -93,7 +93,7 @@ object InlineConfiguration
}
object ModuleSettings
{
def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID)(baseDirectory: File, log: IvyLogger) =
def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID)(baseDirectory: File, log: Logger) =
{
log.debug("Autodetecting dependencies.")
val defaultPOMFile = IvySbt.defaultPOM(baseDirectory)

View File

@ -5,17 +5,8 @@ package sbt
import org.apache.ivy.util.{Message, MessageLogger, MessageLoggerEngine}
trait IvyLogger extends NotNull
{
def info(msg: => String)
def debug(msg: => String)
def warn(msg: => String)
def error(msg: => String)
def verbose(msg: => String)
}
/** Interface to Ivy logging. */
private final class IvyLoggerInterface(logger: IvyLogger) extends MessageLogger
private final class IvyLoggerInterface(logger: Logger) extends MessageLogger
{
def rawlog(msg: String, level: Int) = log(msg, level)
def log(msg: String, level: Int)

View File

@ -31,7 +31,7 @@ class AggressiveCompiler extends xsbti.AppMain
val classpath = outputDirectory +++ (cwd * "*.jar") +++(cwd * (-"project")).descendentsExcept( "*.jar", "project" || HiddenFileFilter)
val cacheDirectory = target / "cache"
val options = args.tail.toSeq
val log = new ConsoleLogger with Logger with sbt.IvyLogger
val log = ConsoleLogger()
val componentManager = new ComponentManager(launcher.globalLock, app.components, log)
val compiler = new AnalyzingCompiler(ScalaInstance(args.head, launcher), componentManager, log)
val javac = JavaCompiler.directOrFork(compiler.cp, compiler.scalaInstance)( (args: Seq[String], log: Logger) => Process("javac", args) ! log )

View File

@ -13,7 +13,7 @@ object CommandSupport
{
def logger(s: State) = s match {
case State(p: Logged) => p.log
case _ => new ConsoleLogger //TODO: add a default logger to State
case _ => ConsoleLogger() //TODO: add a default logger to State
}
def notReadable(files: Seq[File]): Seq[File] = files filter { !_.canRead }
def readable(files: Seq[File]): Seq[File] = files filter { _.canRead }

View File

@ -60,7 +60,7 @@ object Build
{
import conf._
// TODO: accept Logger as an argument
val log = new ConsoleLogger with Logger with sbt.IvyLogger
val log = ConsoleLogger()
val componentManager = new ComponentManager(launcher.globalLock, configuration.provider.components, log)
val compiler = new AnalyzingCompiler(instance, componentManager, log)

View File

@ -3,8 +3,24 @@
*/
package sbt
import java.io.{PrintStream, PrintWriter}
object ConsoleLogger
{
def systemOut: ConsoleOut = printStreamOut(System.out)
def printStreamOut(out: PrintStream): ConsoleOut = new ConsoleOut {
val lockObject = out
def print(s: String) = out.print(s)
def println(s: String) = out.println(s)
def println() = out.println()
}
def printWriterOut(out: PrintWriter): ConsoleOut = new ConsoleOut {
val lockObject = out
def print(s: String) = out.print(s)
def println(s: String) = out.println(s)
def println() = out.println()
}
private val formatEnabled = ansiSupported && !formatExplicitlyDisabled
private[this] def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat")
@ -14,15 +30,20 @@ object ConsoleLogger
private[this] def os = System.getProperty("os.name")
private[this] def isWindows = os.toLowerCase.indexOf("windows") >= 0
def apply(): ConsoleLogger = apply(systemOut)
def apply(out: PrintStream): ConsoleLogger = apply(printStreamOut(out))
def apply(out: PrintWriter): ConsoleLogger = apply(printWriterOut(out))
def apply(out: ConsoleOut, ansiCodesSupported: Boolean = formatEnabled, useColor: Boolean = true): ConsoleLogger =
new ConsoleLogger(out, ansiCodesSupported, useColor)
}
/** A logger that logs to the console. On supported systems, the level labels are
* colored.
*
* This logger is not thread-safe.*/
class ConsoleLogger extends BasicLogger
class ConsoleLogger private[ConsoleLogger](val out: ConsoleOut, override val ansiCodesSupported: Boolean, val useColor: Boolean) extends BasicLogger
{
override def ansiCodesSupported = ConsoleLogger.formatEnabled
def messageColor(level: Level.Value) = Console.RESET
def labelColor(level: Level.Value) =
level match
@ -39,41 +60,50 @@ class ConsoleLogger extends BasicLogger
log(successLabelColor, Level.SuccessLabel, successMessageColor, message)
}
def trace(t: => Throwable): Unit =
System.out.synchronized
out.lockObject.synchronized
{
val traceLevel = getTrace
if(traceLevel >= 0)
System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) }
out.print(StackTrace.trimmed(t, traceLevel))
}
def log(level: Level.Value, message: => String)
{
if(atLevel(level))
log(labelColor(level), level.toString, messageColor(level), message)
}
private def reset(): Unit = setColor(Console.RESET)
private def setColor(color: String)
{
if(ansiCodesSupported)
System.out.synchronized { System.out.print(color) }
if(ansiCodesSupported && useColor)
out.lockObject.synchronized { out.print(color) }
}
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit =
System.out.synchronized
out.lockObject.synchronized
{
for(line <- message.split("""\n"""))
{
setColor(Console.RESET)
System.out.print('[')
reset()
out.print("[")
setColor(labelColor)
System.out.print(label)
setColor(Console.RESET)
System.out.print("] ")
out.print(label)
reset()
out.print("] ")
setColor(messageColor)
System.out.print(line)
setColor(Console.RESET)
System.out.println()
out.print(line)
reset()
out.println()
}
}
def logAll(events: Seq[LogEvent]) = System.out.synchronized { events.foreach(log) }
def logAll(events: Seq[LogEvent]) = out.lockObject.synchronized { events.foreach(log) }
def control(event: ControlEvent.Value, message: => String)
{ log(labelColor(Level.Info), Level.Info.toString, Console.BLUE, message) }
}
sealed trait ConsoleOut
{
val lockObject: AnyRef
def print(s: String): Unit
def println(s: String): Unit
def println(): Unit
}

View File

@ -16,6 +16,8 @@ object Level extends Enumeration
* label is also defined here. */
val SuccessLabel = "success"
def union(a: Value, b: Value) = if(a.id < b.id) a else b
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */
def apply(s: String) = values.find(s == _.toString)
/** Same as apply, defined for use in pattern matching. */

View File

@ -4,7 +4,8 @@
*/
package sbt
// note that setting the logging level on this logger has no effect on its behavior, only
// on the behavior of the delegates.
class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger
{
override lazy val ansiCodesSupported = delegates.forall(_.ansiCodesSupported)