Logging

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 的文档中有详细描述。

Log event structure

每个事件生成多个数据项,其中一些由用户提供,另一些则是自动提取的。我们先来看看用户定义的数据:

  • 日志级别 是用于早期过滤的消息的广泛类别。 有几种标准级别,类型为 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)。

系统还为每个事件生成一些标准信息:

  • 扩展日志宏的 module
  • 在源代码中记录宏发生的 fileline
  • 一个消息 id 是一个唯一的、固定的标识符,用于标识日志宏出现的 源代码语句。这个标识符的设计旨在保持相对稳定,即使文件的源代码发生变化,只要日志语句本身保持不变。
  • 一个 group 用于事件,默认设置为文件的基本名称,不带扩展名。 这可以用于将消息更细致地分组到类别中,而不仅仅是日志级别(例如,所有弃用警告都有组 :depwarn),或者在模块之间或内部进行逻辑分组。

请注意,某些有用的信息,例如事件时间,默认情况下并未包含。这是因为提取此类信息可能代价高昂,并且对于当前记录器来说也是动态可用的。定义一个 custom logger 来根据需要增强事件数据,包括时间、回溯、全局变量的值和其他有用信息是很简单的。

Processing log events

正如您在示例中所看到的,日志语句并未提及日志事件的去向或处理方式。这是一个关键的设计特性,使系统具有可组合性,并且适合并发使用。它通过分离两个不同的关注点来实现这一点:

  • 创建 日志事件是模块作者的责任,他们需要决定事件触发的位置以及包含哪些信息。
  • 处理日志事件——即显示、过滤、聚合和记录——是应用程序作者的关注点,他们需要将多个模块整合成一个协作的应用程序。

Loggers

事件的处理由一个 logger 执行,它是第一个可配置的用户代码,用于查看事件。所有的日志记录器必须是 AbstractLogger 的子类型。

当事件被触发时,通过查找任务本地的记录器并以全局记录器作为后备,找到合适的记录器。这里的想法是应用程序代码知道日志事件应该如何处理,并且存在于调用栈的顶部。因此,我们应该向上查找调用栈以发现记录器——也就是说,记录器应该是动态作用域的。(这与记录框架的一个对比点是,记录器是词法作用域的;由模块作者显式提供或作为简单的全局变量。在这样的系统中,在组合多个模块的功能时控制日志记录是很尴尬的。)

全局记录器可以通过 global_logger 设置,任务本地记录器可以通过 with_logger 控制。新生成的任务会继承父任务的记录器。

库提供了三种记录器类型。 ConsoleLogger 是您在启动 REPL 时看到的默认记录器。它以可读的文本格式显示事件,并尝试提供简单但用户友好的格式化和过滤控制。 NullLogger 是一种方便的方式,可以在必要时丢弃所有消息;它是 devnull 流的记录等价物。 SimpleLogger 是一种非常简单的文本格式化记录器,主要用于调试记录系统本身。

自定义日志记录器应提供对reference section中描述的函数的重载。

Early filtering and message handling

当事件发生时,会进行一些早期过滤步骤,以避免生成将被丢弃的消息:

  1. 消息日志级别会与全局最低级别进行检查(通过 disable_logging 设置)。这是一个粗糙但极其便宜的全局设置。
  2. 当前的日志记录器状态被查找,并且消息级别与日志记录器缓存的最小级别进行检查,这可以通过调用 Logging.min_enabled_level 来实现。此行为可以通过环境变量进行覆盖(稍后会详细介绍)。
  3. Logging.shouldlog 函数被当前日志记录器调用,接受一些最小的信息(级别、模块、组、id),这些信息可以静态计算。最有用的是,shouldlog 被传递一个事件 id,可以用来根据缓存的谓词提前丢弃事件。

如果所有这些检查都通过,消息和键值对将被完全评估并通过 Logging.handle_message 函数传递给当前的记录器。handle_message() 可能会根据需要执行额外的过滤,并将事件显示在屏幕上,保存到文件等。

在生成日志事件时发生的异常会被默认捕获并记录。这可以防止单个损坏的事件导致应用程序崩溃,这在生产系统中启用不常用的调试事件时非常有用。此行为可以通过扩展 Logging.catch_exceptions 来根据日志记录器类型进行自定义。

Testing log events

日志事件是运行正常代码的副作用,但您可能会发现自己想要测试特定的信息消息和警告。Test模块提供了一个@test_logs宏,可以用于对日志事件流进行模式匹配。

Environment variables

消息过滤可以通过 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 incompatible 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 incompatible cache header
└ @ Base loading.jl:1328
...

在 Windows 上,可以通过首先在 CMD 中运行 set JULIA_DEBUG="loading",以及在 Powershell 中运行 $env:JULIA_DEBUG="loading" 来实现相同的效果。

同样,环境变量可以用来启用模块的调试日志记录,例如 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

Examples

Example: Writing log events to a file

有时将日志事件写入文件是有用的。以下是如何使用任务本地和全局记录器将信息写入文本文件的示例:

# 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)

Example: Enable debug-level messages

这是一个创建 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)

Reference

Logging module

Logging.LoggingModule

用于捕获、过滤和呈现日志事件流的工具。通常情况下,您不需要导入 Logging 来创建日志事件;为此,标准日志宏,例如 @info,已经由 Base 导出并默认可用。

