Ведение журнала
Модуль 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; также возможны пользовательские уровни. Каждый из них имеет разную цель:-
Debugпредназначена для разработчика программы.
Эти события отключены по умолчанию.
-
Infoпредназначена для общей информации для пользователя.
Представьте это как альтернативу прямому использованию
println.-
Warnозначает, что что-то не так, и, вероятно, требуется действие
, но пока программа все еще работает.
-
Errorозначает, что что-то не так, и, скорее всего, это не удастся исправить,
по крайней мере, этой частью кода. Зачастую этот уровень журнала не нужен, так как выброс исключения может передать всю необходимую информацию.
-
Сообщение — это объект, описывающий событие. По соглашению
AbstractStringв качестве сообщений предполагаются в формате Markdown. Другие типы будут отображаться с использованиемprint(io, obj)илиstring(obj)для текстового вывода и, возможно,show(io,mime,obj)для других мультимедийных отображений, используемых установленным логгером.Дополнительные пар ключ-значение позволяют добавлять произвольные данные к каждому событию. Некоторые ключи имеют условное значение, которое может повлиять на способ интерпретации события (см.
@logmsg).
Система также генерирует некоторые стандартные данные для каждого события:
- Файл
module, в котором был расширен макрос ведения журнала. - Строка
fileиlineместа расположения макроса ведения журнала в исходном коде. - Сообщение
id— это уникальный, фиксированный идентификатор инструкции исходного кода, где появляется макрос ведения журнала. Этот идентификатор предназначен для обеспечения достаточной стабильности даже при изменении исходного кода файла, если само утверждение ведения журнала остается неизменным. - Группа
groupдля события, которая по умолчанию устанавливается в имя файла без расширения. Это может быть использовано для группировки сообщений в более мелкие категории, чем уровень журнала (например, все предупреждения о устаревании имеют группу: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:
$ 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 ...
Аналогичным образом, переменная окружения может использоваться для включения отладочного ведения журнала модулей, таких как 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
Запись событий журнала в файл
Иногда может быть полезно записывать события журнала в файл. Вот пример того, как использовать локальный и глобальный логгеры для записи информации в текстовый файл:
# 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)
Справочник
Модуль ведения журнала
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исходный код
Обработка событий с 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=stderr, 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=stderr, min_level=Info)
Простой журнализатор для записи всех сообщений с уровнем не меньше min_level в stream.
© 2009–2021 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.6.0/stdlib/Logging/