記錄

Logging 模組提供一種方式,可以將運算的歷程和進度記錄為事件日誌。透過在原始碼中插入記錄陳述來建立事件,例如

@warn "Abandon printf debugging, all ye who enter here!"
┌ Warning: Abandon printf debugging, all ye who enter here!
└ @ Main REPL[1]:1

這個系統比在原始碼中散佈呼叫 println() 有幾個優點。首先,它允許您控制訊息的可見性和呈現方式,而不用編輯原始碼。例如,與上述 @warn 相比

@debug "The sum of some values $(sum(rand(100)))"

預設不會產生任何輸出。此外,在原始碼中保留此類除錯陳述的成本非常低,因為如果系統稍後會忽略訊息,它會避免評估訊息。在此情況下,除非啟用除錯記錄,否則永遠不會執行 sum(rand(100)) 和相關的字串處理。

其次,記錄工具允許您將任意資料附加到每個事件,作為一組鍵值對。這允許您擷取區域變數和其他程式狀態,以供稍後分析。例如,若要附加區域陣列變數 A 和向量 v 的總和作為鍵 s,您可以使用

A = ones(Int, 4, 4)
v = ones(100)
@info "Some variables"  A  s=sum(v)

# output
┌ Info: Some variables
│   A =
│    4×4 Matrix{Int64}:
│     1  1  1  1
│     1  1  1  1
│     1  1  1  1
│     1  1  1  1
└   s = 100.0

所有記錄巨集 @debug@info@warn@error 共享一些常見功能,這些功能在更通用的巨集 @logmsg 的文件中有詳細說明。

記錄事件結構

每個事件會產生多個資料片段,有些由使用者提供,有些則自動擷取。讓我們先檢查使用者定義的資料

  • 記錄層級 是訊息的廣泛類別,用於早期過濾。有數個標準層級的 LogLevel 類型;使用者定義的層級也是可能的。每個層級的目的都不同

    • Logging.Debug(記錄層級 -1000)是提供給程式開發人員的資訊。這些事件在預設情況下會停用。
    • Logging.Info(記錄層級 0)是提供給使用者的一般資訊。可以將它視為直接使用 println 的替代方案。
    • Logging.Warn(記錄層級 1000)表示有些地方出錯,可能需要採取行動,但目前程式仍然在執行。
    • Logging.Error(記錄層級 2000)表示有些地方出錯,而且不太可能復原,至少在程式碼的這個部分不太可能復原。通常不需要這個記錄層級,因為擲回例外狀況可以傳達所有需要的資訊。
  • 訊息 是描述事件的物件。根據慣例,傳遞為訊息的 AbstractString 會假設為 markdown 格式。其他類型會使用 print(io, obj)string(obj) 顯示為文字輸出,而安裝的記錄器中使用的其他多媒體顯示則可能使用 show(io,mime,obj)

  • 可選的關鍵字值對允許將任意資料附加到每個事件。有些關鍵字具有慣例意義,可能會影響事件的解譯方式(請參閱 @logmsg)。

系統也會為每個事件產生一些標準資訊

  • 擴充記錄巨集的模組
  • 記錄巨集在原始碼中發生的檔案
  • 訊息id,這是記錄巨集出現的原始碼陳述式的唯一固定識別碼。此識別碼旨在相當穩定,即使檔案的原始碼變更,只要記錄陳述式本身保持不變即可。
  • 事件的群組,預設設定為檔案的基本名稱,不含副檔名。這可以用於將訊息分類為比記錄層級更精細的類別(例如,所有不建議使用的警告都具有群組 :depwarn),或分類為模組內部或跨模組的邏輯群組。

請注意,預設不包含一些有用的資訊,例如事件時間。這是因為此類資訊的擷取成本可能很高,而且目前記錄器也可以動態取得。定義一個 自訂記錄器 很簡單,可以根據需要,用時間、回溯、全域變數值和其他有用的資訊來擴充事件資料。

處理記錄事件

如範例所示,記錄陳述式不會提到記錄事件的去向或處理方式。這是系統可組合且自然適用於並行使用的關鍵設計功能。它透過區分兩個不同的考量來達成此目的

  • 建立記錄事件是模組作者的考量,他們需要決定觸發事件的位置和要包含哪些資訊。
  • 記錄事件的處理 — 也就是顯示、篩選、彙總和記錄 — 是應用程式作者的考量,他們需要將多個模組整合到一個協作的應用程式中。

記錄器

