SBT将projectID添加到多项目构建

时间:2017-12-11 17:20:22

标签: scala logging sbt multi-project

在SBT多项目构建中,当您在聚合器项目上运行任务并且它在每个聚合子项目中运行任务时,来自每个子项目的所有日志将在一个大流中一起输出。

这使得在多项目构建中调试构建问题变得很困难,因为所有日志混合在一起。有没有办法在每个日志行输出projectID,以便您可以快速识别日志来自哪个子项目?

以下是一个示例项目:

name := "my-multiproject-build"

lazy val ProjectOne = project
lazy val ProjectTwo = project

lazy val root = project.in( file(".") ).aggregate(ProjectOne, ProjectTwo)

(默认情况下会发生什么)

sbt package

[info] Packaging ProjectOne.jar ...
[info] Done packaging.
[info] Packaging ProjectTwo.jar ...
[info] Done packaging.

(我想要的)

sbt package

[info] [ProjectOne] Packaging ProjectOne.jar ...
[info] [ProjectOne] Done packaging.
[info] [ProjectTwo] Packaging ProjectTwo.jar ...
[info] [ProjectTwo] Done packaging.

我试着调查SBT custom loggers,但不幸的是文档有点稀疏,我绝不是SBT专家。

2 个答案:

答案 0 :(得分:2)

像Rich说的那样,目前没有自定义sbt日志格式的扩展点。但是,如果您不介意依赖内部API,您可以接近您想要的内容,具体取决于您使用的sbt版本。

基本上,您必须替换默认的logManager而不是添加extraLoggers(尽管API类似)。

sbt 0.13.x

我们在这里的工作看起来更简单。我们可以重复使用BufferedLogger来避免将所有内容委托给ConsoleLogger所涉及的样板:

  logManager := LogManager.withScreenLogger { (_, state) =>
    val console = ConsoleLogger(state.globalLogging.console)
    new BufferedLogger(console) {
      val project = projectID.value.name
      override def log(level: Level.Value, message: => String): Unit =
        console.log(level, s"[$project] $message")
    }
  }

sbt 1.0.x

此处更改了日志记录API以提供event logging。我们现在必须提供更灵活的log4j Appender,但这使我们的工作更加困难。我们不能重用日志记录实现移动的sbt.internal中的类,因为它们都是私有的,密封的,最终的等等。我唯一能想到的就是没有复制ConsoleAppender的功能。是破解输出流:

   logManager := LogManager.defaultManager(
    ConsoleOut.printStreamOut(new PrintStream(System.out) {
      val project = projectID.value.name
      override def println(str: String): Unit = {
        val (lvl, msg) = str.span(_ != ']')
        super.println(s"$lvl] [$project$msg")
      }
    }))

请注意,无法保证调用println而不是其他print方法。

我不知道是否可以使用log4j配置文件来自定义格式。

答案 1 :(得分:1)

仔细阅读SBT代码,我认为这不太可能。

这是一个build.sbt,可以完成你想要的大部分工作。

import sbt.Level
name := "my-multiproject-build"

lazy val ProjectOne = project
lazy val ProjectTwo = project

lazy val root = project.in( file(".") ).aggregate(ProjectOne, ProjectTwo)

val wrapLogger = (project: Project, inner: AbstractLogger) => {
  new AbstractLogger {

    override def log(level: Level.Value, message: => String): Unit = {
      inner.log(
        level,
        "[" + project.id + "] " + message
      )
    }

    override def setTrace(flag: Int): Unit = inner.setTrace(flag)

    override def setLevel(newLevel: Level.Value): Unit = {
      // MultiLogger keeps setting this to debug
      inner.setLevel(Level.Info)
    }

    override def setSuccessEnabled(flag: Boolean): Unit = inner.setSuccessEnabled(flag)

    override def logAll(events: Seq[LogEvent]): Unit = {
      events.foreach(log)
    }

    override def control(event: _root_.sbt.ControlEvent.Value, message: => String): Unit
      = inner.control(event, message)

    override def successEnabled: Boolean = inner.successEnabled

    override def getLevel = inner.getLevel

    override def getTrace: Int = inner.getTrace

    override def trace(t: => Throwable): Unit = inner.trace(t)

    override def success(message: => String): Unit = inner.success(message)
  }
}

extraLoggers in ProjectOne := {
  val currentFunction = extraLoggers.value
  (key: ScopedKey[_]) => {
    val logger = wrapLogger(ProjectOne, ConsoleLogger())
    logger.setLevel(Level.Info)
    logger +: currentFunction(key)
  }
}

extraLoggers in ProjectTwo := {
  val currentFunction = extraLoggers.value
  (key: ScopedKey[_]) => {
    val logger = wrapLogger(ProjectTwo, ConsoleLogger())
    logger.setLevel(Level.Info)
    logger +: currentFunction(key)
  }
}

现在,对于项目特定的日志,输出重复:一次是项目名称前置,一次是没有项目名称。 输出如下:

[info] Done packaging.
[info] [ProjectTwo] Done packaging.
[info] Done updating.
[info] [ProjectOne] Done updating.

ConsoleLogger是在MainLogging.defaultScreen构建的,没有扩展点可以让你操作我能看到的日志消息。

如果SBT使用了logbacklog4j2之类的日志库,而不是使用自己的日志框架重新发明轮子,那么这是可能的。 : - (