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.")
}