事件的處理是由記錄器執行的,它是第一個看到事件的使用者可設定程式碼。所有記錄器都必須是 AbstractLogger 的子類型。

當觸發事件時,會透過尋找具有全域記錄器作為後備的任務本地記錄器來找到適當的記錄器。此處的想法是,應用程式程式碼知道記錄事件應如何處理,並且存在於呼叫堆疊的頂端某處。因此,我們應透過呼叫堆疊向上尋找以找出記錄器 — 也就是說,記錄器應動態範圍。(這與記錄器詞彙範圍的記錄架構形成對比;由模組作者明確提供或作為單純的全域變數。在這樣的系統中,在組合來自多個模組的功能時,很難控制記錄。)

全域記錄器可以使用 global_logger 設定,並使用 with_logger 控制任務本地記錄器。新產生的任務會繼承父任務的記錄器。

此函式庫提供三種類型的記錄器。 ConsoleLogger 是啟動 REPL 時您會看到的預設記錄器。它會以可讀文字格式顯示事件,並試圖提供簡單但使用者友善的格式化和篩選控制。 NullLogger 是在必要時捨棄所有訊息的便利方式;它是 devnull 串流的記錄等效項。 SimpleLogger 是一個非常簡化的文字格式化記錄器,主要用於偵錯記錄系統本身。

自訂記錄器應附帶在 參考區段 中所述函數的重載。

早期過濾和訊息處理

當事件發生時,會執行一些早期過濾步驟,以避免產生將被捨棄的訊息

  1. 訊息記錄層級會與全球最低層級(經由 disable_logging 設定)進行比對。這是一個粗略但極為便宜的全球設定。
  2. 會查詢目前的記錄器狀態,並將訊息層級與記錄器的快取最低層級進行比對,方法是呼叫 Logging.min_enabled_level。此行為可透過環境變數覆寫(稍後會進一步說明)。
  3. 會呼叫 Logging.shouldlog 函數,並提供目前的記錄器,以及一些可以靜態計算的最小資訊(層級、模組、群組、ID)。最實用的部分是,shouldlog 會傳遞事件 id,可根據快取謂詞提早捨棄事件。

如果所有這些檢查都通過,則會完整評估訊息和鍵值對,並透過 Logging.handle_message 函數傳遞給目前的記錄器。handle_message() 可能會根據需要執行其他過濾,並將事件顯示在螢幕上、儲存到檔案中等等。

預設會擷取並記錄產生記錄事件時發生的例外狀況。這可防止個別中斷事件使應用程式當機,這有助於在生產系統中啟用鮮少使用的偵錯事件。此行為可透過擴充 Logging.catch_exceptions,針對每個記錄器類型進行自訂。

測試記錄事件

記錄事件是執行正常程式碼的副作用,但你可能會發現自己想要測試特定的資訊訊息和警告。Test 模組提供了一個 @test_logs 巨集,可用於對照記錄事件串流進行模式配對。

環境變數

訊息過濾可透過 JULIA_DEBUG 環境變數來影響,並作為啟用檔案或模組偵錯記錄的簡易方式。使用 JULIA_DEBUG=loading 載入 julia 將會在 loading.jl 中啟用 @debug 記錄訊息。例如,在 Linux shell 中

