mirror of https://github.com/sbt/sbt.git
Merge pull request #68 from eed3si9n/topic/logging
Reimplement multi-logger using log4j2
This commit is contained in:
commit
5b8de3abca
|
|
@ -2,7 +2,7 @@ import Dependencies._
|
|||
import Util._
|
||||
import com.typesafe.tools.mima.core._, ProblemFilters._
|
||||
|
||||
def baseVersion: String = "0.1.0-M16"
|
||||
def baseVersion: String = "1.0.0-M18"
|
||||
def internalPath = file("internal")
|
||||
|
||||
def commonSettings: Seq[Setting[_]] = Seq(
|
||||
|
|
@ -98,12 +98,14 @@ lazy val utilComplete = (project in internalPath / "util-complete").
|
|||
|
||||
// logging
|
||||
lazy val utilLogging = (project in internalPath / "util-logging").
|
||||
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
|
||||
dependsOn(utilInterface, utilTesting % Test).
|
||||
settings(
|
||||
commonSettings,
|
||||
crossScalaVersions := Seq(scala210, scala211, scala212),
|
||||
name := "Util Logging",
|
||||
libraryDependencies += jline
|
||||
libraryDependencies ++= Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson),
|
||||
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala"
|
||||
)
|
||||
|
||||
// Relation
|
||||
|
|
@ -150,7 +152,8 @@ lazy val utilTesting = (project in internalPath / "util-testing").
|
|||
crossScalaVersions := Seq(scala210, scala211, scala212),
|
||||
name := "Util Testing",
|
||||
libraryDependencies ++= Seq(scalaCheck, scalatest)
|
||||
)
|
||||
).
|
||||
configure(addSbtIO)
|
||||
|
||||
lazy val utilScripted = (project in internalPath / "util-scripted").
|
||||
dependsOn(utilLogging).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* This code is generated using sbt-datatype.
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util
|
||||
abstract class AbstractEntry(
|
||||
val channelName: Option[String],
|
||||
val execId: Option[String]) extends Serializable {
|
||||
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: AbstractEntry => (this.channelName == x.channelName) && (this.execId == x.execId)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (17 + channelName.##) + execId.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"AbstractEntry(" + channelName + ", " + execId + ")"
|
||||
}
|
||||
}
|
||||
object AbstractEntry {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* This code is generated using sbt-datatype.
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util
|
||||
final class ChannelLogEntry private (
|
||||
val level: String,
|
||||
val message: String,
|
||||
channelName: Option[String],
|
||||
execId: Option[String]) extends sbt.internal.util.AbstractEntry(channelName, execId) with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: ChannelLogEntry => (this.level == x.level) && (this.message == x.message) && (this.channelName == x.channelName) && (this.execId == x.execId)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (37 * (17 + level.##) + message.##) + channelName.##) + execId.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"ChannelLogEntry(" + level + ", " + message + ", " + channelName + ", " + execId + ")"
|
||||
}
|
||||
protected[this] def copy(level: String = level, message: String = message, channelName: Option[String] = channelName, execId: Option[String] = execId): ChannelLogEntry = {
|
||||
new ChannelLogEntry(level, message, channelName, execId)
|
||||
}
|
||||
def withLevel(level: String): ChannelLogEntry = {
|
||||
copy(level = level)
|
||||
}
|
||||
def withMessage(message: String): ChannelLogEntry = {
|
||||
copy(message = message)
|
||||
}
|
||||
def withChannelName(channelName: Option[String]): ChannelLogEntry = {
|
||||
copy(channelName = channelName)
|
||||
}
|
||||
def withChannelName(channelName: String): ChannelLogEntry = {
|
||||
copy(channelName = Option(channelName))
|
||||
}
|
||||
def withExecId(execId: Option[String]): ChannelLogEntry = {
|
||||
copy(execId = execId)
|
||||
}
|
||||
def withExecId(execId: String): ChannelLogEntry = {
|
||||
copy(execId = Option(execId))
|
||||
}
|
||||
}
|
||||
object ChannelLogEntry {
|
||||
|
||||
def apply(level: String, message: String, channelName: Option[String], execId: Option[String]): ChannelLogEntry = new ChannelLogEntry(level, message, channelName, execId)
|
||||
def apply(level: String, message: String, channelName: String, execId: String): ChannelLogEntry = new ChannelLogEntry(level, message, Option(channelName), Option(execId))
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* This code is generated using sbt-datatype.
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util.codec
|
||||
import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder }
|
||||
trait AbstractEntryFormats { self: sjsonnew.BasicJsonProtocol with sbt.internal.util.codec.ChannelLogEntryFormats =>
|
||||
implicit lazy val AbstractEntryFormat: JsonFormat[sbt.internal.util.AbstractEntry] = flatUnionFormat1[sbt.internal.util.AbstractEntry, sbt.internal.util.ChannelLogEntry]("type")
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* This code is generated using sbt-datatype.
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util.codec
|
||||
import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder }
|
||||
trait ChannelLogEntryFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val ChannelLogEntryFormat: JsonFormat[sbt.internal.util.ChannelLogEntry] = new JsonFormat[sbt.internal.util.ChannelLogEntry] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.util.ChannelLogEntry = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val level = unbuilder.readField[String]("level")
|
||||
val message = unbuilder.readField[String]("message")
|
||||
val channelName = unbuilder.readField[Option[String]]("channelName")
|
||||
val execId = unbuilder.readField[Option[String]]("execId")
|
||||
unbuilder.endObject()
|
||||
sbt.internal.util.ChannelLogEntry(level, message, channelName, execId)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.internal.util.ChannelLogEntry, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("level", obj.level)
|
||||
builder.addField("message", obj.message)
|
||||
builder.addField("channelName", obj.channelName)
|
||||
builder.addField("execId", obj.execId)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* This code is generated using sbt-datatype.
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util.codec
|
||||
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
||||
with sbt.internal.util.codec.ChannelLogEntryFormats
|
||||
with sbt.internal.util.codec.AbstractEntryFormats
|
||||
object JsonProtocol extends JsonProtocol
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package sbt.internal.util
|
||||
@target(Scala)
|
||||
@codecPackage("sbt.internal.util.codec")
|
||||
@fullCodec("JsonProtocol")
|
||||
|
||||
interface AbstractEntry {
|
||||
channelName: String
|
||||
execId: String
|
||||
}
|
||||
|
||||
type ChannelLogEntry implements sbt.internal.util.AbstractEntry {
|
||||
level: String!
|
||||
message: String!
|
||||
channelName: String
|
||||
execId: String
|
||||
}
|
||||
|
|
@ -1,13 +1,69 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009, 2010, 2011 Mark Harrah
|
||||
*/
|
||||
package sbt.internal.util
|
||||
|
||||
import sbt.util._
|
||||
import java.io.{ PrintStream, PrintWriter }
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import org.apache.logging.log4j.{ Level => XLevel }
|
||||
import org.apache.logging.log4j.message.{ Message, ParameterizedMessage, ObjectMessage }
|
||||
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 ConsoleAppender._
|
||||
|
||||
object ConsoleLogger {
|
||||
// These are provided so other modules do not break immediately.
|
||||
@deprecated("Use ConsoleAppender.", "0.13.x")
|
||||
final val ESC = ConsoleAppender.ESC
|
||||
@deprecated("Use ConsoleAppender.", "0.13.x")
|
||||
private[sbt] def isEscapeTerminator(c: Char): Boolean = ConsoleAppender.isEscapeTerminator(c)
|
||||
@deprecated("Use ConsoleAppender.", "0.13.x")
|
||||
def hasEscapeSequence(s: String): Boolean = ConsoleAppender.hasEscapeSequence(s)
|
||||
@deprecated("Use ConsoleAppender.", "0.13.x")
|
||||
def removeEscapeSequences(s: String): String = ConsoleAppender.removeEscapeSequences(s)
|
||||
@deprecated("Use ConsoleAppender.", "0.13.x")
|
||||
val formatEnabled = ConsoleAppender.formatEnabled
|
||||
@deprecated("Use ConsoleAppender.", "0.13.x")
|
||||
val noSuppressedMessage = ConsoleAppender.noSuppressedMessage
|
||||
|
||||
def apply(out: PrintStream): ConsoleLogger = apply(ConsoleOut.printStreamOut(out))
|
||||
def apply(out: PrintWriter): ConsoleLogger = apply(ConsoleOut.printWriterOut(out))
|
||||
def apply(out: ConsoleOut = ConsoleOut.systemOut, ansiCodesSupported: Boolean = ConsoleAppender.formatEnabled,
|
||||
useColor: Boolean = ConsoleAppender.formatEnabled, suppressedMessage: SuppressedTraceContext => Option[String] = ConsoleAppender.noSuppressedMessage): ConsoleLogger =
|
||||
new ConsoleLogger(out, ansiCodesSupported, useColor, suppressedMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
* A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored.
|
||||
*/
|
||||
class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val ansiCodesSupported: Boolean, val useColor: Boolean, val suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger {
|
||||
private[sbt] val appender = ConsoleAppender(generateName, out, ansiCodesSupported, useColor, suppressedMessage)
|
||||
|
||||
override def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
appender.control(event, message)
|
||||
override def log(level: Level.Value, message: => String): Unit =
|
||||
{
|
||||
if (atLevel(level)) {
|
||||
appender.appendLog(level, message)
|
||||
}
|
||||
}
|
||||
|
||||
override def success(message: => String): Unit =
|
||||
{
|
||||
if (successEnabled) {
|
||||
appender.success(message)
|
||||
}
|
||||
}
|
||||
override def trace(t: => Throwable): Unit =
|
||||
appender.trace(t, getTrace)
|
||||
|
||||
override def logAll(events: Seq[LogEvent]) = out.lockObject.synchronized { events.foreach(log) }
|
||||
}
|
||||
|
||||
object ConsoleAppender {
|
||||
/** Escape character, used to introduce an escape sequence. */
|
||||
final val ESC = '\u001B'
|
||||
|
||||
|
|
@ -122,25 +178,83 @@ object ConsoleLogger {
|
|||
private[this] def os = System.getProperty("os.name")
|
||||
private[this] def isWindows = os.toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
|
||||
|
||||
def apply(out: PrintStream): ConsoleLogger = apply(ConsoleOut.printStreamOut(out))
|
||||
def apply(out: PrintWriter): ConsoleLogger = apply(ConsoleOut.printWriterOut(out))
|
||||
def apply(out: ConsoleOut = ConsoleOut.systemOut, ansiCodesSupported: Boolean = formatEnabled,
|
||||
useColor: Boolean = formatEnabled, suppressedMessage: SuppressedTraceContext => Option[String] = noSuppressedMessage): ConsoleLogger =
|
||||
new ConsoleLogger(out, ansiCodesSupported, useColor, suppressedMessage)
|
||||
def apply(out: PrintStream): ConsoleAppender = apply(generateName, ConsoleOut.printStreamOut(out))
|
||||
def apply(out: PrintWriter): ConsoleAppender = apply(generateName, ConsoleOut.printWriterOut(out))
|
||||
def apply(name: String = generateName, out: ConsoleOut = ConsoleOut.systemOut, ansiCodesSupported: Boolean = formatEnabled,
|
||||
useColor: Boolean = formatEnabled, suppressedMessage: SuppressedTraceContext => Option[String] = noSuppressedMessage): ConsoleAppender =
|
||||
{
|
||||
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useColor, suppressedMessage)
|
||||
appender.start
|
||||
appender
|
||||
}
|
||||
def generateName: String =
|
||||
"out-" + generateId.incrementAndGet
|
||||
private val generateId: AtomicInteger = new AtomicInteger
|
||||
|
||||
private[this] val EscapeSequence = (27.toChar + "[^@-~]*[@-~]").r
|
||||
def stripEscapeSequences(s: String): String =
|
||||
EscapeSequence.pattern.matcher(s).replaceAll("")
|
||||
|
||||
def toLevel(level: XLevel): Level.Value =
|
||||
level match {
|
||||
case XLevel.OFF => Level.Debug
|
||||
case XLevel.FATAL => Level.Error
|
||||
case XLevel.ERROR => Level.Error
|
||||
case XLevel.WARN => Level.Warn
|
||||
case XLevel.INFO => Level.Info
|
||||
case XLevel.DEBUG => Level.Debug
|
||||
case _ => Level.Debug
|
||||
}
|
||||
def toXLevel(level: Level.Value): XLevel =
|
||||
level match {
|
||||
case Level.Error => XLevel.ERROR
|
||||
case Level.Warn => XLevel.WARN
|
||||
case Level.Info => XLevel.INFO
|
||||
case Level.Debug => XLevel.DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
// See http://stackoverflow.com/questions/24205093/how-to-create-a-custom-appender-in-log4j2
|
||||
// for custom appender using Java.
|
||||
// http://logging.apache.org/log4j/2.x/manual/customconfig.html
|
||||
// https://logging.apache.org/log4j/2.x/log4j-core/apidocs/index.html
|
||||
|
||||
/**
|
||||
* A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored.
|
||||
*
|
||||
* This logger is not thread-safe.
|
||||
*/
|
||||
class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val ansiCodesSupported: Boolean, val useColor: Boolean, val suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger {
|
||||
class ConsoleAppender private[ConsoleAppender] (
|
||||
val name: String,
|
||||
val out: ConsoleOut,
|
||||
val ansiCodesSupported: Boolean,
|
||||
val useColor: Boolean,
|
||||
val suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
) extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
|
||||
import scala.Console.{ BLUE, GREEN, RED, RESET, YELLOW }
|
||||
|
||||
def append(event: XLogEvent): Unit =
|
||||
{
|
||||
val level = ConsoleAppender.toLevel(event.getLevel)
|
||||
val message = event.getMessage
|
||||
val str = messageToString(message)
|
||||
appendLog(level, str)
|
||||
}
|
||||
|
||||
def messageToString(msg: Message): String =
|
||||
msg match {
|
||||
case p: ParameterizedMessage => p.getFormattedMessage
|
||||
case r: RingBufferLogEvent => r.getFormattedMessage
|
||||
case o: ObjectMessage => objectToString(o.getParameter)
|
||||
case _ => msg.toString
|
||||
}
|
||||
def objectToString(o: AnyRef): String =
|
||||
o match {
|
||||
case x: ChannelLogEntry => x.message
|
||||
case _ => o.toString
|
||||
}
|
||||
|
||||
def messageColor(level: Level.Value) = RESET
|
||||
def labelColor(level: Level.Value) =
|
||||
level match {
|
||||
|
|
@ -148,24 +262,29 @@ class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val an
|
|||
case Level.Warn => YELLOW
|
||||
case _ => RESET
|
||||
}
|
||||
def successLabelColor = GREEN
|
||||
def successMessageColor = RESET
|
||||
override def success(message: => String): Unit = {
|
||||
if (successEnabled)
|
||||
log(successLabelColor, Level.SuccessLabel, successMessageColor, message)
|
||||
|
||||
// success is called by ConsoleLogger.
|
||||
// This should turn into an event.
|
||||
private[sbt] def success(message: => String): Unit = {
|
||||
appendLog(successLabelColor, Level.SuccessLabel, successMessageColor, message)
|
||||
}
|
||||
def trace(t: => Throwable): Unit =
|
||||
private[sbt] def successLabelColor = GREEN
|
||||
private[sbt] def successMessageColor = RESET
|
||||
|
||||
def trace(t: => Throwable, traceLevel: Int): Unit =
|
||||
out.lockObject.synchronized {
|
||||
val traceLevel = getTrace
|
||||
if (traceLevel >= 0)
|
||||
out.print(StackTrace.trimmed(t, traceLevel))
|
||||
if (traceLevel <= 2)
|
||||
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useColor)))
|
||||
printLabeledLine(labelColor(Level.Error), "trace", messageColor(Level.Error), msg)
|
||||
}
|
||||
def log(level: Level.Value, message: => String): Unit = {
|
||||
if (atLevel(level))
|
||||
log(labelColor(level), level.toString, messageColor(level), message)
|
||||
|
||||
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
appendLog(labelColor(Level.Info), Level.Info.toString, BLUE, message)
|
||||
|
||||
def appendLog(level: Level.Value, message: => String): Unit = {
|
||||
appendLog(labelColor(level), level.toString, messageColor(level), message)
|
||||
}
|
||||
private def reset(): Unit = setColor(RESET)
|
||||
|
||||
|
|
@ -173,7 +292,7 @@ class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val an
|
|||
if (ansiCodesSupported && useColor)
|
||||
out.lockObject.synchronized { out.print(color) }
|
||||
}
|
||||
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit =
|
||||
private def appendLog(labelColor: String, label: String, messageColor: String, message: String): Unit =
|
||||
out.lockObject.synchronized {
|
||||
for (line <- message.split("""\n"""))
|
||||
printLabeledLine(labelColor, label, messageColor, line)
|
||||
|
|
@ -189,10 +308,9 @@ class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val an
|
|||
setColor(messageColor)
|
||||
out.print(line)
|
||||
reset()
|
||||
out.print(s" ($name)")
|
||||
out.println()
|
||||
}
|
||||
|
||||
def logAll(events: Seq[LogEvent]) = out.lockObject.synchronized { events.foreach(log) }
|
||||
def control(event: ControlEvent.Value, message: => String): Unit = log(labelColor(Level.Info), Level.Info.toString, BLUE, message)
|
||||
}
|
||||
|
||||
final class SuppressedTraceContext(val traceLevel: Int, val useColor: Boolean)
|
||||
|
|
@ -33,7 +33,7 @@ object ConsoleOut {
|
|||
def println(s: String): Unit = synchronized { current.append(s); println() }
|
||||
def println(): Unit = synchronized {
|
||||
val s = current.toString
|
||||
if (ConsoleLogger.formatEnabled && last.exists(lmsg => f(s, lmsg)))
|
||||
if (ConsoleAppender.formatEnabled && last.exists(lmsg => f(s, lmsg)))
|
||||
lockObject.print(OverwriteLine)
|
||||
lockObject.println(s)
|
||||
last = Some(s)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package sbt.internal.util
|
|||
|
||||
import sbt.util._
|
||||
import java.io.{ File, PrintWriter }
|
||||
import org.apache.logging.log4j.core.Appender
|
||||
|
||||
/**
|
||||
* Provides the current global logging configuration.
|
||||
|
|
@ -15,7 +16,10 @@ import java.io.{ File, PrintWriter }
|
|||
* `backing` tracks the files that persist the global logging.
|
||||
* `newLogger` creates a new global logging configuration from a sink and backing configuration.
|
||||
*/
|
||||
final case class GlobalLogging(full: Logger, console: ConsoleOut, backed: AbstractLogger, backing: GlobalLogBacking, newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging)
|
||||
final case class GlobalLogging(full: ManagedLogger, console: ConsoleOut, backed: Appender,
|
||||
backing: GlobalLogBacking, newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging)
|
||||
|
||||
final case class GlobalLogging1(full: Logger, console: ConsoleOut, backed: AbstractLogger, backing: GlobalLogBacking, newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1)
|
||||
|
||||
/**
|
||||
* Tracks the files that persist the global logging.
|
||||
|
|
@ -38,10 +42,28 @@ final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile
|
|||
object GlobalLogBacking {
|
||||
def apply(newBackingFile: => File): GlobalLogBacking = GlobalLogBacking(newBackingFile, None, newBackingFile _)
|
||||
}
|
||||
|
||||
object GlobalLogging {
|
||||
def initial(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File, console: ConsoleOut): GlobalLogging =
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
private def generateName: String =
|
||||
"GlobalLogging" + generateId.incrementAndGet
|
||||
private val generateId: AtomicInteger = new AtomicInteger
|
||||
|
||||
def initial1(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1, newBackingFile: => File, console: ConsoleOut): GlobalLogging1 =
|
||||
{
|
||||
val log = ConsoleLogger(console)
|
||||
GlobalLogging(log, console, log, GlobalLogBacking(newBackingFile), newLogger)
|
||||
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger)
|
||||
}
|
||||
}
|
||||
|
||||
def initial(newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File, console: ConsoleOut): GlobalLogging =
|
||||
{
|
||||
val loggerName = generateName
|
||||
val log = LogExchange.logger(loggerName)
|
||||
val appender = ConsoleAppender(ConsoleAppender.generateName, console)
|
||||
LogExchange.bindLoggerAppenders(
|
||||
loggerName, List(appender -> Level.Info)
|
||||
)
|
||||
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,42 +2,65 @@ package sbt.internal.util
|
|||
|
||||
import sbt.util._
|
||||
import java.io.PrintWriter
|
||||
import org.apache.logging.log4j.core.Appender
|
||||
|
||||
object MainLogging {
|
||||
def multiLogger(config: MultiLoggerConfig): Logger =
|
||||
object MainAppender {
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
private def generateGlobalBackingName: String =
|
||||
"GlobalBacking" + generateId.incrementAndGet
|
||||
private val generateId: AtomicInteger = new AtomicInteger
|
||||
|
||||
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger =
|
||||
{
|
||||
import config._
|
||||
val multi = new MultiLogger(console :: backed :: extra)
|
||||
// sets multi to the most verbose for clients that inspect the current level
|
||||
multi setLevel Level.unionAll(backingLevel :: screenLevel :: extra.map(_.getLevel))
|
||||
// set the specific levels
|
||||
console setLevel screenLevel
|
||||
backed setLevel backingLevel
|
||||
console setTrace screenTrace
|
||||
backed setTrace backingTrace
|
||||
multi: Logger
|
||||
// TODO
|
||||
// console setTrace screenTrace
|
||||
// backed setTrace backingTrace
|
||||
// multi: Logger
|
||||
|
||||
// val log = LogExchange.logger(loggerName)
|
||||
LogExchange.unbindLoggerAppenders(log.name)
|
||||
LogExchange.bindLoggerAppenders(
|
||||
log.name,
|
||||
(consoleOpt.toList map { _ -> screenLevel }) :::
|
||||
List(backed -> backingLevel) :::
|
||||
(extra map { x => (x -> Level.Info) })
|
||||
)
|
||||
log
|
||||
}
|
||||
|
||||
def globalDefault(console: ConsoleOut): (PrintWriter, GlobalLogBacking) => GlobalLogging =
|
||||
def globalDefault(console: ConsoleOut): (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging =
|
||||
{
|
||||
lazy val f: (PrintWriter, GlobalLogBacking) => GlobalLogging = (writer, backing) => {
|
||||
val backed = defaultBacked()(writer)
|
||||
val full = multiLogger(defaultMultiConfig(console, backed))
|
||||
GlobalLogging(full, console, backed, backing, f)
|
||||
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = (log, writer, backing) => {
|
||||
val backed: Appender = defaultBacked(generateGlobalBackingName)(writer)
|
||||
val full = multiLogger(log, defaultMultiConfig(Option(console), backed, Nil))
|
||||
GlobalLogging(full, console, backed, backing, newAppender)
|
||||
}
|
||||
f
|
||||
newAppender
|
||||
}
|
||||
|
||||
def defaultMultiConfig(console: ConsoleOut, backing: AbstractLogger): MultiLoggerConfig =
|
||||
new MultiLoggerConfig(defaultScreen(console, ConsoleLogger.noSuppressedMessage), backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue)
|
||||
def defaultMultiConfig(consoleOpt: Option[ConsoleOut], backing: Appender, extra: List[Appender]): MainAppenderConfig =
|
||||
MainAppenderConfig(consoleOpt map { defaultScreen(_, ConsoleAppender.noSuppressedMessage) }, backing, extra,
|
||||
Level.Info, Level.Debug, -1, Int.MaxValue)
|
||||
def defaultScreen(console: ConsoleOut): Appender = ConsoleAppender(ConsoleAppender.generateName, console)
|
||||
def defaultScreen(console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): Appender =
|
||||
ConsoleAppender(ConsoleAppender.generateName, console, suppressedMessage = suppressedMessage)
|
||||
def defaultScreen(name: String, console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): Appender =
|
||||
ConsoleAppender(name, console, suppressedMessage = suppressedMessage)
|
||||
|
||||
def defaultScreen(console: ConsoleOut): AbstractLogger = ConsoleLogger(console)
|
||||
def defaultScreen(console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): AbstractLogger =
|
||||
ConsoleLogger(console, suppressedMessage = suppressedMessage)
|
||||
def defaultBacked(
|
||||
loggerName: String = generateGlobalBackingName,
|
||||
useColor: Boolean = ConsoleAppender.formatEnabled
|
||||
): PrintWriter => Appender =
|
||||
to => {
|
||||
ConsoleAppender(
|
||||
ConsoleAppender.generateName,
|
||||
ConsoleOut.printWriterOut(to), useColor = useColor
|
||||
)
|
||||
}
|
||||
|
||||
def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger =
|
||||
to => ConsoleLogger(ConsoleOut.printWriterOut(to), useColor = useColor)
|
||||
final case class MainAppenderConfig(
|
||||
consoleOpt: Option[Appender], backed: Appender, extra: List[Appender],
|
||||
screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int
|
||||
)
|
||||
}
|
||||
|
||||
final case class MultiLoggerConfig(console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger],
|
||||
screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.util._
|
||||
import org.apache.logging.log4j.{ Logger => XLogger }
|
||||
import org.apache.logging.log4j.message.ObjectMessage
|
||||
|
||||
/**
|
||||
* Delegates log events to the associated LogExchange.
|
||||
*/
|
||||
class ManagedLogger(
|
||||
val name: String,
|
||||
val channelName: Option[String],
|
||||
val execId: Option[String],
|
||||
xlogger: XLogger
|
||||
) extends Logger {
|
||||
override def trace(t: => Throwable): Unit = () // exchange.appendLog(new Trace(t))
|
||||
override def log(level: Level.Value, message: => String): Unit =
|
||||
{
|
||||
xlogger.log(
|
||||
ConsoleAppender.toXLevel(level),
|
||||
new ObjectMessage(ChannelLogEntry(level.toString, message, channelName, execId))
|
||||
)
|
||||
}
|
||||
override def success(message: => String): Unit = xlogger.info(message)
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger {
|
|||
|
||||
private[this] def removeEscapes(event: LogEvent): LogEvent =
|
||||
{
|
||||
import ConsoleLogger.{ removeEscapeSequences => rm }
|
||||
import ConsoleAppender.{ removeEscapeSequences => rm }
|
||||
event match {
|
||||
case s: Success => new Success(rm(s.msg))
|
||||
case l: Log => new Log(l.level, rm(l.msg))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package sbt.util
|
||||
|
||||
import sbt.internal.util._
|
||||
import org.apache.logging.log4j.{ LogManager => XLogManager, Level => XLevel }
|
||||
import org.apache.logging.log4j.core._
|
||||
import org.apache.logging.log4j.core.appender.AsyncAppender
|
||||
import org.apache.logging.log4j.core.config.{ AppenderRef, LoggerConfig }
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
// http://logging.apache.org/log4j/2.x/manual/customconfig.html
|
||||
// https://logging.apache.org/log4j/2.x/log4j-core/apidocs/index.html
|
||||
|
||||
sealed abstract class LogExchange {
|
||||
private[sbt] lazy val context: LoggerContext = init()
|
||||
private[sbt] lazy val asyncStdout: AsyncAppender = buildAsyncStdout
|
||||
|
||||
def logger(name: String): ManagedLogger = logger(name, None, None)
|
||||
def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger = {
|
||||
val _ = context
|
||||
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
|
||||
val config = ctx.getConfiguration
|
||||
val loggerConfig = LoggerConfig.createLogger(false, XLevel.DEBUG, name,
|
||||
"true", Array[AppenderRef](), null, config, null)
|
||||
config.addLogger(name, loggerConfig)
|
||||
ctx.updateLoggers
|
||||
val logger = ctx.getLogger(name)
|
||||
new ManagedLogger(name, channelName, execId, logger)
|
||||
}
|
||||
def unbindLoggerAppenders(loggerName: String): Unit = {
|
||||
val lc = loggerConfig(loggerName)
|
||||
lc.getAppenders.asScala foreach {
|
||||
case (k, v) => lc.removeAppender(k)
|
||||
}
|
||||
}
|
||||
def bindLoggerAppenders(loggerName: String, appenders: List[(Appender, Level.Value)]): Unit = {
|
||||
val lc = loggerConfig(loggerName)
|
||||
appenders foreach {
|
||||
case (x, lv) => lc.addAppender(x, ConsoleAppender.toXLevel(lv), null)
|
||||
}
|
||||
}
|
||||
def loggerConfig(loggerName: String): LoggerConfig = {
|
||||
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
|
||||
val config = ctx.getConfiguration
|
||||
config.getLoggerConfig(loggerName)
|
||||
}
|
||||
private[sbt] def buildAsyncStdout: AsyncAppender = {
|
||||
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
|
||||
val config = ctx.getConfiguration
|
||||
// val layout = PatternLayout.newBuilder
|
||||
// .withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
|
||||
// .build
|
||||
val appender = ConsoleAppender("Stdout")
|
||||
// CustomConsoleAppenderImpl.createAppender("Stdout", layout, null, null)
|
||||
appender.start
|
||||
config.addAppender(appender)
|
||||
val asyncAppender: AsyncAppender = (AsyncAppender.newBuilder(): AsyncAppender.Builder)
|
||||
.setName("AsyncStdout")
|
||||
.setAppenderRefs(Array(AppenderRef.createAppenderRef("Stdout", XLevel.DEBUG, null)))
|
||||
.setBlocking(false)
|
||||
.setConfiguration(config)
|
||||
.build
|
||||
asyncAppender.start
|
||||
config.addAppender(asyncAppender)
|
||||
asyncAppender
|
||||
}
|
||||
private[sbt] def init(): LoggerContext = {
|
||||
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory
|
||||
import org.apache.logging.log4j.core.config.Configurator
|
||||
val builder = ConfigurationBuilderFactory.newConfigurationBuilder
|
||||
builder.setConfigurationName("sbt.util.logging")
|
||||
val ctx = Configurator.initialize(builder.build())
|
||||
ctx match { case x: LoggerContext => x }
|
||||
}
|
||||
}
|
||||
object LogExchange extends LogExchange
|
||||
|
|
@ -0,0 +1 @@
|
|||
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
|
||||
|
|
@ -4,7 +4,7 @@ import org.scalacheck._
|
|||
import Prop._
|
||||
import Gen.{ listOf, oneOf }
|
||||
|
||||
import ConsoleLogger.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequences }
|
||||
import ConsoleAppender.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequences }
|
||||
|
||||
object Escapes extends Properties("Escapes") {
|
||||
property("genTerminator only generates terminators") =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import org.scalatest._
|
||||
import sbt.util._
|
||||
import java.io.{ File, PrintWriter }
|
||||
import sbt.io.Using
|
||||
|
||||
class ManagedLoggerSpec extends FlatSpec with Matchers {
|
||||
"ManagedLogger" should "log to console" in {
|
||||
val log = LogExchange.logger("foo")
|
||||
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
|
||||
log.info("test")
|
||||
log.debug("test")
|
||||
}
|
||||
|
||||
"global logging" should "log immediately after initialization" in {
|
||||
// this is passed into State normally
|
||||
val global0 = initialGlobalLogging
|
||||
val full = global0.full
|
||||
(1 to 3).toList foreach { x => full.info(s"test$x") }
|
||||
}
|
||||
|
||||
// This is done in Mainloop.scala
|
||||
it should "create a new backing with newAppender" in {
|
||||
val global0 = initialGlobalLogging
|
||||
val logBacking0 = global0.backing
|
||||
val global1 = Using.fileWriter(append = true)(logBacking0.file) { writer =>
|
||||
val out = new PrintWriter(writer)
|
||||
val g = global0.newAppender(global0.full, out, logBacking0)
|
||||
val full = g.full
|
||||
(1 to 3).toList foreach { x => full.info(s"newAppender $x") }
|
||||
assert(logBacking0.file.exists)
|
||||
g
|
||||
}
|
||||
val logBacking1 = global1.backing
|
||||
Using.fileWriter(append = true)(logBacking1.file) { writer =>
|
||||
val out = new PrintWriter(writer)
|
||||
val g = global1.newAppender(global1.full, out, logBacking1)
|
||||
val full = g.full
|
||||
(1 to 3).toList foreach { x => full.info(s"newAppender $x") }
|
||||
// println(logBacking.file)
|
||||
// print("Press enter to continue. ")
|
||||
// System.console.readLine
|
||||
assert(logBacking1.file.exists)
|
||||
}
|
||||
}
|
||||
|
||||
val console = ConsoleOut.systemOut
|
||||
def initialGlobalLogging: GlobalLogging = GlobalLogging.initial(
|
||||
MainAppender.globalDefault(console), File.createTempFile("sbt", ".log"), console
|
||||
)
|
||||
}
|
||||
|
|
@ -35,10 +35,15 @@ object Dependencies {
|
|||
|
||||
val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.13.4"
|
||||
val scalatest = "org.scalatest" %% "scalatest" % "3.0.1"
|
||||
|
||||
val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
|
||||
|
||||
val sjsonnewVersion = "0.7.0"
|
||||
val sjsonnew = "com.eed3si9n" %% "sjson-new-core" % sjsonnewVersion
|
||||
val sjsonnewScalaJson = "com.eed3si9n" %% "sjson-new-scalajson" % sjsonnewVersion
|
||||
|
||||
def log4jVersion = "2.7"
|
||||
val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4jVersion
|
||||
val log4jCore = "org.apache.logging.log4j" % "log4j-core" % log4jVersion
|
||||
val log4jSlf4jImpl = "org.apache.logging.log4j" % "log4j-slf4j-impl" % log4jVersion
|
||||
val disruptor = "com.lmax" % "disruptor" % "3.3.6"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0-M3")
|
||||
Loading…
Reference in New Issue