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
).
Система также генерирует некоторую стандартную информацию для каждого события:
модуль
, в котором была развернута макрос для логирования.file
иline
, где происходит макрос логирования в исходном коде.- Сообщение
id
, которое является уникальным, фиксированным идентификатором для оператора исходного кода, в котором появляется макрос ведения журнала. Этот идентификатор разработан так, чтобы быть довольно стабильным, даже если исходный код файла изменяется, при условии, что сам оператор ведения журнала остается прежним. - Группа
group
для события, которая по умолчанию устанавливается в базовое имя файла без расширения. Это можно использовать для более тонкой группировки сообщений по категориям, чем уровень журнала (например, все предупреждения об устаревании имеют группу:depwarn
), или для логической группировки по модулям или внутри них.
Обратите внимание, что такая полезная информация, как время события, по умолчанию не включена. Это связано с тем, что такая информация может быть дорогостоящей для извлечения и также динамически доступна текущему логгеру. Просто определить custom logger, чтобы дополнить данные события временем, трассировкой, значениями глобальных переменных и другой полезной информацией по мере необходимости.
Processing log events
Как вы можете видеть в примерах, операторы журналирования не упоминают, куда идут события журнала или как они обрабатываются. Это ключевая особенность дизайна, которая делает систему составной и естественной для параллельного использования. Это достигается за счет разделения двух различных аспектов:
- Создание лог-событий является задачей автора модуля, который должен решить, где будут вызываться события и какую информацию включить.
- Обработка логов событий — то есть отображение, фильтрация, агрегация и запись — это задача автора приложения, который должен объединить несколько модулей в совместно работающем приложении.
Loggers
Обработка событий выполняется логгером, который является первым элементом настраиваемого пользователем кода, который видит событие. Все логгеры должны быть подтипами 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 с JULIA_DEBUG=loading
активирует сообщения логов @debug
в loading.jl
. Например, в оболочках Linux:
$ 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 сообщение [ключ=значение | значение ...]
@info сообщение [ключ=значение | значение ...]
@warn сообщение [ключ=значение | значение ...]
@error сообщение [ключ=значение | значение ...]
@logmsg уровень сообщение [ключ=значение | значение ...]
Создайте запись журнала с информационным `сообщением`. Для удобства определены четыре макроса для ведения журнала `@debug`, `@info`, `@warn` и `@error`, которые ведут журнал на стандартных уровнях серьезности `Debug`, `Info`, `Warn` и `Error`. `@logmsg` позволяет установить `уровень` программно на любой `LogLevel` или пользовательские типы уровней журнала.
`сообщение` должно быть выражением, которое оценивается в строку, представляющую собой читаемое описание события журнала. По соглашению, эта строка будет отформатирована как markdown при представлении.
Необязательный список пар `ключ=значение` поддерживает произвольные пользовательские метаданные, которые будут переданы в бэкенд журнала как часть записи журнала. Если предоставлено только выражение `значение`, будет сгенерирован ключ, представляющий выражение, с использованием [`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`. Связанный трассировка `bt` может быть прикреплена с использованием кортежа `exception=(ex,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
и группой; исходное местоположение — файлом и строкой. id
— это произвольное уникальное значение (обычно Symbol
), которое будет использоваться в качестве ключа для идентификации оператора журнала при фильтрации.
Logging.shouldlog
— Functionshouldlog(logger, level, _module, group, id)
Возвращает true
, когда logger
принимает сообщение на уровне level
, сгенерированное для _module
, group
и с уникальным идентификатором лога id
.
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
Установка и проверка логгера:
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
.