$ JULIA_DEBUG=loading julia -e 'using OhMyREPL'
┌ Debug: Rejecting cache file /home/user/.julia/compiled/v0.7/OhMyREPL.ji due to it containing an invalid cache header
└ @ Base loading.jl:1328
[ Info: Recompiling stale cache file /home/user/.julia/compiled/v0.7/OhMyREPL.ji for module OhMyREPL
┌ Debug: Rejecting cache file /home/user/.julia/compiled/v0.7/Tokenize.ji due to it containing an invalid cache header
└ @ Base loading.jl:1328
...

在 Windows 中,可透過先執行 set JULIA_DEBUG="loading"CMD 中達成相同目的,並透過 $env:JULIA_DEBUG="loading"Powershell 中達成。

類似地,環境變數可用於啟用模組的偵錯記錄,例如 Pkg 或模組根目錄(請參閱 Base.moduleroot)。若要啟用所有偵錯記錄,請使用特殊值 all

若要從 REPL 開啟偵錯記錄,請將 ENV["JULIA_DEBUG"] 設定為感興趣模組的名稱。在 REPL 中定義的函式屬於模組 Main;它們的記錄可像這樣啟用

julia> foo() = @debug "foo"
foo (generic function with 1 method)

julia> foo()

julia> ENV["JULIA_DEBUG"] = Main
Main

julia> foo()
┌ Debug: foo
└ @ Main REPL[1]:1

使用逗號分隔符號來啟用多個模組的偵錯:JULIA_DEBUG=loading,Main

範例

範例:將記錄事件寫入檔案

有時將記錄事件寫入檔案會很有用。以下是使用任務區域和全域記錄器將資訊寫入文字檔案的範例

# Load the logging module
julia> using Logging

# Open a textfile for writing
julia> io = open("log.txt", "w+")
IOStream(<file log.txt>)

# Create a simple logger
julia> logger = SimpleLogger(io)
SimpleLogger(IOStream(<file log.txt>), Info, Dict{Any,Int64}())

# Log a task-specific message
julia> with_logger(logger) do
           @info("a context specific log message")
       end

# Write all buffered messages to the file
julia> flush(io)

# Set the global logger to logger
julia> global_logger(logger)
SimpleLogger(IOStream(<file log.txt>), Info, Dict{Any,Int64}())

# This message will now also be written to the file
julia> @info("a global log message")

# Close the file
julia> close(io)

範例:啟用偵錯層級訊息

以下是建立 ConsoleLogger 的範例,它會傳遞任何記錄層級高於或等於 Logging.Debug 的訊息。

julia> using Logging

# Create a ConsoleLogger that prints any log messages with level >= Debug to stderr
julia> debuglogger = ConsoleLogger(stderr, Logging.Debug)

# Enable debuglogger for a task
julia> with_logger(debuglogger) do
           @debug "a context specific log message"
       end

# Set the global logger
julia> global_logger(debuglogger)

參考

記錄模組

Logging.Logging模組

用於擷取、篩選和呈現記錄事件串流的公用程式。通常您不需要匯入 Logging 來建立記錄事件;對於此,標準記錄巨集(例如 @info)已由 Base 匯出,並預設提供。

建立事件

Logging.@logmsg巨集
@debug message  [key=value | value ...]
@info  message  [key=value | value ...]
@warn  message  [key=value | value ...]
@error message  [key=value | value ...]

@logmsg level message [key=value | value ...]

建立包含資訊性 訊息 的記錄記錄。為了方便起見,定義了四個記錄巨集 @debug@info@warn@error,它們會記錄標準嚴重性層級 DebugInfoWarnError@logmsg 允許將 level 透過程式設定為任何 LogLevel 或自訂記錄層級類型。

訊息應為一個表達式,其評估結果為字串,而該字串是記錄事件的人類可讀描述。根據慣例,這個字串在呈現時會以標記格式設定。

選用清單 key=value 對支援任意使用者定義的元資料,這些元資料會作為記錄的一部分傳遞到記錄後端。如果只提供 value 表達式,將會使用 Symbol 產生一個代表表達式的金鑰。例如,x 會變成 x=x,而 foo(10) 會變成 Symbol("foo(10)")=foo(10)。要展開金鑰值對清單,請使用一般的展開語法 @info "blah" kws...

有一些金鑰允許自動產生的記錄資料被覆寫

  • _module=mod 可用於指定不同於訊息來源位置的原始模組。
  • _group=symbol 可用於覆寫訊息群組(這通常會從來源檔案的基本名稱衍生而來)。
  • _id=symbol 可用於覆寫自動產生的唯一訊息識別碼。如果您需要非常緊密地關聯在不同來源列上產生的訊息,這會很有用。
  • _file=string_line=integer 可用於覆寫記錄訊息的明顯來源位置。

還有一些金鑰值對具有慣例意義

  • maxlog=integer 應作為後端的提示,表示訊息不應顯示超過 maxlog 次。
  • exception=ex 應與記錄訊息一起用於傳輸例外,通常與 @error 一起使用。關聯的後溯 bt 可以使用元組 exception=(ex,bt) 附加。

範例

@debug "Verbose debugging information.  Invisible by default"
@info  "An informational message"
@warn  "Something was odd.  You should pay attention"
@error "A non fatal error occurred"

x = 10
@info "Some variables attached to the message" x a=42.0

@debug begin
    sA = sum(A)
    "sum(A) = $sA is an expensive operation, evaluated only when `shouldlog` returns true"
end

for i=1:10000
    @info "With the default backend, you will only see (i = $i) ten times"  maxlog=10
    @debug "Algorithm1" i progress=i/10000
end
來源
Logging.LogLevel類型
LogLevel(level)

記錄的嚴重性/詳細程度。

記錄層級提供一個關鍵,用於在執行任何其他工作以建構記錄資料結構本身之前,過濾潛在的記錄。

範例

julia> Logging.LogLevel(0) == Logging.Info
true
來源

使用 AbstractLogger 處理事件

事件處理受控於覆寫與 AbstractLogger 關聯的函式

要實作的方法簡要說明
Logging.handle_message處理記錄事件
Logging.shouldlog事件的早期過濾
Logging.min_enabled_level可接受事件的記錄層級下限
選用方法預設定義簡要說明
Logging.catch_exceptionstrue在事件評估期間捕捉例外
Logging.AbstractLogger類型

記錄器控制記錄的過濾和傳遞方式。當產生記錄時,記錄器是第一個使用者可設定的程式碼,用於檢查記錄並決定如何處理它。

來源
Logging.handle_message函式
handle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)