source

Creating events

Logging.@logmsgMacro
@debug 消息  [key=value | value ...]
@info  消息  [key=value | value ...]
@warn  消息  [key=value | value ...]
@error 消息  [key=value | value ...]

@logmsg 级别 消息 [key=value | value ...]

创建一个包含信息性 `消息` 的日志记录。为了方便,定义了四个日志宏 `@debug`、`@info`、`@warn` 和 `@error`,它们在标准严重性级别 `Debug`、`Info`、`Warn` 和 `Error` 下进行日志记录。`@logmsg` 允许 `级别` 被程序化设置为任何 `LogLevel` 或自定义日志级别类型。

`消息` 应该是一个表达式,评估为一个字符串,作为日志事件的可读描述。根据惯例,当呈现时,这个字符串将被格式化为 markdown。

可选的 `key=value` 对列表支持任意用户定义的元数据,这些元数据将作为日志记录的一部分传递给日志后端。如果只提供了一个 `value` 表达式,将使用 [`Symbol`](@ref) 生成一个表示该表达式的键。例如,`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` 一起使用。可以使用元组 `exception=(ex,bt)` 附加相关的回溯 `bt`。

# 示例

julia @debug "详细调试信息。默认情况下不可见" @info "一条信息性消息" @warn "有些事情很奇怪。你应该注意" @error "发生了一个非致命错误"

x = 10 @info "附加到消息的一些变量" x a=42.0

@debug begin sA = sum(A) "sum(A) = sA 是一个昂贵的操作,仅在 shouldlog 返回 true 时评估" end

for i=1:10000 @info "使用默认后端,你将只看到 (i = i) 十次" maxlog=10 @debug "算法1" i progress=i/10000 end ```

source
Logging.LogLevelType
LogLevel(level)

日志记录的严重性/详细程度。

日志级别提供了一个关键字,可以在构建日志记录数据结构之前,对潜在的日志记录进行过滤。

示例

julia> Logging.LogLevel(0) == Logging.Info
true
source

Processing events with AbstractLogger

事件处理是通过重写与 AbstractLogger 相关的函数来控制的:

Methods to implementBrief description
Logging.handle_messageHandle a log event
Logging.shouldlogEarly filtering of events
Logging.min_enabled_levelLower bound for log level of accepted events
Optional methodsDefault definitionBrief description
Logging.catch_exceptionstrueCatch exceptions during event evaluation
Logging.AbstractLoggerType

一个记录器控制日志记录的过滤和分发。当生成日志记录时,记录器是第一段用户可配置的代码,它会检查记录并决定如何处理它。

source
Logging.handle_messageFunction
handle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)

将消息记录到 logger,级别为 level。消息生成的逻辑位置由模块 _modulegroup 给出;源位置由 fileline 给出。id 是一个任意的唯一值(通常是一个 Symbol),用于在过滤时作为标识日志语句的键。

source
Logging.shouldlogFunction
shouldlog(logger, level, _module, group, id)

logger 接受在 level 级别生成的针对 _modulegroup 和具有唯一日志标识符 id 的消息时,返回 true

source
Logging.min_enabled_levelFunction
min_enabled_level(logger)

返回 logger 的最低启用级别,以便进行早期过滤。也就是说,所有消息都将在该日志级别以下或等于该级别时被过滤。

source
Logging.catch_exceptionsFunction
catch_exceptions(logger)

如果记录器应该捕获在日志记录构造期间发生的异常,则返回 true。 默认情况下,消息会被捕获。

默认情况下,所有异常都会被捕获,以防止日志消息生成导致程序崩溃。这使得用户可以自信地在生产系统中切换不常用的功能,例如调试日志记录。

如果您想将日志记录用作审计跟踪,则应为您的记录器类型禁用此功能。

source
Logging.disable_loggingFunction
disable_logging(level)

禁用所有日志消息,日志级别等于或低于 level。这是一个 全局 设置,旨在使调试日志在禁用时极其便宜。

示例

Logging.disable_logging(Logging.Info) # 禁用调试和信息
source

Using Loggers

Logger 安装和检查:

Logging.global_loggerFunction
global_logger()

返回全局日志记录器,用于在当前任务没有特定日志记录器时接收消息。

global_logger(logger)

将全局日志记录器设置为 logger,并返回之前的全局日志记录器。

source
Logging.with_loggerFunction
with_logger(function, logger)

执行 function,将所有日志消息定向到 logger

示例

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

with_logger(logger) do
    test(1)
    test([1,2])
end
source
Logging.current_loggerFunction
current_logger()

返回当前任务的日志记录器,如果任务没有附加日志记录器,则返回全局日志记录器。

source

系统提供的日志记录器:

Logging.NullLoggerType
NullLogger()

禁用所有消息并且不产生输出的日志记录器 - 相当于 /dev/null 的日志记录器。

source
Base.CoreLogging.ConsoleLoggerType
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 是日志元数据右对齐的整数列。默认值为零(元数据单独占一行)。
source
Logging.SimpleLoggerType
SimpleLogger([stream,] min_level=Info)

简单的日志记录器,用于将所有级别大于或等于 min_level 的消息记录到 stream。如果流被关闭,则级别大于或等于 Warn 的消息将记录到 stderr,而级别较低的消息将记录到 stdout

source