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 Util._
|
||||||
import com.typesafe.tools.mima.core._, ProblemFilters._
|
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 internalPath = file("internal")
|
||||||
|
|
||||||
def commonSettings: Seq[Setting[_]] = Seq(
|
def commonSettings: Seq[Setting[_]] = Seq(
|
||||||
|
|
@ -98,12 +98,14 @@ lazy val utilComplete = (project in internalPath / "util-complete").
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
lazy val utilLogging = (project in internalPath / "util-logging").
|
lazy val utilLogging = (project in internalPath / "util-logging").
|
||||||
|
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
|
||||||
dependsOn(utilInterface, utilTesting % Test).
|
dependsOn(utilInterface, utilTesting % Test).
|
||||||
settings(
|
settings(
|
||||||
commonSettings,
|
commonSettings,
|
||||||
crossScalaVersions := Seq(scala210, scala211, scala212),
|
crossScalaVersions := Seq(scala210, scala211, scala212),
|
||||||
name := "Util Logging",
|
name := "Util Logging",
|
||||||
libraryDependencies += jline
|
libraryDependencies ++= Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson),
|
||||||
|
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Relation
|
// Relation
|
||||||
|
|
@ -150,7 +152,8 @@ lazy val utilTesting = (project in internalPath / "util-testing").
|
||||||
crossScalaVersions := Seq(scala210, scala211, scala212),
|
crossScalaVersions := Seq(scala210, scala211, scala212),
|
||||||
name := "Util Testing",
|
name := "Util Testing",
|
||||||
libraryDependencies ++= Seq(scalaCheck, scalatest)
|
libraryDependencies ++= Seq(scalaCheck, scalatest)
|
||||||
)
|
).
|
||||||
|
configure(addSbtIO)
|
||||||
|
|
||||||
lazy val utilScripted = (project in internalPath / "util-scripted").
|
lazy val utilScripted = (project in internalPath / "util-scripted").
|
||||||
dependsOn(utilLogging).
|
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
|
package sbt.internal.util
|
||||||
|
|
||||||
import sbt.util._
|
import sbt.util._
|
||||||
import java.io.{ PrintStream, PrintWriter }
|
import java.io.{ PrintStream, PrintWriter }
|
||||||
import java.util.Locale
|
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 {
|
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. */
|
/** Escape character, used to introduce an escape sequence. */
|
||||||
final val ESC = '\u001B'
|
final val ESC = '\u001B'
|
||||||
|
|
||||||
|
|
@ -122,25 +178,83 @@ object ConsoleLogger {
|
||||||
private[this] def os = System.getProperty("os.name")
|
private[this] def os = System.getProperty("os.name")
|
||||||
private[this] def isWindows = os.toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
|
private[this] def isWindows = os.toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
|
||||||
|
|
||||||
def apply(out: PrintStream): ConsoleLogger = apply(ConsoleOut.printStreamOut(out))
|
def apply(out: PrintStream): ConsoleAppender = apply(generateName, ConsoleOut.printStreamOut(out))
|
||||||
def apply(out: PrintWriter): ConsoleLogger = apply(ConsoleOut.printWriterOut(out))
|
def apply(out: PrintWriter): ConsoleAppender = apply(generateName, ConsoleOut.printWriterOut(out))
|
||||||
def apply(out: ConsoleOut = ConsoleOut.systemOut, ansiCodesSupported: Boolean = formatEnabled,
|
def apply(name: String = generateName, out: ConsoleOut = ConsoleOut.systemOut, ansiCodesSupported: Boolean = formatEnabled,
|
||||||
useColor: Boolean = formatEnabled, suppressedMessage: SuppressedTraceContext => Option[String] = noSuppressedMessage): ConsoleLogger =
|
useColor: Boolean = formatEnabled, suppressedMessage: SuppressedTraceContext => Option[String] = noSuppressedMessage): ConsoleAppender =
|
||||||
new ConsoleLogger(out, ansiCodesSupported, useColor, suppressedMessage)
|
{
|
||||||
|
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
|
private[this] val EscapeSequence = (27.toChar + "[^@-~]*[@-~]").r
|
||||||
def stripEscapeSequences(s: String): String =
|
def stripEscapeSequences(s: String): String =
|
||||||
EscapeSequence.pattern.matcher(s).replaceAll("")
|
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
|
* A logger that logs to the console. On supported systems, the level labels are
|
||||||
* colored.
|
* colored.
|
||||||
*
|
*
|
||||||
* This logger is not thread-safe.
|
* 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 }
|
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 messageColor(level: Level.Value) = RESET
|
||||||
def labelColor(level: Level.Value) =
|
def labelColor(level: Level.Value) =
|
||||||
level match {
|
level match {
|
||||||
|
|
@ -148,24 +262,29 @@ class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val an
|
||||||
case Level.Warn => YELLOW
|
case Level.Warn => YELLOW
|
||||||
case _ => RESET
|
case _ => RESET
|
||||||
}
|
}
|
||||||
def successLabelColor = GREEN
|
|
||||||
def successMessageColor = RESET
|
// success is called by ConsoleLogger.
|
||||||
override def success(message: => String): Unit = {
|
// This should turn into an event.
|
||||||
if (successEnabled)
|
private[sbt] def success(message: => String): Unit = {
|
||||||
log(successLabelColor, Level.SuccessLabel, successMessageColor, message)
|
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 {
|
out.lockObject.synchronized {
|
||||||
val traceLevel = getTrace
|
|
||||||
if (traceLevel >= 0)
|
if (traceLevel >= 0)
|
||||||
out.print(StackTrace.trimmed(t, traceLevel))
|
out.print(StackTrace.trimmed(t, traceLevel))
|
||||||
if (traceLevel <= 2)
|
if (traceLevel <= 2)
|
||||||
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useColor)))
|
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useColor)))
|
||||||
printLabeledLine(labelColor(Level.Error), "trace", messageColor(Level.Error), msg)
|
printLabeledLine(labelColor(Level.Error), "trace", messageColor(Level.Error), msg)
|
||||||
}
|
}
|
||||||
def log(level: Level.Value, message: => String): Unit = {
|
|
||||||
if (atLevel(level))
|
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||||
log(labelColor(level), level.toString, messageColor(level), message)
|
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)
|
private def reset(): Unit = setColor(RESET)
|
||||||
|
|
||||||
|
|
@ -173,7 +292,7 @@ class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val an
|
||||||
if (ansiCodesSupported && useColor)
|
if (ansiCodesSupported && useColor)
|
||||||
out.lockObject.synchronized { out.print(color) }
|
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 {
|
out.lockObject.synchronized {
|
||||||
for (line <- message.split("""\n"""))
|
for (line <- message.split("""\n"""))
|
||||||
printLabeledLine(labelColor, label, messageColor, line)
|
printLabeledLine(labelColor, label, messageColor, line)
|
||||||
|
|
@ -189,10 +308,9 @@ class ConsoleLogger private[ConsoleLogger] (val out: ConsoleOut, override val an
|
||||||
setColor(messageColor)
|
setColor(messageColor)
|
||||||
out.print(line)
|
out.print(line)
|
||||||
reset()
|
reset()
|
||||||
|
out.print(s" ($name)")
|
||||||
out.println()
|
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)
|
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(s: String): Unit = synchronized { current.append(s); println() }
|
||||||
def println(): Unit = synchronized {
|
def println(): Unit = synchronized {
|
||||||
val s = current.toString
|
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.print(OverwriteLine)
|
||||||
lockObject.println(s)
|
lockObject.println(s)
|
||||||
last = Some(s)
|
last = Some(s)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ package sbt.internal.util
|
||||||
|
|
||||||
import sbt.util._
|
import sbt.util._
|
||||||
import java.io.{ File, PrintWriter }
|
import java.io.{ File, PrintWriter }
|
||||||
|
import org.apache.logging.log4j.core.Appender
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the current global logging configuration.
|
* Provides the current global logging configuration.
|
||||||
|
|
@ -15,7 +16,10 @@ import java.io.{ File, PrintWriter }
|
||||||
* `backing` tracks the files that persist the global logging.
|
* `backing` tracks the files that persist the global logging.
|
||||||
* `newLogger` creates a new global logging configuration from a sink and backing configuration.
|
* `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.
|
* Tracks the files that persist the global logging.
|
||||||
|
|
@ -38,10 +42,28 @@ final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile
|
||||||
object GlobalLogBacking {
|
object GlobalLogBacking {
|
||||||
def apply(newBackingFile: => File): GlobalLogBacking = GlobalLogBacking(newBackingFile, None, newBackingFile _)
|
def apply(newBackingFile: => File): GlobalLogBacking = GlobalLogBacking(newBackingFile, None, newBackingFile _)
|
||||||
}
|
}
|
||||||
|
|
||||||
object GlobalLogging {
|
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)
|
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 sbt.util._
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
|
import org.apache.logging.log4j.core.Appender
|
||||||
|
|
||||||
object MainLogging {
|
object MainAppender {
|
||||||
def multiLogger(config: MultiLoggerConfig): Logger =
|
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._
|
import config._
|
||||||
val multi = new MultiLogger(console :: backed :: extra)
|
// TODO
|
||||||
// sets multi to the most verbose for clients that inspect the current level
|
// console setTrace screenTrace
|
||||||
multi setLevel Level.unionAll(backingLevel :: screenLevel :: extra.map(_.getLevel))
|
// backed setTrace backingTrace
|
||||||
// set the specific levels
|
// multi: Logger
|
||||||
console setLevel screenLevel
|
|
||||||
backed setLevel backingLevel
|
// val log = LogExchange.logger(loggerName)
|
||||||
console setTrace screenTrace
|
LogExchange.unbindLoggerAppenders(log.name)
|
||||||
backed setTrace backingTrace
|
LogExchange.bindLoggerAppenders(
|
||||||
multi: Logger
|
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) => {
|
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = (log, writer, backing) => {
|
||||||
val backed = defaultBacked()(writer)
|
val backed: Appender = defaultBacked(generateGlobalBackingName)(writer)
|
||||||
val full = multiLogger(defaultMultiConfig(console, backed))
|
val full = multiLogger(log, defaultMultiConfig(Option(console), backed, Nil))
|
||||||
GlobalLogging(full, console, backed, backing, f)
|
GlobalLogging(full, console, backed, backing, newAppender)
|
||||||
}
|
}
|
||||||
f
|
newAppender
|
||||||
}
|
}
|
||||||
|
|
||||||
def defaultMultiConfig(console: ConsoleOut, backing: AbstractLogger): MultiLoggerConfig =
|
def defaultMultiConfig(consoleOpt: Option[ConsoleOut], backing: Appender, extra: List[Appender]): MainAppenderConfig =
|
||||||
new MultiLoggerConfig(defaultScreen(console, ConsoleLogger.noSuppressedMessage), backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue)
|
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 defaultBacked(
|
||||||
def defaultScreen(console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): AbstractLogger =
|
loggerName: String = generateGlobalBackingName,
|
||||||
ConsoleLogger(console, suppressedMessage = suppressedMessage)
|
useColor: Boolean = ConsoleAppender.formatEnabled
|
||||||
|
): PrintWriter => Appender =
|
||||||
|
to => {
|
||||||
|
ConsoleAppender(
|
||||||
|
ConsoleAppender.generateName,
|
||||||
|
ConsoleOut.printWriterOut(to), useColor = useColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger =
|
final case class MainAppenderConfig(
|
||||||
to => ConsoleLogger(ConsoleOut.printWriterOut(to), useColor = useColor)
|
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 =
|
private[this] def removeEscapes(event: LogEvent): LogEvent =
|
||||||
{
|
{
|
||||||
import ConsoleLogger.{ removeEscapeSequences => rm }
|
import ConsoleAppender.{ removeEscapeSequences => rm }
|
||||||
event match {
|
event match {
|
||||||
case s: Success => new Success(rm(s.msg))
|
case s: Success => new Success(rm(s.msg))
|
||||||
case l: Log => new Log(l.level, rm(l.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 Prop._
|
||||||
import Gen.{ listOf, oneOf }
|
import Gen.{ listOf, oneOf }
|
||||||
|
|
||||||
import ConsoleLogger.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequences }
|
import ConsoleAppender.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequences }
|
||||||
|
|
||||||
object Escapes extends Properties("Escapes") {
|
object Escapes extends Properties("Escapes") {
|
||||||
property("genTerminator only generates terminators") =
|
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 scalaCheck = "org.scalacheck" %% "scalacheck" % "1.13.4"
|
||||||
val scalatest = "org.scalatest" %% "scalatest" % "3.0.1"
|
val scalatest = "org.scalatest" %% "scalatest" % "3.0.1"
|
||||||
|
|
||||||
val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
|
val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
|
||||||
|
|
||||||
val sjsonnewVersion = "0.7.0"
|
val sjsonnewVersion = "0.7.0"
|
||||||
val sjsonnew = "com.eed3si9n" %% "sjson-new-core" % sjsonnewVersion
|
val sjsonnew = "com.eed3si9n" %% "sjson-new-core" % sjsonnewVersion
|
||||||
val sjsonnewScalaJson = "com.eed3si9n" %% "sjson-new-scalajson" % 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