1. 配置和使用日志

配置和使用日志 

查看先前执行命令的日志输出 

运行命令时,比屏幕显示更详细的日志输出会发送到文件(默认情况下)。此输出可以通过运行 last 回调上一个执行的命令。

例如,当源代码最新时,run 的输出是

> run
[info] Running A
Hi!
[success] Total time: 0 s, completed Feb 25, 2012 1:00:00 PM

可以通过运行 last 回调此执行的详细信息

> last
[debug] Running task... Cancelable: false, max worker threads: 4, check cycles: false
[debug]
[debug] Initial source changes:
[debug]     removed:Set()
[debug]     added: Set()
[debug]     modified: Set()
[debug] Removed products: Set()
[debug] Modified external sources: Set()
[debug] Modified binary dependencies: Set()
[debug] Initial directly invalidated sources: Set()
[debug]
[debug] Sources indirectly invalidated by:
[debug]     product: Set()
[debug]     binary dep: Set()
[debug]     external source: Set()
[debug] Initially invalidated: Set()
[debug] Copy resource mappings:
[debug]
[info] Running A
[debug] Starting sandboxed run...
[debug] Waiting for threads to exit or System.exit to be called.
[debug]   Classpath:
[debug]     /tmp/e/target/scala-2.9.2/classes
[debug]     /tmp/e/.sbt/0.12.0/boot/scala-2.9.2/lib/scala-library.jar
[debug] Waiting for thread runMain to exit
[debug]     Thread runMain exited.
[debug] Interrupting remaining threads (should be all daemons).
[debug] Sandboxed run complete..
[debug] Exited with code 0
[success] Total time: 0 s, completed Jan 1, 2012 1:00:00 PM

以下部分描述了控制台和后台文件日志级别的配置。

查看特定任务的先前日志输出 

运行任务时,比屏幕显示更详细的日志输出会发送到文件(默认情况下)。此输出可以通过运行 last <task> 回调特定任务。例如,第一次运行 compile 时,输出可能看起来像

> compile
[info] Updating {file:/.../demo/}example...
[info] Resolving org.scala-lang#scala-library;2.9.2 ...
[info] Done updating.
[info] Compiling 1 Scala source to .../demo/target/scala-2.9.2/classes...
[success] Total time: 0 s, completed Jun 1, 2012 1:11:11 PM

输出表明执行了依赖解析和编译。可以单独回调每个步骤的详细输出。例如,

> last compile
[debug]
[debug] Initial source changes:
[debug]     removed:Set()
[debug]     added: Set(/home/mark/tmp/a/b/A.scala)
[debug]     modified: Set()
...

> last update
[info] Updating {file:/.../demo/}example...
[debug] post 1.3 ivy file: using exact as default matcher
[debug] :: resolving dependencies :: example#example_2.9.2;0.1-SNAPSHOT
[debug]     confs: [compile, runtime, test, provided, optional, compile-internal, runtime-internal, test-internal, plugin, sources, docs, pom]
[debug]     validate = true
[debug]     refresh = false
[debug] resolving dependencies for configuration 'compile'
...

显示先前编译的警告 

默认情况下,Scala 编译器不会打印警告的完整细节。编译使用 Predef 中已弃用的 error 方法的代码可能会生成以下输出

> compile
[info] Compiling 1 Scala source to <...>/classes...
[warn] there were 1 deprecation warnings; re-run with -deprecation for details
[warn] one warning found

由于没有提供详细信息,因此需要将 -deprecation 添加到传递给编译器的选项(scalacOptions)中,然后重新编译。使用 Scala 2.10 及更高版本时,另一种选择是运行 printWarnings。此任务将显示先前编译的所有警告。例如,

> printWarnings
[warn] A.scala:2: method error in object Predef is deprecated: Use sys.error(message) instead
[warn]  def x = error("Failed.")
[warn]          ^

全局更改日志级别 

更改日志级别的最快方法是使用 errorwarninfodebug 命令。这些命令设置命令和任务的默认日志级别。例如,

> warn

默认情况下将仅显示警告和错误。要设置在启动时执行任何命令之前的日志级别,请在日志级别之前使用 --。例如,

$ sbt --warn
> compile
[warn] there were 2 feature warning(s); re-run with -feature for details
[warn] one warning found
[success] Total time: 4 s, completed ...
>

日志级别可以在更细粒度的级别上被覆盖,这将在下一节中进行说明。

更改特定任务、配置或项目的日志级别 

日志量由 logLevel 设置控制,该设置接受来自 Level 枚举的值。有效值为 ErrorWarnInfoDebug,按从低到高的详细程度排序。日志级别可以全局配置,如上一节所述,也可以应用于特定项目、配置或任务。例如,要将编译日志级别更改为仅显示警告和错误,

> set Compile / compile / logLevel := Level.Warn

要为当前项目中的所有任务启用调试日志,

> set logLevel := Level.Warn

常见的情况是,在运行任务后,您注意到需要比默认情况下显示的更多信息。基于 logLevel 的解决方案通常需要更改日志级别并再次运行任务。但是,有两种情况下不需要这样做。首先,可以使用 printWarnings 显示先前编译的警告,用于主源代码,或使用 Test/printWarnings 用于测试源代码。其次,先前执行的输出可用于单个任务或其整体。请参阅有关 printWarnings 的部分以及有关 先前输出 的部分。

