РУКОВОДСТВО ПО ВЕДЕНИЮ ЖУРНАЛА
- Автор:
-
Vinay Sajip <vinay_sajip at red-dove dot com>
На этой странице приведены сведения в формате учебного руководства. Ссылки на справочную документацию и сборник рецептов по ведению журнала см. в разделе Другие ресурсы.
Основы ведения журнала
Ведение журнала — это способ отслеживать события, происходящие во время работы программного обеспечения. Разработчик добавляет в код вызовы для ведения журнала, чтобы отметить наступление определённых событий. Событие описывается информативным сообщением, которое при необходимости может содержать переменные данные (то есть данные, потенциально отличающиеся при каждом наступлении события). Разработчик также присваивает событию важность; важность также называют уровнем или степенью серьёзности.
Когда использовать ведение журнала
Получить доступ к функциям ведения журнала можно, создав регистратор с помощью logger =
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()выбирает объект-форматтер, который будет использовать этот обработчик. -
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(). Чтобы изменить поведение для всех форматтеров (например, если требуется отображать время всех записей в GMT), задайте атрибут converter класса Formatter (для отображения времени по GMT укажите 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())
должен обеспечить желаемый эффект. Если организация выпускает несколько библиотек, в качестве имени логгера можно указать «orgname.foo», а не просто «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. Помимо строки формата, применяемой к каждому сообщению пакета, можно задать строки формата для заголовка и завершающей части.
Если фильтрации по уровню логгера и/или обработчика недостаточно, к экземплярам Logger и Handler можно добавить экземпляры Filter (с помощью их метода 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())
Таким образом, если пороговый уровень логгера выше DEBUG, вызовы expensive_func1 и expensive_func2 не будут выполняться.
Примечание
В некоторых случаях вызов isEnabledFor() сам по себе может оказаться слишком затратным (например, для глубоко вложенных логгеров, когда явный уровень задан только на высоком уровне иерархии логгеров). В таких случаях (или если нужно избежать вызова метода в интенсивных циклах) можно сохранить результат вызова isEnabledFor() в локальной переменной или переменной экземпляра и использовать его вместо повторного вызова метода. Такое сохранённое значение потребуется пересчитать только в том случае, если конфигурация журналирования динамически изменится во время работы приложения (что случается нечасто).
Для некоторых приложений, которым требуется более точный контроль над собираемой информацией журнала, доступны и другие оптимизации. Вот список действий, позволяющих избежать ненужной обработки при журналировании:
Какие данные не нужно собирать | Как избежать их сбора |
|---|---|
Информация о месте вызова. | Задайте для |
Информация о потоках. | Задайте для |
Идентификатор текущего процесса ( | Задайте для |
Имя текущего процесса при использовании | Задайте для |
Имя текущей задачи | Задайте для |
Кроме того, обратите внимание: основной модуль logging включает только базовые обработчики. Если не импортировать logging.handlers и logging.config, они не будут занимать память.
Другие ресурсы
См. также
-
Modulelogging -
Справочник API модуля logging.
-
Modulelogging.config -
API настройки модуля logging.
-
Modulelogging.handlers -
Полезные обработчики, входящие в состав модуля logging.
© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/logging.html