Руководство по ведению журнала
- Автор:
-
Vinay Sajip <vinay_sajip at red-dove dot com>
На этой странице содержится учебная информация. Ссылки на справочную информацию и сборник рецептов по ведению журнала см. в разделе Другие ресурсы.
Основы ведения журнала
Ведение журнала — это способ отслеживать события, происходящие при работе программного обеспечения. Разработчик добавляет в код вызовы ведения журнала, чтобы указать на возникновение определённых событий. Событие описывается сообщением, которое при необходимости может содержать переменные данные (то есть данные, потенциально различающиеся при каждом возникновении события). Событиям также присваивается важность, которую определяет разработчик; важность также называют уровнем или степенью серьёзности.
Когда использовать ведение журнала
Чтобы получить доступ к функциям ведения журнала, создайте регистратор с помощью logger =
logging.getLogger(__name__), а затем вызывайте его методы debug(), info(), warning(), error() и critical(). Чтобы определить, когда следует использовать ведение журнала и какие методы регистратора в каких случаях применять, см. таблицу ниже. В ней для каждой из распространённых задач указан наиболее подходящий инструмент.
Задача | Лучший инструмент для выполнения задачи |
|---|---|
Вывести данные в консоль при обычном использовании скрипта или программы командной строки | |
Сообщать о событиях, происходящих при нормальной работе программы (например, для отслеживания состояния или расследования сбоев) | Метод регистратора |
Выдать предупреждение о конкретном событии во время выполнения |
Метод регистратора |
Сообщить об ошибке, связанной с конкретным событием во время выполнения | Выбросить исключение |
Сообщить о подавлении ошибки без выброса исключения (например, в обработчике ошибок долгоживущего серверного процесса) | Метод регистратора |
Методы регистратора названы в соответствии с уровнем или степенью серьёзности событий, для отслеживания которых они используются. Ниже описаны стандартные уровни и их назначение (в порядке возрастания серьёзности):
Уровень | Когда используется |
|---|---|
| Подробная информация, обычно интересная только при диагностике проблем. |
| Подтверждение того, что всё работает как ожидалось. |
| Указание на то, что произошло что-то неожиданное или в ближайшем будущем возможна проблема (например, «мало места на диске»). Программное обеспечение по-прежнему работает как ожидалось. |
| Из-за более серьёзной проблемы программное обеспечение не смогло выполнить какую-либо функцию. |
| Серьёзная ошибка, указывающая на то, что программа, возможно, не сможет продолжить работу. |
По умолчанию установлен уровень WARNING: отслеживаются только события этого уровня и выше, если пакет ведения журнала не настроен иначе.
Отслеживаемые события можно обрабатывать по-разному. Самый простой способ обработки — выводить их в консоль. Другой распространённый способ — записывать их в файл на диске.
Простой пример
Вот очень простой пример:
import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything
Если ввести эти строки в скрипт и запустить его, вы увидите:
WARNING:root:Watch out!
выведенное в консоль. Сообщение INFO не появляется, потому что по умолчанию установлен уровень WARNING. В выведенном сообщении указаны уровень и описание события, переданное при вызове ведения журнала, то есть «Осторожно!». При необходимости формат вывода можно гибко настроить; параметры форматирования также будут рассмотрены далее.
Обратите внимание, что в этом примере мы используем функции непосредственно из модуля logging, например logging.debug, а не создаём регистратор и не вызываем его функции. Эти функции работают с корневым регистратором, но могут быть полезны: если вызов basicConfig() ещё не выполнялся, как в этом примере, они вызовут его за вас. Однако в более крупных программах обычно требуется явно управлять настройкой ведения журнала, поэтому, а также по другим причинам, лучше создавать регистраторы и вызывать их методы.
Запись журнала в файл
Очень часто события журнала записывают в файл, поэтому рассмотрим этот случай. Обязательно попробуйте выполнить следующие действия в заново запущенном интерпретаторе Python, а не продолжайте работу в сеансе, описанном выше:
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')
logger.error('And non-ASCII stuff, too, like Øresund and Malmö')
Изменено в версии 3.9: Добавлен аргумент encoding. В более ранних версиях Python или если этот аргумент не указан используется значение кодировки по умолчанию, заданное для open(). Хотя в приведённом выше примере это не показано, теперь также можно передать аргумент errors, который определяет обработку ошибок кодирования. Допустимые значения и значение по умолчанию см. в документации для open().
Теперь, если открыть файл и посмотреть его содержимое, мы должны увидеть сообщения журнала:
DEBUG:__main__:This message should go to the log file INFO:__main__:So should this WARNING:__main__:And this, too ERROR:__main__:And non-ASCII stuff, too, like Øresund and Malmö
Этот пример также показывает, как задать уровень ведения журнала, который служит порогом для отслеживания. В данном случае, поскольку мы задали порог DEBUG, были выведены все сообщения.
Если вы хотите задать уровень ведения журнала с помощью параметра командной строки, например:
--log=INFO
и значение параметра --log сохранено в переменной loglevel, можно воспользоваться следующим кодом:
getattr(logging, loglevel.upper())
чтобы получить значение, которое будет передано в basicConfig() через аргумент level. Возможно, следует проверить введённое пользователем значение, например, как в следующем примере:
# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, ...)
Вызов basicConfig() должен выполняться до любых вызовов методов регистратора, таких как debug(), info() и т. д. В противном случае событие журнала может быть обработано не так, как нужно.
Если запустить приведённый выше скрипт несколько раз, сообщения последующих запусков будут добавляться в файл example.log. Если нужно, чтобы каждый запуск начинался с чистого листа, без сообщений предыдущих запусков, можно указать аргумент filemode, изменив вызов в приведённом выше примере следующим образом:
logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)
Вывод останется таким же, как и прежде, но файл журнала больше не будет дополняться, поэтому сообщения предыдущих запусков будут потеряны.
Запись переменных данных
Чтобы записывать переменные данные, используйте строку форматирования в сообщении с описанием события и передавайте переменные данные в качестве аргументов. Например:
import logging
logging.warning('%s before you %s', 'Look', 'leap!')
выведет:
WARNING:root:Look before you leap!
Как видите, для подстановки переменных данных в сообщение с описанием события используется старый стиль форматирования строк с помощью %. Это сделано для обратной совместимости: пакет ведения журнала появился до более новых вариантов форматирования, таких как str.format() и string.Template. Эти новые варианты форматирования поддерживаются, но их рассмотрение выходит за рамки этого руководства. Дополнительную информацию см. в разделе Использование определённых стилей форматирования во всём приложении.
Изменение формата отображаемых сообщений
Чтобы изменить формат отображения сообщений, укажите желаемый формат:
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')
В результате будет выведено:
DEBUG:This message should appear on the console INFO:So should this WARNING:And this, too
Обратите внимание, что «root», присутствовавшее в предыдущих примерах, исчезло. Полный список элементов, которые могут использоваться в строках форматирования, приведён в документации по атрибутам LogRecord, но для простого использования достаточно указать levelname (степень серьёзности), message (описание события, включая переменные данные) и, возможно, время возникновения события. Это описано в следующем разделе.
Отображение даты и времени в сообщениях
Чтобы отобразить дату и время события, поместите «%(asctime)s» в строку форматирования:
import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')
В результате должно быть выведено что-то вроде этого:
2010-12-12 11:41:42,612 is when this event was logged.
Формат отображения даты и времени по умолчанию (показанный выше) соответствует ISO8601 или RFC 3339. Если требуется точнее настроить формат даты и времени, передайте аргумент datefmt в basicConfig, как в этом примере:
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
В результате будет выведено что-то вроде этого:
12/12/2010 11:46:36 AM is when this event was logged.
Формат аргумента datefmt такой же, как и в time.strftime().
Дальнейшие шаги
На этом основное руководство заканчивается. Этого должно быть достаточно, чтобы начать использовать ведение журнала. Пакет ведения журнала предлагает гораздо больше возможностей, но, чтобы использовать его в полной мере, нужно уделить немного больше времени чтению следующих разделов. Если вы готовы, возьмите любимый напиток и продолжайте.
Если ваши потребности в ведении журнала невелики, используйте приведённые выше примеры в своих скриптах. Если возникнут проблемы или что-то будет непонятно, задайте вопрос в категории Help на форуме обсуждения Python — вам должны вскоре помочь.
Вы всё ещё здесь? Можете продолжить чтение следующих разделов: в них приведено более продвинутое и подробное руководство, чем в рассмотренном выше. После этого можно перейти к сборнику рецептов по ведению журнала.
Расширенное руководство по ведению журнала
Библиотека ведения журнала использует модульный подход и предлагает несколько категорий компонентов: регистраторы, обработчики, фильтры и форматтеры.
- Регистраторы предоставляют интерфейс, который непосредственно использует код приложения.
- Обработчики отправляют записи журнала (созданные регистраторами) в соответствующее место назначения.
- Фильтры предоставляют более точные средства для определения того, какие записи журнала выводить.
- Форматтеры задают структуру записей журнала в итоговом выводе.
Информация о событиях журнала передается между регистраторами, обработчиками, фильтрами и форматтерами в экземпляре LogRecord.
Ведение журнала осуществляется вызовом методов экземпляров класса Logger (далее — регистраторы). Каждый экземпляр имеет имя, а сами экземпляры концептуально организованы в иерархию пространств имен, разделенных точками. Например, регистратор с именем «scan» является родительским для регистраторов «scan.text», «scan.html» и «scan.pdf». Имена регистраторов могут быть любыми и указывают на область приложения, в которой возникло записанное сообщение.
Хорошее соглашение для именования регистраторов — использовать регистратор на уровне модуля в каждом модуле, где применяется ведение журнала, и задавать ему следующее имя:
logger = logging.getLogger(__name__)
Это означает, что имена регистраторов отражают иерархию пакетов и модулей, поэтому по одному только имени регистратора интуитивно понятно, где записываются события.
Корень иерархии регистраторов называется корневым регистратором. Именно этот регистратор используют функции debug(), info(), warning(), error() и critical(), которые просто вызывают одноименный метод корневого регистратора. Функции и методы имеют одинаковые сигнатуры. В выводе журнала имя корневого регистратора отображается как «root».
Разумеется, сообщения журнала можно записывать в разные места назначения. Пакет поддерживает запись сообщений журнала в файлы, по адресам HTTP GET/POST, отправку по электронной почте через SMTP, в обычные сокеты, очереди или механизмы ведения журнала, специфичные для операционной системы, например syslog или журнал событий Windows NT. За места назначения отвечают классы обработчиков. Если у вас есть особые требования, которым не удовлетворяют встроенные классы обработчиков, можно создать собственный класс назначения для журнала.
По умолчанию для сообщений журнала не задано место назначения. Его (например, консоль или файл) можно указать с помощью basicConfig(), как показано в примерах руководства. При вызове функций debug(), info(), warning(), error() и critical() они проверяют, задано ли место назначения; если оно не задано, то устанавливают в качестве места назначения консоль (sys.stderr) и формат по умолчанию для отображаемого сообщения, после чего передают сообщение корневому регистратору для фактического вывода.
Формат сообщений, заданный по умолчанию функцией basicConfig():
severity:logger name:message
Его можно изменить, передав строку формата функции basicConfig() с помощью именованного аргумента format. Все варианты составления строки формата описаны в разделе Объекты форматтеров.
Поток обработки журнала
Поток передачи информации о событиях журнала между регистраторами и обработчиками показан на следующей диаграмме.
Регистраторы
Объекты Logger выполняют три задачи. Во-первых, они предоставляют прикладному коду несколько методов, позволяющих приложениям записывать сообщения во время выполнения. Во-вторых, объекты регистраторов определяют, какие сообщения журнала обрабатывать, на основе уровня серьезности (средства фильтрации по умолчанию) или объектов фильтров. В-третьих, объекты регистраторов передают соответствующие сообщения журнала всем заинтересованным обработчикам журналов.
Наиболее часто используемые методы объектов регистраторов относятся к двум категориям: настройка и отправка сообщений.
Наиболее распространены следующие методы настройки:
-
Logger.setLevel()задает наименьший уровень серьезности сообщения журнала, которое будет обрабатываться регистратором; debug — самый низкий встроенный уровень серьезности, а critical — самый высокий. Например, если задан уровень INFO, регистратор будет обрабатывать только сообщения INFO, WARNING, ERROR и CRITICAL, игнорируя сообщения DEBUG. -
Logger.addHandler()иLogger.removeHandler()добавляют объекты обработчиков в объект регистратора и удаляют их из него. Обработчики подробнее рассматриваются в разделе Обработчики. -
Logger.addFilter()иLogger.removeFilter()добавляют объекты фильтров в объект регистратора и удаляют их из него. Фильтры подробнее рассматриваются в разделе Объекты фильтров.
Не обязательно всегда вызывать эти методы для каждого созданного регистратора. См. два последних абзаца этого раздела.
После настройки объекта регистратора следующие методы создают сообщения журнала:
-
Logger.debug(),Logger.info(),Logger.warning(),Logger.error()иLogger.critical()создают записи журнала с сообщением и уровнем, соответствующим имени вызванного метода. Сообщение фактически представляет собой строку формата, которая может содержать стандартный синтаксис подстановки строк%s,%d,%fи т. д. Остальные аргументы — это список объектов, соответствующих полям подстановки в сообщении. Что касается**kwargs, методы ведения журнала учитывают только именованный аргументexc_infoи используют его, чтобы определить, следует ли записывать информацию об исключении. -
Logger.exception()создает сообщение журнала, похожее на сообщениеLogger.error(). Разница в том, чтоLogger.exception()также выводит трассировку стека. Вызывайте этот метод только из обработчика исключений. -
Logger.log()принимает уровень журнала в качестве явного аргумента. Для записи сообщений это немного многословнее, чем использование перечисленных выше вспомогательных методов для уровней журнала, однако именно так можно записывать сообщения с пользовательскими уровнями журнала.
getLogger() возвращает ссылку на экземпляр регистратора с указанным именем, если оно задано, или root в противном случае. Имена образуют иерархические структуры, части которых разделяются точками. Несколько вызовов getLogger() с одним и тем же именем возвращают ссылку на один и тот же объект регистратора. Регистраторы, расположенные ниже в иерархии, являются дочерними по отношению к регистраторам, расположенным выше. Например, если регистратор имеет имя foo, то регистраторы с именами foo.bar, foo.bar.baz и foo.bam являются потомками foo.
У регистраторов есть понятие эффективного уровня. Если уровень явно не задан для регистратора, в качестве его эффективного уровня используется уровень родителя. Если у родителя явно не задан уровень, проверяется его родитель и так далее — поиск продолжается по всем предкам, пока не будет найден явно заданный уровень. Для корневого регистратора уровень всегда задан явно (по умолчанию WARNING). При принятии решения об обработке события эффективный уровень регистратора используется для определения того, будет ли событие передано обработчикам регистратора.
Дочерние регистраторы передают сообщения вверх, обработчикам, связанным с их предками. Поэтому нет необходимости определять и настраивать обработчики для всех регистраторов, используемых приложением. Достаточно настроить обработчики для регистратора верхнего уровня и при необходимости создавать дочерние регистраторы. (Однако распространение сообщений можно отключить, задав атрибуту propagate регистратора значение False.)
Обработчики
Объекты Handler отвечают за отправку соответствующих сообщений журнала (на основе уровня серьезности сообщений) в указанное для обработчика место назначения. Объекты Logger могут добавлять к себе ноль или более объектов обработчиков с помощью метода addHandler(). Например, приложению может потребоваться отправлять все сообщения журнала в файл журнала, все сообщения уровня error и выше — в stdout, а все сообщения уровня critical — на адрес электронной почты. Для этого нужны три отдельных обработчика, каждый из которых отвечает за отправку сообщений определенного уровня серьезности в определенное место.
Стандартная библиотека включает довольно много типов обработчиков (см. раздел Полезные обработчики); в примерах руководств в основном используются StreamHandler и FileHandler.
Разработчикам приложений обычно нужно знать лишь несколько методов обработчика. Для разработчиков, использующих встроенные объекты обработчиков (то есть не создающих собственные обработчики), актуальны только следующие методы настройки:
- Метод
setLevel(), как и в объектах регистраторов, задает наименьший уровень серьезности, сообщения которого будут отправляться в соответствующее место назначения. Зачем нужны два методаsetLevel()? Уровень, заданный в регистраторе, определяет, сообщения какого уровня серьезности он будет передавать своим обработчикам. Уровень, заданный в каждом обработчике, определяет, какие сообщения этот обработчик будет отправлять дальше. -
setFormatter()выбирает объект Formatter, который будет использоваться этим обработчиком. -
addFilter()иremoveFilter()соответственно подключают фильтры к обработчикам и отключают их.
Код приложения не должен напрямую создавать и использовать экземпляры Handler. Вместо этого класс Handler служит базовым классом, определяющим интерфейс, который должны предоставлять все обработчики, и задающим поведение по умолчанию, которое могут использовать (или переопределять) дочерние классы.
Форматтеры
Объекты форматтеров настраивают итоговый порядок, структуру и содержимое сообщения журнала. В отличие от базового класса logging.Handler, классы форматтеров можно создавать в коде приложения, хотя при необходимости особого поведения, вероятно, лучше создать подкласс форматтера. Конструктор принимает три необязательных аргумента: строку формата сообщения, строку формата даты и индикатор стиля.
-
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
Если строка формата сообщения не задана, по умолчанию используется исходное сообщение. Если строка формата даты не задана, используется следующий формат даты по умолчанию:
%Y-%m-%d %H:%M:%S
в конце добавляются миллисекунды. Значение style должно быть одним из '%', '{' или '$'. Если ни одно из этих значений не указано, используется '%'.
Если значение style равно '%', в строке формата сообщения используется подстановка строк в стиле %(<dictionary key>)s; возможные ключи описаны в разделе Атрибуты LogRecord. Если стиль — '{', предполагается, что строка формата сообщения совместима с str.format() (с использованием именованных аргументов); если же стиль — '$', строка формата сообщения должна соответствовать требованиям string.Template.substitute().
Изменено в версии 3.2: Добавлен параметр style.
Следующая строка формата сообщения записывает время в удобочитаемом формате, уровень серьезности сообщения и его содержимое именно в таком порядке:
'%(asctime)s - %(levelname)s - %(message)s'
Форматтеры используют настраиваемую пользователем функцию для преобразования времени создания записи в кортеж. По умолчанию используется time.localtime(); чтобы изменить ее для конкретного экземпляра форматтера, задайте атрибуту converter экземпляра функцию с той же сигнатурой, что у time.localtime() или time.gmtime(). Чтобы изменить функцию для всех форматтеров, например если требуется отображать время всех записей журнала по Гринвичу, задайте атрибут converter класса Formatter (для отображения времени по Гринвичу укажите time.gmtime).
Настройка журналирования
Программисты могут настроить журналирование тремя способами:
- Явно создать регистраторы, обработчики и форматировщики с помощью кода Python, вызывающего перечисленные выше методы настройки.
- Создать файл конфигурации журналирования и прочитать его с помощью функции
fileConfig(). - Создать словарь с информацией о конфигурации и передать его функции
dictConfig().
Справочную документацию по двум последним вариантам см. в разделе Функции настройки. В следующем примере с помощью кода Python настраиваются очень простой регистратор, обработчик консоли и простой форматировщик:
import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
При запуске этого модуля из командной строки выводятся следующие данные:
$ python simple_logging_module.py 2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message 2005-03-19 15:10:26,620 - simple_example - INFO - info message 2005-03-19 15:10:26,695 - simple_example - WARNING - warn message 2005-03-19 15:10:26,697 - simple_example - ERROR - error message 2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message
Следующий модуль Python создает регистратор, обработчик и форматировщик, почти идентичные приведенным выше в примере; единственное различие заключается в именах объектов:
import logging
import logging.config
logging.config.fileConfig('logging.conf')
# create logger
logger = logging.getLogger('simpleExample')
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
Вот файл logging.conf:
[loggers] keys=root,simpleExample [handlers] keys=consoleHandler [formatters] keys=simpleFormatter [logger_root] level=DEBUG handlers=consoleHandler [logger_simpleExample] level=DEBUG handlers=consoleHandler qualname=simpleExample propagate=0 [handler_consoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,) [formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
Вывод почти не отличается от вывода примера без использования файла конфигурации:
$ python simple_logging_config.py 2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message 2005-03-19 15:38:55,979 - simpleExample - INFO - info message 2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message 2005-03-19 15:38:56,055 - simpleExample - ERROR - error message 2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message
Как видно, подход с файлом конфигурации имеет несколько преимуществ перед подходом с кодом Python: прежде всего, это разделение конфигурации и кода, а также возможность легко изменять параметры журналирования без написания кода.
Предупреждение
Функция fileConfig() принимает параметр по умолчанию, disable_existing_loggers, значение которого по умолчанию — True из соображений обратной совместимости. Это может соответствовать или не соответствовать вашим потребностям, поскольку в результате все регистраторы, кроме корневого, существовавшие до вызова fileConfig(), будут отключены, если они (или их предок) явно не указаны в конфигурации. Дополнительную информацию см. в справочной документации. При необходимости укажите для этого параметра значение False.
В словаре, передаваемом функции dictConfig(), также можно указать логическое значение с ключом disable_existing_loggers. Если оно явно не указано в словаре, по умолчанию оно также интерпретируется как True. Это приводит к описанному выше отключению регистраторов, что может не соответствовать вашим потребностям. В этом случае явно укажите ключ со значением False.
Обратите внимание, что имена классов, указанные в файлах конфигурации, должны быть либо относительными по отношению к модулю logging, либо абсолютными значениями, которые можно разрешить с помощью стандартных механизмов импорта. Таким образом, можно использовать либо WatchedFileHandler (относительно модуля logging), либо mypackage.mymodule.MyHandler (для класса, определенного в пакете mypackage и модуле mymodule, где mypackage доступен в пути импорта Python).
В Python 3.2 появился новый способ настройки журналирования с использованием словарей для хранения информации о конфигурации. Он предоставляет надмножество возможностей подхода с файлом конфигурации, описанного выше, и является рекомендуемым способом настройки для новых приложений и развертываний. Поскольку информация о конфигурации хранится в словаре Python, который можно заполнить разными способами, у вас появляется больше вариантов настройки. Например, для заполнения словаря конфигурации можно использовать файл конфигурации в формате JSON или, если доступна обработка YAML, файл в формате YAML. Также, конечно, можно создать словарь в коде Python, получить его в сериализованном виде через сокет или использовать любой другой подход, подходящий для вашего приложения.
Вот пример той же конфигурации, что и выше, в формате YAML для нового подхода на основе словаря:
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
loggers:
simpleExample:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
Дополнительную информацию о журналировании с помощью словаря см. в разделе Функции настройки.
Что происходит, если конфигурация не задана
Если конфигурация журналирования не задана, может возникнуть ситуация, когда необходимо вывести событие журнала, но обработчики для его вывода не найдены.
Событие выводится с помощью «обработчика последней надежды», хранящегося в lastResort. Этот внутренний обработчик не связан ни с одним регистратором и работает подобно StreamHandler, записывая описание события в текущее значение sys.stderr (и поэтому учитывая все действующие перенаправления). Форматирование сообщения не выполняется — выводится только его исходное описание. Уровень обработчика установлен в WARNING, поэтому выводятся все события этого и более высоких уровней серьезности.
Изменено в версии 3.2: Для версий Python до 3.2 поведение было следующим:
- Если
raiseExceptionsимеет значениеFalse(рабочий режим), событие незаметно отбрасывается. - Если
raiseExceptionsимеет значениеTrue(режим разработки), один раз выводится сообщение «Не удалось найти обработчики для регистратора X.Y.Z».
Чтобы получить поведение, действовавшее до версии 3.2, можно установить для lastResort значение None.
Настройка журналирования для библиотеки
При разработке библиотеки, использующей журналирование, следует документировать особенности ее использования — например, имена используемых регистраторов. Также необходимо продумать настройку журналирования. Если использующее библиотеку приложение не использует журналирование, а код библиотеки вызывает функции журналирования, то, как описано в предыдущем разделе, события уровня WARNING и выше будут выводиться в sys.stderr. Это считается наиболее подходящим поведением по умолчанию.
Если по какой-либо причине вы не хотите, чтобы эти сообщения выводились при отсутствии конфигурации журналирования, можно присоединить к регистратору верхнего уровня вашей библиотеки пустой обработчик. Это предотвратит вывод сообщения, поскольку для событий библиотеки всегда будет найден обработчик, который, однако, ничего не выводит. Если пользователь библиотеки настроит журналирование для приложения, эта конфигурация, предположительно, добавит обработчики, и при соответствующей настройке уровней вызовы журналирования в коде библиотеки будут, как обычно, направлять вывод этим обработчикам.
В пакете logging предусмотрен пустой обработчик: NullHandler (начиная с Python 3.1). Экземпляр этого обработчика можно добавить к регистратору верхнего уровня пространства имен журнала, используемого библиотекой (если вы хотите предотвратить вывод зарегистрированных библиотекой событий в sys.stderr при отсутствии конфигурации журналирования). Если все операции журналирования в библиотеке foo выполняются с помощью регистраторов, имена которых соответствуют шаблонам «foo.x», «foo.x.y» и т. д., то следующий код:
import logging
logging.getLogger('foo').addHandler(logging.NullHandler())
должен дать желаемый результат. Если организация создает несколько библиотек, вместо простого «foo» можно указать имя регистратора «orgname.foo».
Примечание
Настоятельно рекомендуется не записывать сообщения в корневой регистратор из вашей библиотеки. Вместо этого используйте регистратор с уникальным и легко распознаваемым именем, например __name__ для пакета или модуля верхнего уровня вашей библиотеки. Запись сообщений в корневой регистратор затруднит или сделает невозможной настройку разработчиком приложения уровня подробности журналирования и обработчиков вашей библиотеки в соответствии со своими потребностями.
Примечание
Настоятельно рекомендуется не добавлять к регистраторам вашей библиотеки обработчики, кроме NullHandler. Настройка обработчиков — прерогатива разработчика приложения, использующего вашу библиотеку. Разработчик приложения знает целевую аудиторию и то, какие обработчики лучше всего подходят для приложения. Добавляя обработчики «под капотом», вы можете помешать ему проводить модульное тестирование и формировать журналы, отвечающие его требованиям.
Уровни журналирования
Числовые значения уровней журналирования приведены в следующей таблице. Они представляют интерес главным образом в том случае, если вы хотите определить собственные уровни и назначить им значения относительно предопределенных уровней. Если определить уровень с тем же числовым значением, он перезапишет предопределенное значение, а предопределенное имя будет утрачено.
Уровень | Числовое значение |
|---|---|
| 50 |
| 40 |
| 30 |
| 20 |
| 10 |
| 0 |
Уровни также можно назначать регистраторам: это может сделать разработчик или загрузка сохраненной конфигурации журналирования. При вызове метода журналирования у регистратора он сравнивает собственный уровень с уровнем, связанным с вызовом метода. Если уровень регистратора выше уровня вызова метода, сообщение журнала фактически не создается. Это основной механизм управления подробностью выводимых журналов.
Сообщения журнала кодируются как экземпляры класса LogRecord. Когда регистратор принимает решение записать событие, из сообщения журнала создается экземпляр LogRecord.
Для отправки сообщений журнала используется механизм диспетчеризации с помощью обработчиков, являющихся экземплярами подклассов класса Handler. Обработчики отвечают за доставку записанного сообщения (в виде LogRecord) в определенное место или несколько мест, полезных целевой аудитории этого сообщения (например, конечным пользователям, сотрудникам службы поддержки, системным администраторам или разработчикам). Обработчикам передаются экземпляры LogRecord, предназначенные для определенных адресатов. С каждым регистратором может быть связано ноль, один или несколько обработчиков (с помощью метода addHandler() класса Logger). Помимо обработчиков, связанных непосредственно с регистратором, для отправки сообщения вызываются все обработчики, связанные со всеми предками регистратора (если только для регистратора не задано ложное значение флага propagate; в этом случае передача обработчикам предков прекращается).
Как и у регистраторов, у обработчиков могут быть уровни. Уровень обработчика действует как фильтр так же, как уровень регистратора. Если обработчик принимает решение отправить событие, для передачи сообщения адресату используется метод emit(). Большинству определяемых пользователем подклассов Handler потребуется переопределить этот emit().
Пользовательские уровни
Определять собственные уровни можно, но в этом не должно быть необходимости, поскольку существующие уровни выбраны на основе практического опыта. Однако если вы уверены, что вам нужны пользовательские уровни, определять их следует очень осторожно. Возможно, определять пользовательские уровни — очень плохая идея, если вы разрабатываете библиотеку. Дело в том, что если несколько авторов библиотек определят собственные пользовательские уровни, то вывод журналирования нескольких таких библиотек, используемых вместе, может оказаться сложным для управления и/или интерпретации, поскольку одно и то же числовое значение может иметь разный смысл в разных библиотеках.
Полезные обработчики
Помимо базового класса Handler, предусмотрено множество полезных подклассов:
-
Экземпляры
StreamHandlerотправляют сообщения в потоки (объекты, подобные файлам). -
Экземпляры
FileHandlerотправляют сообщения в файлы на диске. -
BaseRotatingHandler— базовый класс обработчиков, которые переключают файлы журнала при достижении определенного условия. Он не предназначен для создания экземпляров напрямую. Вместо этого используйтеRotatingFileHandlerилиTimedRotatingFileHandler. -
Экземпляры
RotatingFileHandlerотправляют сообщения в файлы на диске, поддерживая ограничение максимального размера файла журнала и его ротацию. -
Экземпляры
TimedRotatingFileHandlerотправляют сообщения в файлы на диске, выполняя ротацию файла журнала через заданные интервалы времени. -
Экземпляры
SocketHandlerотправляют сообщения через сокеты TCP/IP. Начиная с версии 3.4 также поддерживаются доменные сокеты Unix. -
Экземпляры
DatagramHandlerотправляют сообщения через сокеты UDP. Начиная с версии 3.4 также поддерживаются доменные сокеты Unix. -
Экземпляры
SMTPHandlerотправляют сообщения на указанный адрес электронной почты. -
Экземпляры
SysLogHandlerотправляют сообщения демону syslog Unix, который может работать на удаленной машине. -
Экземпляры
NTEventLogHandlerотправляют сообщения в журнал событий Windows NT/2000/XP. -
Экземпляры
MemoryHandlerотправляют сообщения в буфер в памяти, который сбрасывается при выполнении определенных условий. -
Экземпляры
HTTPHandlerотправляют сообщения HTTP-серверу с использованием семантикиGETилиPOST. -
Экземпляры
WatchedFileHandlerотслеживают файл, в который записывают журнал. При изменении файла он закрывается и снова открывается по имени файла. Этот обработчик полезен только в Unix-подобных системах; Windows не поддерживает используемый базовый механизм. -
Экземпляры
QueueHandlerотправляют сообщения в очередь, например в очереди из модулейqueueилиmultiprocessing. -
Экземпляры
NullHandlerне выполняют никаких действий с сообщениями об ошибках. Их используют разработчики библиотек, которым нужно журналирование, но которые хотят избежать появления сообщения «Не удалось найти обработчики для регистратора XXX», отображаемого, если пользователь библиотеки не настроил журналирование. Дополнительную информацию см. в разделе Настройка журналирования для библиотеки.
Добавлено в версии 3.1: Класс NullHandler.
Добавлено в версии 3.2: Класс QueueHandler.
Классы NullHandler, StreamHandler и FileHandler определены в основном пакете logging. Остальные обработчики определены во вложенном модуле logging.handlers. (Есть также другой вложенный модуль, logging.config, предоставляющий функциональность настройки.)
Записанные сообщения форматируются для отображения с помощью экземпляров класса Formatter. При их инициализации задаются строка форматирования, подходящая для использования с оператором %, и словарь.
Для форматирования нескольких сообщений в пакете можно использовать экземпляры BufferingFormatter. Помимо строки форматирования, применяемой к каждому сообщению пакета, можно задать строки форматирования заголовка и завершающей части.
Если фильтрации по уровню регистратора и/или обработчика недостаточно, экземпляры Filter можно добавить как к экземплярам Logger, так и к экземплярам Handler (с помощью их метода addFilter()). Прежде чем продолжить обработку сообщения, и регистраторы, и обработчики запрашивают разрешение у всех своих фильтров. Если любой из фильтров возвращает ложное значение, дальнейшая обработка сообщения прекращается.
Базовая функциональность Filter позволяет фильтровать сообщения по имени регистратора. Если используется эта возможность, фильтр пропускает сообщения, отправленные указанному регистратору и его потомкам, а все остальные отбрасывает.
Исключения, возникающие при журналировании
Пакет logging устроен так, чтобы в рабочем режиме подавлять исключения, возникающие во время журналирования. Это необходимо, чтобы ошибки при обработке событий журнала — например, неправильная конфигурация журналирования, сетевые и другие подобные ошибки — не приводили к преждевременному завершению приложения, использующего журналирование.
Исключения SystemExit и KeyboardInterrupt никогда не подавляются. Другие исключения, возникающие в методе emit() подкласса Handler, передаются его методу handleError().
Реализация handleError() по умолчанию в Handler проверяет, установлена ли переменная уровня модуля raiseExceptions. Если установлена, трассировка стека выводится в sys.stderr. Если нет, исключение подавляется.
Примечание
Значение raiseExceptions по умолчанию — True. Во время разработки обычно важно получать уведомления о возникающих исключениях. Для рабочей среды рекомендуется установить для raiseExceptions значение False.
Использование произвольных объектов в качестве сообщений
В предыдущих разделах и примерах предполагалось, что сообщение, передаваемое при регистрации события, является строкой. Однако это не единственная возможность. В качестве сообщения можно передать произвольный объект: когда системе журналирования потребуется преобразовать его в строковое представление, будет вызван его метод __str__(). На самом деле при желании можно вообще не вычислять строковое представление — например, SocketHandler отправляет событие, сериализуя его с помощью pickle и передавая по сети.
Оптимизация
Форматирование аргументов сообщения откладывается до тех пор, пока без него нельзя обойтись. Однако вычисление аргументов, передаваемых методу журналирования, также может быть затратным, и, возможно, вы захотите избежать его, если регистратор просто отбросит событие. Чтобы решить, что делать, можно вызвать метод isEnabledFor(), который принимает аргумент уровня и возвращает истинное значение, если регистратор создаст событие для вызова этого уровня. Можно написать такой код:
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(),
expensive_func2())
чтобы вызовы expensive_func1 и expensive_func2 не выполнялись, если пороговый уровень регистратора установлен выше DEBUG.
Примечание
В некоторых случаях метод isEnabledFor() сам по себе может быть более затратным, чем хотелось бы (например, для глубоко вложенных регистраторов, когда явный уровень задан только высоко в иерархии регистраторов). В таких случаях (или если вы хотите избежать вызова метода в тесных циклах) можно сохранить результат вызова isEnabledFor() в локальной переменной или переменной экземпляра и использовать его вместо повторных вызовов метода. Такое сохраненное значение нужно пересчитывать только в том случае, если конфигурация журналирования динамически меняется во время работы приложения (что случается не так уж часто).
Для отдельных приложений, которым требуется более точный контроль над собираемой информацией журнала, можно применить и другие оптимизации. Вот список действий, позволяющих избежать ненужной обработки при журналировании:
Какие данные не нужно собирать | Как избежать их сбора |
|---|---|
Информация о месте вызова. | Установите для |
Информация о потоках. | Установите для |
Идентификатор текущего процесса ( | Установите для |
Имя текущего процесса при использовании | Установите для |
Имя текущей задачи | Установите для |
Также обратите внимание, что основной модуль logging содержит только базовые обработчики. Если не импортировать logging.handlers и logging.config, они не будут занимать память.
Другие ресурсы
См. также
-
Modulelogging -
Справочник API модуля logging.
-
Modulelogging.config -
API настройки модуля logging.
-
Modulelogging.handlers -
Полезные обработчики, входящие в состав модуля logging.
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/howto/logging.html