配置打印堆栈跟踪 

默认情况下,sbt 会隐藏在执行期间抛出的大多数异常的堆栈跟踪。它会打印一条消息,指示如何显示异常。但是,您可能希望默认情况下显示更多堆栈跟踪。

要配置的设置是 traceLevel,它是一个具有 Int 值的设置。当 traceLevel 设置为负值时,不会显示堆栈跟踪。当它为零时,堆栈跟踪将显示到第一个 sbt 堆栈帧。当为正时,堆栈跟踪将显示到该帧数。

例如,以下配置将 sbt 配置为显示到第一个 sbt 帧的堆栈跟踪

> set every traceLevel := 0

every 部分表示在所有范围内覆盖设置。要更改单个项目、配置或任务的跟踪打印行为,请相应地限定 traceLevel

> set Test / traceLevel := 5
> set update / traceLevel := 0
> set ThisProject / traceLevel := -1

立即打印测试输出,而不是缓冲 

默认情况下,sbt 会缓冲测试的日志输出,直到整个类完成。这样做是为了防止在并行执行时输出混合在一起。要禁用缓冲,请将 logBuffered 设置设置为 false

logBuffered := false

添加自定义日志记录器 

extraLoggers 设置可用于添加自定义日志记录器。在内部,sbt 使用 log4j2 库,因此自定义日志记录器应实现 org.apache.logging.log4j.core.Appender,通常是通过扩展 AbstractAppender

extraLoggers 是一个函数 ScopedKey[_] => Seq[Appender]。这意味着它可以根据请求日志记录器的任务提供不同的日志记录。

extraLoggers := {
  val currentFunction = extraLoggers.value
    (key: ScopedKey[_]) => {
        myCustomLogger(key) +: currentFunction(key)
    }
}

在这里,我们获取当前函数 currentFunction 用于设置,并提供一个新函数。新函数将我们的自定义日志记录器预置到旧函数提供的日志记录器之前。

log4j2 中的 Appender 会追加一个 LogEvent,其核心在内部是一个 Message。可以存在多种类型的 Message,但 sbt 会生成包含 ObjectMessage 实例的事件,该实例包含一个可以通过调用 getParameter() 检索的有效负载。

sbt 日志记录发出的有效负载是 StringEvent 的实例,它包含 String 字段,包括 messagelevel

将所有内容放在一起,这是一个(完全无用!)示例,展示了将任务中的消息反向记录到控制台的额外日志记录器

extraLoggers := {
  import org.apache.logging.log4j.core.LogEvent;
  import org.apache.logging.log4j.core.appender.AbstractAppender
  import org.apache.logging.log4j.message.{Message,ObjectMessage}

  import sbt.internal.util.StringEvent

  def loggerNameForKey( key : sbt.Def.ScopedKey[_] ) = s"""reverse.${key.scope.task.toOption.getOrElse("<unknown>")}"""

  class ReverseConsoleAppender( key : ScopedKey[_] ) extends AbstractAppender (
    loggerNameForKey( key ), // name : String
    null,                    // filter : org.apache.logging.log4j.core.Filter
    null,                    // layout : org.apache.logging.log4j.core.Layout[ _ <: Serializable]
    false                    // ignoreExceptions : Boolean
  ) {

    this.start() // the log4j2 Appender must be started, or it will fail with an Exception

    override def append( event : LogEvent ) : Unit = {
      val output = {
        def forUnexpected( message : Message ) = s"[${this.getName()}] Unexpected: ${message.getFormattedMessage()}"
        event.getMessage() match {
	   case om : ObjectMessage => { // what we expect
	     om.getParameter() match {
	       case se : StringEvent => s"[${this.getName()} - ${se.level}] ${se.message.reverse}"
	       case other            => forUnexpected( om )
	     }
	   }
	   case unexpected : Message => forUnexpected( unexpected )
	}
      }
      System.out.synchronized { // sbt adopts a convention of acquiring System.out's monitor printing to the console
         println( output )
      }
    }
  }

  val currentFunction = extraLoggers.value
  (key: ScopedKey[_]) => {
     new ReverseConsoleAppender(key) +: currentFunction(key)
  }
}

现在,如果我们执行一个记录消息的任务,我们应该会看到我们的日志记录器被调用

sbt:sbt-logging-example> update
[info] Updating ...
[reverse.update - info] ... gnitadpU
[info] Done updating.
[reverse.update - info] .gnitadpu enoD
[success] Total time: 0 s, completed Oct 16, 2019 5:22:22 AM

在任务中记录消息 

特殊的 streams 任务通过 Streams 实例提供每个任务的日志记录和 I/O。要进行日志记录,任务会使用 streams 任务中的 log 成员。调用 log 会提供一个 Logger

import sbt.Keys.streams

myTask := {
  val log = streams.value.log
  log.warn("A warning.")
}

在设置中记录消息 

由于设置不能引用任务,因此在设置初始化期间无法使用特殊的 streams 任务来提供日志记录。建议的方法是使用 sLog。调用 sLog.value 会提供一个 Logger

mySetting := {
  val log = sLog.value
  log.warn("A warning.")
}