Ведение журнала
Модуль 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).
Система также генерирует некоторую стандартную информацию для каждого события:
- Место, в котором макрос ведения журнала был расширен.
- Строка и столбец, где находится макрос ведения журнала в исходном коде.
- Сообщение
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
Используйте разделитель запятых для включения отладки для нескольких модулей: 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Модуль
Утилиты для захвата, фильтрации и представления потоков событий логирования. Обычно нет необходимости импортировать 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–2022 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.8/stdlib/Logging/