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
。 - 在源代码中记录宏发生的
file
和line
。 - 一个消息
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
当事件发生时,会进行一些早期过滤步骤,以避免生成将被丢弃的消息:
- 消息日志级别会与全局最低级别进行检查(通过
disable_logging
设置)。这是一个粗糙但极其便宜的全局设置。 - 当前的日志记录器状态被查找,并且消息级别与日志记录器缓存的最小级别进行检查,这可以通过调用
Logging.min_enabled_level
来实现。此行为可以通过环境变量进行覆盖(稍后会详细介绍)。 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.Logging
— Module用于捕获、过滤和呈现日志事件流的工具。通常情况下,您不需要导入 Logging
来创建日志事件;为此,标准日志宏,例如 @info
,已经由 Base
导出并默认可用。
Creating events
Logging.@logmsg
— Macro@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 ```
Logging.LogLevel
— TypeLogLevel(level)
日志记录的严重性/详细程度。
日志级别提供了一个关键字,可以在构建日志记录数据结构之前,对潜在的日志记录进行过滤。
示例
julia> Logging.LogLevel(0) == Logging.Info
true
Logging.Debug
— Constant调试
LogLevel(-1000)
的别名。
Logging.Info
— Constant信息
LogLevel(0)
的别名。
Logging.Warn
— Constant警告
LogLevel(1000)
的别名。
Logging.Error
— Constant错误
LogLevel(2000)
的别名。
Logging.BelowMinLevel
— ConstantBelowMinLevel
LogLevel(-1_000_001)
的别名。
Logging.AboveMaxLevel
— ConstantAboveMaxLevel
LogLevel(1_000_001)
的别名。
Processing events with AbstractLogger
事件处理是通过重写与 AbstractLogger
相关的函数来控制的:
Methods to implement | Brief description | |
---|---|---|
Logging.handle_message | Handle a log event | |
Logging.shouldlog | Early filtering of events | |
Logging.min_enabled_level | Lower bound for log level of accepted events | |
Optional methods | Default definition | Brief description |
Logging.catch_exceptions | true | Catch exceptions during event evaluation |
Logging.AbstractLogger
— Type一个记录器控制日志记录的过滤和分发。当生成日志记录时,记录器是第一段用户可配置的代码,它会检查记录并决定如何处理它。
Logging.handle_message
— Functionhandle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)
将消息记录到 logger
,级别为 level
。消息生成的逻辑位置由模块 _module
和 group
给出;源位置由 file
和 line
给出。id
是一个任意的唯一值(通常是一个 Symbol
),用于在过滤时作为标识日志语句的键。
Logging.shouldlog
— Functionshouldlog(logger, level, _module, group, id)
当 logger
接受在 level
级别生成的针对 _module
、group
和具有唯一日志标识符 id
的消息时,返回 true
。
Logging.min_enabled_level
— Functionmin_enabled_level(logger)
返回 logger
的最低启用级别,以便进行早期过滤。也就是说,所有消息都将在该日志级别以下或等于该级别时被过滤。
Logging.catch_exceptions
— Functioncatch_exceptions(logger)
如果记录器应该捕获在日志记录构造期间发生的异常,则返回 true
。 默认情况下,消息会被捕获。
默认情况下,所有异常都会被捕获,以防止日志消息生成导致程序崩溃。这使得用户可以自信地在生产系统中切换不常用的功能,例如调试日志记录。
如果您想将日志记录用作审计跟踪,则应为您的记录器类型禁用此功能。
Logging.disable_logging
— Functiondisable_logging(level)
禁用所有日志消息,日志级别等于或低于 level
。这是一个 全局 设置,旨在使调试日志在禁用时极其便宜。
示例
Logging.disable_logging(Logging.Info) # 禁用调试和信息
Using Loggers
Logger 安装和检查:
Logging.global_logger
— Functionglobal_logger()
返回全局日志记录器,用于在当前任务没有特定日志记录器时接收消息。
global_logger(logger)
将全局日志记录器设置为 logger
,并返回之前的全局日志记录器。
Logging.with_logger
— Functionwith_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
— Functioncurrent_logger()
返回当前任务的日志记录器,如果任务没有附加日志记录器,则返回全局日志记录器。
系统提供的日志记录器:
Logging.NullLogger
— TypeNullLogger()
禁用所有消息并且不产生输出的日志记录器 - 相当于 /dev/null 的日志记录器。
Base.CoreLogging.ConsoleLogger
— TypeConsoleLogger([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
— TypeSimpleLogger([stream,] min_level=Info)
简单的日志记录器,用于将所有级别大于或等于 min_level
的消息记录到 stream
。如果流被关闭,则级别大于或等于 Warn
的消息将记录到 stderr
,而级别较低的消息将记录到 stdout
。