SBT добавляет идентификатор проекта в журналы в многопроектной сборке

В многопроектной сборке SBT, когда вы запускаете задачу в проекте агрегатора, и она запускает задачи в каждом агрегированном подпроекте, все журналы из каждого подпроекта выводятся вместе в один большой поток.

Это затрудняет отладку проблем сборки в многопроектной сборке, поскольку все журналы смешиваются. Есть ли способ вывести идентификатор проекта в каждой строке журнала, чтобы вы могли быстро определить, из какого подпроекта был получен журнал?

Вот пример проекта:

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, но, к сожалению, документация немного скудна, а я ни в коем случае не эксперт по SBT.


person gnicholas    schedule 11.12.2017    source источник


Ответы (2)


Как сказал Рич, в настоящее время нет точки расширения для настройки формата ведения журнала sbt. Но если вы не возражаете полагаться на внутренние API, вы можете приблизиться к тому, что хотите, в зависимости от того, какую версию sbt вы используете.

В основном вам придется заменить logManager по умолчанию, а не добавлять extraLoggers (хотя API похож).

сбт 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")
    }
  }

сбт 1.0.x

API ведения журнала был изменен здесь, чтобы обеспечить регистрацию событий. . Теперь нам нужно предоставить 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 для настройки формата.

person g.krastev    schedule 19.12.2017
comment
Спасибо за подробный ответ. К сожалению, SBT не предоставляет этого из коробки. Для других, которым интересно, как настроить это в реальном мире, вы не можете установить ключ настройки logManager из плагина, поскольку отдельные подпроекты переопределяют значение logManager. Вместо этого вы можете создать общую функцию в своем каталоге project, а затем вызвать эту функцию из каждого отдельного подпроекта, чтобы установить значение logManager в каждом подпроекте. Это гарантирует, что вы переопределяете значение по умолчанию logManager, а не наоборот. - person gnicholas; 20.12.2017

Просматривая код 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 использовал библиотеку ведения журналов, такую ​​как logback или log4j2, а не изобретал колесо с собственной структурой ведения журналов, это было бы возможно. :-(

person Rich    schedule 17.12.2017