Руководство по ведению журнала¶
- Автор:
Винай Саджип <винай_саджип в интернет-магазине red-dove>
Базовое руководство по ведению журнала¶
Ведение журнала - это средство отслеживания событий, которые происходят при запуске некоторого программного обеспечения. Разработчик программного обеспечения добавляет вызовы ведения журнала в свой код, чтобы указать, что произошли определенные события. Событие описывается описательным сообщением, которое при необходимости может содержать переменные данные (т.е. данные, которые потенциально различаются при каждом возникновении события). События также имеют важность, которую разработчик приписывает событию; важность также можно назвать «уровнем» или «серьезностью».
Когда использовать ведение журнала¶
Вы можете получить доступ к функциям ведения журнала, создав программу ведения журнала с помощью 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
Обратите внимание, что «корень», который появлялся в предыдущих примерах, исчез. Для получения полного набора параметров, которые могут отображаться в строках формата, вы можете обратиться к документации для Атрибуты записи журнала, но для простого использования вам просто нужны имяуровня (серьезность), сообщение (описание события, включая переменные данные) и, возможно, для отображения, когда событие произошло. Это описано в следующем разделе.
Отображение даты/времени в сообщениях¶
Чтобы отобразить дату и время события, вы должны поместить «%(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()
.
следующие шаги¶
На этом основное руководство завершается. Этого должно быть достаточно, чтобы вы освоились с ведением журнала. Пакет ведения журнала предлагает гораздо больше, но чтобы извлечь из него максимум пользы, вам нужно потратить немного больше времени на чтение следующих разделов. Если вы готовы к этому, возьмите свой любимый напиток и продолжайте в том же духе.
Если ваши потребности в ведении журнала просты, то используйте приведенные выше примеры, чтобы включить ведение журнала в свои собственные скрипты, а если у вас возникнут проблемы или вы чего-то не поймете, пожалуйста, задайте вопрос в группе comp.lang.python Usenet (доступна по адресу https://groups.google.com/g/comp.lang.python), и вы скоро получите помощь.
Все еще здесь? Вы можете продолжить чтение следующих нескольких разделов, которые содержат немного более продвинутый / углубленный учебник, чем базовый, приведенный выше. После этого вы можете ознакомиться с Кулинарная книга для ведения журнала.
Расширенное руководство по ведению журнала¶
Библиотека ведения журнала использует модульный подход и предлагает несколько категорий компонентов: регистраторы, обработчики, фильтры и средства форматирования.
Регистраторы предоставляют доступ к интерфейсу, который непосредственно использует код приложения.
Обработчики отправляют записи журнала (созданные регистраторами) в соответствующее место назначения.
Фильтры обеспечивают более детальное определение того, какие записи журнала следует выводить.
Форматировщики определяют расположение записей журнала в конечном результате.
Информация о событии журнала передается между регистраторами, обработчиками, фильтрами и форматировщиками в экземпляре LogRecord
.
Ведение журнала выполняется путем вызова методов для экземпляров класса Logger
(далее называемого loggers). У каждого экземпляра есть имя, и они концептуально упорядочены в иерархии пространства имен с использованием точек (period) в качестве разделителей. Например, логгер с именем «scan» является родительским для логгеров «scan.text», «scan.html» и «scan.pdf». Имена логгеров могут быть любыми по вашему желанию и указывать область приложения, в которой создается зарегистрированное сообщение.
Хорошим решением при присвоении имен регистраторам является использование регистратора на уровне модуля в каждом модуле, который использует ведение журнала, названного следующим образом:
logger = logging.getLogger(__name__)
Это означает, что имена регистраторов отслеживают иерархию пакетов/модулей, и интуитивно очевидно, где регистрируются события, просто исходя из имени регистратора.
Корень иерархии регистраторов называется корневым регистратором. Это логгер, используемый функциями debug()
, info()
, warning()
, error()
и critical()
, которые просто вызывают одноименный метод корневого логгера. Функции и методы имеют одинаковые сигнатуры. Имя корневого регистратора выводится как «root» в протоколируемых выходных данных.
Конечно, можно отправлять сообщения в разные пункты назначения. В пакет включена поддержка записи сообщений журнала в файлы, места получения/публикации по протоколу HTTP, электронную почту через SMTP, универсальные сокеты, очереди или механизмы ведения журнала, специфичные для операционной системы, такие как системный журнал или журнал событий Windows NT. Пункты назначения обслуживаются классами handler. Вы можете создать свой собственный класс назначения журнала, если у вас есть особые требования, которым не удовлетворяет ни один из встроенных классов обработчиков.
По умолчанию для сообщений журнала не задан адресат. Вы можете указать адресат (например, консоль или файл), используя basicConfig()
, как в примерах из руководства. Если вы вызовете функции debug()
, info()
, warning()
, error()
и critical()
, они проверят, не задан ли пункт назначения; и если он есть, то если этот параметр не задан, они установят назначение консоли (sys.stderr
) и формат по умолчанию для отображаемого сообщения, прежде чем делегировать пользователю root logger выполнение фактического вывода сообщения.
Формат сообщений по умолчанию, установленный с помощью basicConfig()
, следующий:
severity:logger name:message
Вы можете изменить это, передав строку формата в basicConfig()
с аргументом ключевого слова format. Все параметры, касающиеся того, как создается строка формата, см. в разделе Объекты средства форматирования.
Поток ведения журнала¶
Поток информации о событиях журнала в регистраторах и обработчиках проиллюстрирован на следующей диаграмме.

Лесорубы¶
Logger
объекты выполняют три задачи. Во-первых, они предоставляют несколько методов коду приложения, чтобы приложения могли регистрировать сообщения во время выполнения. Во-вторых, объекты-регистраторы определяют, с какими сообщениями журнала следует действовать, исходя из серьезности (средство фильтрации по умолчанию) или объектов-фильтров. В-третьих, объекты регистратора передают соответствующие сообщения журнала всем заинтересованным обработчикам журналов.
Наиболее широко используемые методы работы с объектами регистратора делятся на две категории: настройка и отправка сообщений.
Это наиболее распространенные методы настройки:
Logger.setLevel()
указывает лог-сообщение с наименьшей степенью серьезности, которое будет обрабатываться средством ведения журнала, где debug - это самый низкий встроенный уровень серьезности, а critical - самый высокий встроенный уровень серьезности. Например, если уровень серьезности равен INFO, то регистратор будет обрабатывать только информационные сообщения, ПРЕДУПРЕЖДЕНИЯ, сообщения об ошибках и КРИТИЧЕСКИЕ сообщения и игнорировать сообщения ОТЛАДКИ.Logger.addHandler()
иLogger.removeHandler()
добавляют и удаляют объекты-обработчики из объекта logger. Более подробно обработчики описаны в Обработчики.Logger.addFilter()
иLogger.removeFilter()
добавляют и удаляют объекты фильтра из объекта регистратора. Более подробно фильтры описаны в Фильтровать объекты.
Вам не нужно всегда вызывать эти методы в каждом создаваемом вами журнале регистрации. Смотрите последние два абзаца в этом разделе.
При настроенном объекте logger сообщения журнала создаются следующими методами:
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()
с одним и тем же именем вернут ссылку на один и тот же объект logger. Логгеры, расположенные ниже в иерархическом списке, являются дочерними по отношению к логгерам, расположенным выше в списке. Например, если задан логгер с именем foo
, все логгеры с именами foo.bar
, foo.bar.baz
, и foo.bam
являются потомками foo
.
У регистраторов есть понятие «эффективный уровень». Если уровень явно не задан в регистраторе, в качестве эффективного уровня используется уровень его родительского элемента. Если у родительского элемента не задан явный уровень, проверяется его родительский элемент и так далее - выполняется поиск по всем предкам, пока не будет найден явно заданный уровень. Корневой регистратор всегда имеет явно заданный уровень (WARNING
по умолчанию). При принятии решения о том, обрабатывать ли событие, используется эффективный уровень средства ведения журнала, чтобы определить, передается ли событие обработчикам средства ведения журнала.
Дочерние регистраторы передают сообщения в обработчики, связанные с их родительскими регистраторами. Из-за этого нет необходимости определять и настраивать обработчики для всех регистраторов, используемых приложением. Достаточно настроить обработчики для регистратора верхнего уровня и при необходимости создать дочерние регистраторы. (Однако вы можете отключить распространение, установив для атрибута propagate регистратора значение False
.)
Обработчики¶
Handler
объекты отвечают за отправку соответствующих сообщений журнала (в зависимости от их серьезности) в указанный адрес обработчика. Logger
объекты могут добавлять к себе ноль или более объектов обработчика с помощью метода addHandler()
. В качестве примера сценария приложение может захотеть отправить все сообщения журнала в файл журнала, все сообщения журнала об ошибках или выше - в стандартный вывод, а все критические сообщения - на адрес электронной почты. Для этого сценария требуются три отдельных обработчика, каждый из которых отвечает за отправку сообщений определенной степени серьезности в определенное место.
Стандартная библиотека включает в себя довольно много типов обработчиков (см. Полезные обработчики); в примерах руководства используются в основном StreamHandler
и FileHandler
.
В обработчике очень мало методов, которые могли бы заинтересовать разработчиков приложений. Единственные методы обработчика, которые кажутся подходящими для разработчиков приложений, использующих встроенные объекты обработчика (то есть не создающих пользовательские обработчики), - это следующие методы настройки:
Метод
setLevel()
, как и в объектах logger, определяет наименьшую степень серьезности, которая будет отправлена соответствующему получателю. Почему существует два методаsetLevel()
? Уровень, установленный в регистраторе, определяет, какую степень серьезности сообщений он будет передавать своим обработчикам. Уровень, установленный в каждом обработчике, определяет, какие сообщения этот обработчик будет отправлять.setFormatter()
выбирает объект форматирования, который будет использоваться этим обработчиком.addFilter()
иremoveFilter()
соответственно настраивают и отменяют настройку объектов фильтра в обработчиках.
Код приложения не должен напрямую создавать и использовать экземпляры Handler
. Вместо этого класс Handler
является базовым классом, который определяет интерфейс, который должен быть у всех обработчиков, и устанавливает некоторое поведение по умолчанию, которое могут использовать (или переопределять) дочерние классы.
Форматировщики¶
Объекты Formatter настраивают окончательный порядок, структуру и содержимое сообщения журнала. В отличие от базового класса logging.Handler
, код приложения может создавать экземпляры классов formatter, хотя вы, вероятно, могли бы создать подкласс formatter, если вашему приложению требуется особое поведение. Конструктор принимает три необязательных аргумента - строку формата сообщения, строку формата даты и индикатор стиля.
- logging.Formatter.__init__(fmt=None, datefmt=None, style='%')¶
Если строка формата сообщения отсутствует, по умолчанию используется необработанное сообщение. Если строка формата даты отсутствует, по умолчанию используется формат даты:
%Y-%m-%d %H:%M:%S
с отсчетом миллисекунд в конце. style
является одним из '%'
, '{'
, или '$'
. Если один из них не указан, то будет использоваться '%'
.
Если style
равно '%'
, в строке формата сообщения используется подстановка в стиле %(<dictionary key>)s
; возможные ключи описаны в Атрибуты записи журнала. Если стиль '{'
, то предполагается, что строка формата сообщения совместима с 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
.
Обратите внимание, что имена классов, на которые ссылаются в конфигурационных файлах, должны быть либо относительными к модулю ведения журнала, либо абсолютными значениями, которые могут быть определены с помощью обычных механизмов импорта. Таким образом, вы могли бы использовать либо WatchedFileHandler
(относительно модуля ведения журнала), либо 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
(режим разработки), то один раз выводится сообщение «Не удалось найти обработчики для logger X.Y.Z».
Чтобы получить поведение, аналогичное поведению до версии 3.2, для параметра lastResort
можно задать значение None
.
Настройка ведения журнала для библиотеки¶
При разработке библиотеки, использующей ведение журнала, вам следует позаботиться о документировании того, как библиотека использует ведение журнала - например, имена используемых регистраторов. Также необходимо уделить некоторое внимание конфигурации ведения журнала. Если использующее приложение не использует протоколирование, а библиотечный код выполняет вызовы протоколирования, то (как описано в предыдущем разделе) события с серьезностью WARNING
и выше будут отображаться как sys.stderr
. Это считается наилучшим поведением по умолчанию.
Если по какой-либо причине вы не хотите, чтобы эти сообщения печатались при отсутствии какой-либо конфигурации ведения журнала, вы можете подключить обработчик бездействия к регистратору верхнего уровня для вашей библиотеки. Это позволяет избежать вывода сообщения на печать, поскольку для событий библиотеки всегда будет найден обработчик: он просто не выдает никаких выходных данных. Если пользователь библиотеки настраивает ведение журнала для использования приложением, предположительно, эта конфигурация добавит некоторые обработчики, и если уровни настроены соответствующим образом, то вызовы ведения журнала, выполненные в библиотечном коде, будут отправлять выходные данные этим обработчикам, как обычно.
В пакет ведения журнала включен обработчик бездействия: NullHandler
(начиная с Python 3.1). Экземпляр этого обработчика может быть добавлен в регистратор верхнего уровня пространства имен ведения журнала, используемого библиотекой (если вы хотите предотвратить вывод зарегистрированных событий вашей библиотеки в sys.stderr
при отсутствии конфигурации ведения журнала). Если все протоколирование библиотекой foo выполняется с использованием регистраторов с именами, совпадающими с „foo.x“, „foo.x.y“ и т.д., то код:
import logging
logging.getLogger('foo').addHandler(logging.NullHandler())
должно дать желаемый эффект. Если организация выпускает несколько библиотек, то указанным именем регистратора может быть «orgname».foo, а не просто «foo».
Примечание
Настоятельно рекомендуется не подключаться к корневому журналу регистрации в вашей библиотеке. Вместо этого используйте программу регистрации с уникальным и легко идентифицируемым именем, например __name__
для пакета или модуля верхнего уровня вашей библиотеки. Регистрация в корневом регистраторе затруднит или сделает невозможным для разработчика приложения настройку подробного ведения журнала или обработчиков вашей библиотеки по своему усмотрению.
Примечание
Настоятельно рекомендуется не добавлять в регистраторы вашей библиотеки никаких обработчиков, кроме * :class:`~logging.NullHandler`. Это связано с тем, что настройка обработчиков является прерогативой разработчика приложения, который использует вашу библиотеку. Разработчик приложения знает свою целевую аудиторию и то, какие обработчики наиболее подходят для его приложения: если вы добавите обработчики «под капотом», вы вполне можете помешать им выполнять модульные тесты и предоставлять журналы, соответствующие их требованиям.
Уровни ведения журнала¶
Числовые значения уровней ведения журнала приведены в следующей таблице. Они в первую очередь представляют интерес, если вы хотите определить свои собственные уровни и хотите, чтобы они имели конкретные значения относительно предопределенных уровней. Если вы определяете уровень с тем же числовым значением, он перезаписывает предопределенное значение; предопределенное имя теряется.
Уровень |
Числовое значение |
---|---|
|
50 |
|
40 |
|
30 |
|
20 |
|
10 |
|
0 |
Уровни также могут быть связаны с регистраторами, которые устанавливаются либо разработчиком, либо путем загрузки сохраненной конфигурации ведения журнала. Когда в программе ведения журнала вызывается метод ведения журнала, программа ведения журнала сравнивает свой собственный уровень с уровнем, связанным с вызовом метода. Если уровень логгера выше, чем у вызова метода, сообщение о регистрации фактически не генерируется. Это базовый механизм, управляющий подробностью вывода журнала.
Протоколирующие сообщения кодируются как экземпляры класса LogRecord
. Когда регистратор решает действительно зарегистрировать событие, на основе протоколирующего сообщения создается экземпляр LogRecord
.
Сообщения журнала обрабатываются механизмом диспетчеризации с помощью handlers, которые являются экземплярами подклассов класса Handler
. Обработчики отвечают за то, чтобы зарегистрированное сообщение (в форме LogRecord
) попадало в определенное местоположение (или набор местоположений), которое полезно для целевой аудитории этого сообщения (например, конечных пользователей, сотрудников службы поддержки, системных администраторов, разработчиков). Обработчикам передаются LogRecord
экземпляра, предназначенные для определенных адресатов. С каждым регистратором может быть связан ноль, один или несколько обработчиков (с помощью addHandler()
метода Logger
). В дополнение к любым обработчикам, непосредственно связанным с регистратором, для отправки сообщения вызываются все обработчики, связанные со всеми предками регистратора (если только для флага распространять для регистратора не установлено значение false, после чего передача к обработчикам-предкам прекращается).
Как и в случае с регистраторами, обработчики могут иметь связанные с ними уровни. Уровень обработчика действует как фильтр точно так же, как и уровень регистратора. Если обработчик действительно решает отправить событие, для отправки сообщения по назначению используется метод emit()
. Большинству пользовательских подклассов Handler
необходимо переопределить этот метод emit()
.
Пользовательские уровни¶
Определение ваших собственных уровней возможно, но в этом нет необходимости, поскольку существующие уровни были выбраны на основе практического опыта. Однако, если вы убеждены, что вам нужны пользовательские уровни, при этом следует проявлять большую осторожность, и, возможно, это очень плохая идея - определять пользовательские уровни, если вы разрабатываете библиотеку. Это связано с тем, что если все авторы нескольких библиотек определяют свои собственные пользовательские уровни, есть вероятность, что результаты ведения журнала из таких нескольких библиотек, используемых совместно, будут трудно контролировать и/или интерпретировать разработчику, использующему их, поскольку заданное числовое значение может означать разные вещи для разных библиотек.
Полезные обработчики¶
В дополнение к базовому классу Handler
предоставляется множество полезных подклассов:
StreamHandler
экземпляры отправляют сообщения потокам (файлоподобным объектам).FileHandler
экземпляры отправляют сообщения в файлы на диске.BaseRotatingHandler
- это базовый класс для обработчиков, которые поворачивают файлы журналов в определенный момент. Он не предназначен для непосредственного создания экземпляров. Вместо этого используйтеRotatingFileHandler
илиTimedRotatingFileHandler
.RotatingFileHandler
экземпляры отправляют сообщения в файлы на диске с поддержкой максимального размера файла журнала и ротации файлов журнала.TimedRotatingFileHandler
экземпляры отправляют сообщения в файлы на диске, чередуя файл журнала с определенными временными интервалами.SocketHandler
экземпляры отправляют сообщения в сокеты TCP/IP. Начиная с версии 3.4, доменные сокеты Unix также поддерживаются.DatagramHandler
экземпляры отправляют сообщения в UDP-сокеты. Начиная с версии 3.4, доменные сокеты Unix также поддерживаются.SMTPHandler
экземпляры отправляют сообщения на указанный адрес электронной почты.SysLogHandler
экземпляры отправляют сообщения демону системного журнала Unix, возможно, на удаленной машине.NTEventLogHandler
экземпляры отправляют сообщения в журнал событий Windows NT/2000/XP.MemoryHandler
экземпляры отправляют сообщения в буфер в памяти, который очищается всякий раз, когда выполняются определенные критерии.экземпляры
HTTPHandler
отправляют сообщения на HTTP-сервер, используя семантикуGET
илиPOST
.WatchedFileHandler
экземпляры отслеживают файл, в который они записываются. Если файл изменяется, он закрывается и снова открывается с использованием имени файла. Этот обработчик полезен только в Unix-подобных системах; Windows не поддерживает используемый базовый механизм.экземпляры
QueueHandler
отправляют сообщения в очередь, подобную тем, которые реализованы в модуляхqueue
илиmultiprocessing
.NullHandler
экземпляры ничего не делают с сообщениями об ошибках. Они используются разработчиками библиотек, которые хотят использовать ведение журнала, но хотят избежать сообщения «Не удалось найти обработчики для logger XXX», которое может отображаться, если пользователь библиотеки не настроил ведение журнала. Смотрите Настройка ведения журнала для библиотеки для получения дополнительной информации.
Добавлено в версии 3.1: Класс NullHandler
.
Добавлено в версии 3.2: Класс QueueHandler
.
Классы NullHandler
, StreamHandler
и FileHandler
определены в основном пакете ведения журнала. Другие обработчики определены в подмодуле logging.handlers
. (Существует также другой подмодуль, logging.config
, для функциональности настройки.)
Зарегистрированные сообщения форматируются для представления с помощью экземпляров класса Formatter
. Они инициализируются строкой формата, подходящей для использования с оператором % и словарем.
Для форматирования нескольких сообщений в пакете могут использоваться экземпляры BufferingFormatter
. В дополнение к строке формата (которая применяется к каждому сообщению в пакете), предусмотрены строки формата заголовка и концевой части.
Если фильтрации на основе уровня регистратора и/или обработчика недостаточно, экземпляры Filter
могут быть добавлены как к экземплярам Logger
, так и к экземплярам Handler
(с помощью их метода addFilter()
). Прежде чем принять решение о дальнейшей обработке сообщения, как регистраторы, так и обработчики проверяют разрешения всех своих фильтров. Если какой-либо фильтр возвращает значение false, дальнейшая обработка сообщения прекращается.
Базовая функция Filter
позволяет выполнять фильтрацию по определенному имени регистратора. Если используется эта функция, сообщения, отправленные в указанный регистратор и его дочерние элементы, пропускаются через фильтр, а все остальные отбрасываются.
Исключения, возникающие во время ведения журнала¶
Пакет ведения журнала предназначен для устранения исключений, возникающих при ведении журнала в рабочей среде. Это делается для того, чтобы ошибки, возникающие при обработке событий ведения журнала, такие как неправильная настройка журнала, сетевые или другие подобные ошибки, не приводили к преждевременному завершению работы приложения, использующего ведение журнала.
Исключения SystemExit
и KeyboardInterrupt
никогда не обрабатываются. Другие исключения, которые возникают во время выполнения метода emit()
подкласса Handler
, передаются в его метод handleError()
.
Реализация handleError()
в Handler
по умолчанию проверяет, установлена ли переменная уровня модуля raiseExceptions
. Если она установлена, выводится значение обратной трассировки sys.stderr
. Если этот параметр не задан, исключение будет пропущено.
Примечание
Значение по умолчанию raiseExceptions
равно True
. Это связано с тем, что во время разработки вы обычно хотите получать уведомления о любых возникающих исключениях. Рекомендуется установить для raiseExceptions
значение False
для использования в производственных условиях.
Использование произвольных объектов в качестве сообщений¶
В предыдущих разделах и примерах предполагалось, что сообщение, передаваемое при регистрации события, представляет собой строку. Однако это не единственная возможность. Вы можете передать произвольный объект в виде сообщения, и его метод __str__()
будет вызван, когда системе ведения журнала потребуется преобразовать его в строковое представление. На самом деле, если вы хотите, вы можете вообще избежать вычисления строкового представления - например, SocketHandler
генерирует событие, выделяя его и отправляя по проводам.
Оптимизация¶
Форматирование аргументов сообщения откладывается до тех пор, пока этого нельзя будет избежать. Однако вычисление аргументов, передаваемых методу ведения журнала, также может быть дорогостоящим, и вы, возможно, захотите избежать этого, если программа ведения журнала просто выбросит ваше событие. Чтобы решить, что делать, вы можете вызвать метод isEnabledFor()
, который принимает аргумент level и возвращает значение true, если событие было создано регистратором для этого уровня вызова. Вы можете написать код, подобный этому:
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(),
expensive_func2())
таким образом, если пороговое значение регистратора установлено выше DEBUG
, вызовы expensive_func1
и expensive_func2
никогда не выполняются.
Примечание
В некоторых случаях isEnabledFor()
сам по себе может быть дороже, чем вам хотелось бы (например, для глубоко вложенных регистраторов, где явный уровень установлен только на самом верху иерархии регистраторов). В таких случаях (или если вы хотите избежать вызова метода в замкнутых циклах) вы можете кэшировать результат вызова isEnabledFor()
в локальной переменной или переменной экземпляра и использовать это вместо вызова метода каждый раз. Такое кэшированное значение нужно было бы пересчитывать только в том случае, если конфигурация ведения журнала динамически изменяется во время работы приложения (что встречается не так уж часто).
Существуют и другие варианты оптимизации, которые могут быть выполнены для конкретных приложений, которым требуется более точный контроль за тем, какая информация собирается в журнале. Вот список того, что вы можете сделать, чтобы избежать обработки данных во время ведения журнала, которая вам не нужна:
Что вы не хотите собирать |
Как избежать его сбора |
---|---|
Информация о том, откуда были сделаны звонки. |
Установите для |
Потоковая передача информации. |
Установите для |
Идентификатор текущего процесса ( |
Установите для |
Текущее имя процесса при использовании |
Установите для |
Также обратите внимание, что основной модуль ведения журнала содержит только базовые обработчики. Если вы не импортируете logging.handlers
и logging.config
, они не будут занимать никакой памяти.
См.также
- Модуль
logging
Ссылка на API для модуля ведения журнала.
- Модуль
logging.config
API настройки для модуля ведения журнала.
- Модуль
logging.handlers
Полезные обработчики, включенные в модуль ведения журнала.