將訊息記錄到 logger 中的 level。訊息產生的邏輯位置由模組 _modulegroup 提供;來源位置由 fileline 提供。id 是任意唯一的數值(通常是 Symbol),用作過濾時識別記錄訊息的關鍵字。

來源
Logging.shouldlog函數
shouldlog(logger, level, _module, group, id)

logger 接受 level 的訊息,並為 _modulegroup 產生,且具有唯一的記錄識別碼 id 時,傳回 true

來源
Logging.min_enabled_level函數
min_enabled_level(logger)

傳回 logger 的最小啟用層級,以進行早期過濾。也就是說,所有訊息都過濾的記錄層級低於或等於此層級。

來源
Logging.catch_exceptions函數
catch_exceptions(logger)

如果記錄器應捕捉記錄記錄建構期間發生的例外狀況,則傳回 true。預設會捕捉訊息

預設會捕捉所有例外狀況,以防止記錄訊息產生導致程式崩潰。這讓使用者能在生產系統中自信地切換鮮少使用的功能(例如除錯記錄)。

如果您想使用記錄作為稽核追蹤,應為記錄器類型停用此功能。

來源
Logging.disable_logging函數
disable_logging(level)

停用所有記錄層級等於或小於 level 的記錄訊息。這是全域設定,目的是在停用時讓除錯記錄極為便宜。

範例

Logging.disable_logging(Logging.Info) # Disable debug and info
來源

使用記錄器

記錄器安裝和檢查

Logging.global_logger函數
global_logger()

傳回全域記錄器,用於在當前任務沒有特定記錄器時接收訊息。

global_logger(logger)

將全域記錄器設定為 logger,並傳回先前的全域記錄器。

來源
Logging.with_logger函數
with_logger(function, logger)

執行 function,將所有記錄訊息導向 logger

範例

function test(x)
    @info "x = $x"
end

with_logger(logger) do
    test(1)
    test([1,2])
end
來源
Logging.current_logger函數
current_logger()

傳回當前任務的記錄器,或如果沒有附加記錄器到任務,則傳回全域記錄器。

來源

系統提供的記錄器

Logging.NullLogger類型
NullLogger()

停用所有訊息且不產生任何輸出的記錄器,是 /dev/null 的記錄器等效項。

來源
Logging.ConsoleLogger類型
ConsoleLogger([stream,] min_level=Info; meta_formatter=default_metafmt,
              show_limited=true, right_justify=0)

針對在文字主控台中可讀性最佳化的記錄器,例如與 Julia REPL 的互動工作。

小於 min_level 的記錄等級會被濾除。

訊息格式化可以透過設定關鍵字參數來控制

  • meta_formatter 是一個函數,它會取得記錄事件的元資料 (level, _module, group, id, file, line),並傳回記錄訊息的顏色(會傳遞給 printstyled)、前置詞和後置詞。預設值會加上記錄等級的前置詞,以及包含模組、檔案和行位置的後置詞。
  • show_limited 會在格式化期間設定 :limit IOContext 鍵,將大型資料結構的列印限制在螢幕上可以容納的範圍內。
  • right_justify 是整數欄,其中日誌中繼資料會靠右對齊。預設為零(中繼資料會在自己的行上)。
Logging.SimpleLogger類型
SimpleLogger([stream,] min_level=Info)

簡化的記錄器,用於將所有層級大於或等於 min_level 的訊息記錄到 stream。如果串流已關閉,則層級大於或等於 Warn 的訊息將記錄到 stderr,而低於此層級的訊息將記錄到 stdout

來源