SBT 将项目 ID 添加到多项目构建的日志中

SBT add projectID to logs in multiproject build

在 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 专家。

查看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 这样的日志库,而不是用自己的日志框架重新发明轮子,这是可能的。 :-(

正如 Rich 所说,目前还没有自定义 sbt 日志格式的扩展点。但是如果你不介意依赖内部 APIs 你可以接近你想要的,这取决于你使用的是哪个版本的 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 中日志实现已移动的 类,因为它们都是私有的、密封的、最终的等。我唯一能想到的就是复制 [的功能=18=] 是破解输出流:

   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配置文件来自定义格式