Ведение журнала
Модуль 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).
Система также генерирует стандартную информацию для каждого события:
- Файл, в котором был развернут макрос ведения журнала.
- Строка и столбец, где встречается макрос ведения журнала в исходном коде.
- Уникальный, фиксированный идентификатор для оператора исходного кода, где появляется макрос ведения журнала. Этот идентификатор разработан таким образом, чтобы быть достаточно стабильным, даже если исходный код файла изменяется, при условии, что само оператор ведения журнала остается неизменным.
- Группа для события, которая по умолчанию устанавливается в базовое имя файла без расширения. Это можно использовать для группирования сообщений в более мелкие категории, чем уровень журнала (например, все предупреждения о устаревании имеют группу
:depwarn), или в логические группы по или внутри модулей.
Обратите внимание, что некоторые полезные сведения, такие как время события, по умолчанию не включены. Это связано с тем, что такая информация может быть дорогостоящей для извлечения и также динамически доступна текущему средству ведения журнала. Легко определить пользовательское средство ведения журнала для дополнения данных события временем, трассировкой стека вызовов, значениями глобальных переменных и другой полезной информацией по мере необходимости.
Обработка событий журнала
Как видно из примеров, операторы ведения журнала ничего не говорят о том, куда попадают события журнала или как они обрабатываются. Это ключевая особенность проектирования, которая делает систему композиционной и естественной для одновременного использования. Она достигает этого, разделяя две разные задачи:
- Создание событий журнала — это задача автора модуля, который должен решить, где срабатывают события и какую информацию следует включить.
- Обработка событий журнала — то есть отображение, фильтрация, агрегирование и запись — это задача автора приложения, которому необходимо объединить несколько модулей в сотрудничающее приложение.
Средства ведения журнала
Обработка событий выполняется средством ведения журнала, которое является первой настраиваемой частью кода пользователя, которая видит событие. Все средства ведения журнала должны быть подтипами AbstractLogger.
Когда возникает событие, соответствующее средство ведения журнала находится путем поиска локального средства ведения журнала с глобальным средством ведения журнала в качестве резервного варианта. Идея в том, что код приложения знает, как должны обрабатываться события журнала и существует где-то в верхней части стека вызовов. Поэтому мы должны искать вверх по стеку вызовов, чтобы обнаружить средство ведения журнала — то есть средство ведения журнала должно быть динамически областью видимости. (Это момент отличия от фреймворков ведения журнала, где средство ведения журнала имеет лексическую область видимости; явно предоставляется автором модуля или как простая глобальная переменная. В такой системе неудобно управлять ведением журнала при составлении функциональности из нескольких модулей.)
Глобальное средство ведения журнала может быть установлено с помощью global_logger, а локальные для задачи средства ведения журнала — с помощью with_logger. Новые задачи наследуют средство ведения журнала родительской задачи.
Библиотека предоставляет три типа средств ведения журнала. ConsoleLogger — это средство ведения журнала по умолчанию, которое вы видите при запуске REPL. Он отображает события в удобочитаемом текстовом формате и пытается обеспечить простой, но удобный для пользователя контроль над форматированием и фильтрацией. NullLogger — удобный способ отбросить все сообщения там, где это необходимо; это эквивалент потока devnull в ведении журнала. SimpleLogger — это очень простое средство ведения журнала с форматированием текста, в основном полезное для отладки самой системы ведения журнала.
Пользовательские средства ведения журнала должны сопровождаться перегрузками функций, описанных в разделе справочника.
Ранняя фильтрация и обработка сообщений
Когда происходит событие, происходит несколько этапов ранней фильтрации для предотвращения генерации сообщений, которые будут отброшены:
- Проверяется уровень журнала сообщения на предмет соответствия глобальному минимальному уровню (установленному через
disable_logging). Это грубое, но чрезвычайно недорогое глобальное значение. - Просматривается текущее состояние средства ведения журнала, и уровень сообщения проверяется на предмет соответствия минимальному уровню средства ведения журнала, как полученному с помощью вызова
Logging.min_enabled_level. Это поведение можно переопределить с помощью переменных среды (подробнее об этом позже). - Вызывается функция
Logging.shouldlogс текущим средством ведения журнала, принимая минимальную информацию (уровень, модуль, группа, идентификатор), которая может быть вычислена статически. Самым полезным образомshouldlogполучает событиеid, что позволяет отбрасывать события на ранней стадии на основе кэшируемого предиката.
Если все эти проверки пройдены, сообщение и пары «ключ-значение» полностью вычисляются и передаются текущему средству ведения журнала через функцию Logging.handle_message. handle_message() может выполнять дополнительную фильтрацию по мере необходимости и отображать событие на экране, сохранять его в файле и т. д.
Исключения, возникающие при генерации события журнала, по умолчанию перехватываются и регистрируются. Это предотвращает то, что отдельные неисправные события приведут к аварийному завершению приложения, что полезно при включении редко используемых отладочных событий в рабочей системе. Это поведение можно настроить для каждого типа средства ведения журнала путем расширения Logging.catch_exceptions.
Тестирование событий журнала
События журнала являются побочным эффектом выполнения обычного кода, но вы можете захотеть протестировать определённые информационные сообщения и предупреждения. Модуль Test предоставляет макрос @test_logs, который можно использовать для сопоставления с образцом потока событий журнала.
Переменные среды
Фильтрацию сообщений можно контролировать через переменную среды 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 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 то же самое можно сделать в 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.
Примеры
Пример: Запись событий журнала в файл
Иногда может быть полезно записывать события журнала в файл. Вот пример того, как использовать локальное для задачи и глобальное средство ведения журнала для записи информации в текстовый файл:
# 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 ...]
Создайте запись журнала с информационным message. Для удобства определены четыре макроса ведения журнала @debug, @info, @warn и @error, которые записывают на стандартных уровнях серьезности Debug, Info, Warn и Error. @logmsg позволяет level программно устанавливать любой LogLevel или пользовательский тип уровня журнала.
message должно быть выражением, которое вычисляется в строку, представляющую собой удобочитаемое описание события журнала. По соглашению эта строка будет отформатирована как разметка Markdown при представлении.
Необязательный список 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исходный код
Logging.DebugКонстанта
Debug
Псевдоним для LogLevel(-1000).
Logging.InfoКонстанта
Info
Псевдоним для LogLevel(0).
Logging.WarnКонстанта
Warn
Псевдоним для LogLevel(1000).
Logging.ErrorКонстанта
Error
Псевдоним для LogLevel(2000).
Обработка событий с AbstractLogger
Обработка событий контролируется путем переопределения функций, связанных с AbstractLogger:
| Методы для реализации | Краткое описание | |
|---|---|---|
Logging.handle_message |
Обработка события журнала | |
Logging.shouldlog |
Предварительный фильтр событий | |
Logging.min_enabled_level |
Нижняя граница уровня журнала для принимаемых событий | |
| Необязательные методы | Определение по умолчанию | Краткое описание |
Logging.catch_exceptions |
true |
Перехват исключений во время оценки событий |
Logging.AbstractLoggerТип
Журнал контролирует, как фильтруются и рассылаются записи журнала. При генерации записи журнала журнал — это первый фрагмент настраиваемого пользователем кода, который может просмотреть запись и решить, что с ней делать.
исходный код
Logging.handle_messageФункция
handle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)
Записать сообщение в logger в level. Логическое расположение, в котором было сгенерировано сообщение, задано модулем _module и group; расположение источника — file и line. id — произвольное уникальное значение (обычно Symbol), используемое в качестве ключа для идентификации оператора журнала при фильтрации.
Logging.shouldlogФункция
shouldlog(logger, level, _module, group, id)
Возвращает true если logger принимает сообщение в level, сгенерированное для _module, group и с уникальным идентификатором журнала id.
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— ограничивает вывод больших структур данных тем, что умещается на экране, устанавливая ключ:limitIOContextво время форматирования. -
right_justify— целое число столбца, в котором метаданные журнала выравниваются вправо. По умолчанию ноль (метаданные идут на отдельной строке).
Logging.SimpleLoggerТип
SimpleLogger([stream,] min_level=Info)
Простой журнал для записи всех сообщений с уровнем, равным или превышающим min_level в stream. Если поток закрыт, сообщения с уровнем журнала, равным или превышающим Warn, будут записаны в stderr, а ниже — в stdout.
© 2009–2023 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.9/stdlib/Logging/