Ведение журнала
Модуль 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сообщения, передаваемые в качестве сообщений, предполагаются в формате маркировки. Другие типы будут отображаться с использованиемprint(io, obj)илиstring(obj)для текстового вывода и, возможно,show(io,mime,obj)для других мультимедийных дисплеев, используемых в установленной программе ведения журнала.Дополнительные пары «ключ-значение» позволяют прикреплять произвольные данные к каждому событию. Некоторые ключи имеют общепринятое значение, которое может повлиять на то, как событие интерпретируется (см.
@logmsg).
Система также генерирует некоторую стандартную информацию для каждого события:
- Строка, в которой был развернут макрос ведения журнала.
- Строка и столбец, где появляется макрос ведения журнала в исходном коде.
- Сообщение
id— это уникальный, фиксированный идентификатор для утверждения исходного кода, где появляется макрос ведения журнала. Этот идентификатор предназначен для обеспечения достаточной стабильности даже при изменении исходного кода файла, пока само утверждение ведения журнала остается неизменным. - Группа для события, которая по умолчанию устанавливается в базовое имя файла без расширения. Это можно использовать для группирования сообщений в более мелкие категории, чем уровень журнала (например, все предупреждения об устаревании имеют группу
: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–2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.10/stdlib/Logging/