Spec-Zone.ru › Python 3.8

Руководство по ведению журналов (Logging HOWTO)

Автор

Винай Саджип <vinay_sajip at red-dove dot com>

Базовый учебник по ведению журналов

Ведение журналов — это способ отслеживания событий, происходящих при выполнении некоторого программного обеспечения. Разработчик ПО добавляет в свой код вызовы ведения журналов, чтобы указать, что произошли определённые события. Событие описывается описательным сообщением, которое может необязательно содержать переменные данные (т.е. данные, которые потенциально могут отличаться для каждого случая события). События также имеют важность, которую разработчик приписывает событию; важность также может называться уровнем или степенью серьезности.

Когда использовать ведение журналов

Ведение журналов предоставляет набор функций для простого использования ведения журналов. Это debug(), info(), warning(), error() и critical(). Чтобы определить, когда использовать ведение журналов, см. таблицу ниже, которая указывает для каждого из набора общих задач лучший инструмент для его решения.

Задача, которую вы хотите выполнить

Лучший инструмент для задачи

Отображение вывода консоли для обычного использования командной строки скрипта или программы

print()

Отчёт об событиях, происходящих во время нормальной работы программы (например, для мониторинга состояния или поиска неисправностей)

logging.info() (или logging.debug() для очень подробного вывода в диагностических целях)

Выдача предупреждения относительно конкретного события выполнения

warnings.warn() в коде библиотеки, если проблема избежима и приложение-клиент должно быть изменено, чтобы устранить предупреждение

logging.warning(), если приложение-клиент ничего не может сделать с ситуацией, но событие всё равно должно быть отмечено

Отчёт об ошибке относительно конкретного события выполнения

Вызвать исключение

Отчёт о подавлении ошибки без подъёма исключения (например, обработчик ошибок в долго выполняющемся процессе сервера)

logging.error(), logging.exception() или logging.critical(), как это соответствует конкретной ошибке и области применения приложения

Функции ведения журналов названы по уровню или степени серьезности событий, которые они используются для отслеживания. Стандартные уровни и их применимость описаны ниже (в порядке возрастания степени серьезности):

Уровень

Когда он используется

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. Выводимое сообщение включает указание уровня и описание события, предоставленного в вызове ведения журнала, т.е. «Осторожно!». Пока не беспокойтесь о части «root»: это будет объяснено позже. Фактический вывод может быть отформатирован довольно гибко, если вам это нужно; параметры форматирования также будут объяснены позже.

Ведение журналов в файл

Очень распространённая ситуация — запись событий ведения журналов в файл, поэтому давайте посмотрим на это дальше. Убедитесь, что вы попробуете следующее в недавно запущенном интерпретаторе Python и не просто продолжайте от сессии, описанной выше:

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

И теперь, если мы откроем файл и посмотрим на то, что у нас есть, мы должны найти сообщения журнала:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too

Этот пример также показывает, как вы можете установить уровень ведения журнала, который действует как порог для отслеживания. В этом случае, поскольку мы установили порог в 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)

Вывод будет таким же, как и раньше, но к файлу журнала больше не будет добавляться, поэтому сообщения от предыдущих запусков потеряются.

Ведение журналов из нескольких модулей

Если ваша программа состоит из нескольких модулей, вот пример того, как вы могли бы организовать ведение журналов в ней:

# myapp.py
import logging
import mylib

def main():
    logging.basicConfig(filename='myapp.log', level=logging.INFO)
    logging.info('Started')
    mylib.do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()
# mylib.py
import logging

def do_something():
    logging.info('Doing something')

Если вы запустите myapp.py, вы увидите это в myapp.log:

INFO:root:Started
INFO:root:Doing something
INFO:root:Finished

что, надеюсь, вы и ожидали увидеть. Вы можете обобщить это на несколько модулей, используя шаблон в mylib.py. Обратите внимание, что для этого простого шаблона использования вы не будете знать, откуда в вашем приложении пришли ваши сообщения, кроме как глядя на описание события. Если вы хотите отслеживать местоположение ваших сообщений, вам нужно обратиться к документации за пределами учебного уровня — см. Расширенный учебник по ведению журналов.

Ведение журналов переменных данных

Для ведения журналов переменных данных используйте строку формата для сообщения описания события и добавьте переменные данные в качестве аргументов. Например:

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 (описание события, включая переменные данные) и, возможно, отобразить время события. Это описано в следующей секции.

END_OF_DOCUMENT_MARKER

Отображение даты/времени в сообщениях

Для отображения даты и времени события, поместите ‘%(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().

Следующие шаги

Это завершает базовый учебник. Его должно хватить, чтобы вы начали работать с ведением логов. Библиотека logging предлагает многое другое, но для извлечения максимума из неё, вам нужно будет потратить немного больше времени на чтение следующих разделов. Если вы готовы к этому, возьмите ваш любимый напиток и продолжайте.

Если ваши потребности в ведении логов простые, используйте приведенные выше примеры, чтобы включить ведение логов в свои скрипты. Если у вас возникнут проблемы или вы что-то не поймёте, задайте вопрос на Usenet-группе comp.lang.python (https://groups.google.com/forum/#!forum/comp.lang.python), и вы, скорее всего, получите помощь в ближайшее время.

Всё ещё здесь? Вы можете продолжить чтение следующих разделов, которые предоставляют немного более продвинутый/глубокий учебник, чем базовый выше. После этого вы можете взглянуть на Руководство по ведению логов.

Учебник по расширенному ведению логов

Библиотека ведения логов использует модульный подход и предоставляет несколько категорий компонентов: логгеры, обработчики, фильтры и форматеры.

  • Логгеры предоставляют интерфейс, который непосредственно использует код приложения.
  • Обработчики отправляют записи логов (созданные логгерами) в соответствующее место назначения.
  • Фильтры обеспечивают более тонкую возможность определения записей логов, которые следует выводить.
  • Форматеры определяют макет записей логов в конечном выводе.

Информация о событиях ведения логов передается между логгерами, обработчиками, фильтрами и форматерами в экземпляре LogRecord.

Ведение логов выполняется путем вызова методов экземпляров класса Logger (в дальнейшем называемых логгерами). Каждый экземпляр имеет имя и концептуально организован в иерархии имен с использованием точек (точек с запятой) в качестве разделителей. Например, логгер с именем «сканирование» является родителем логгеров «сканирование.текст», «сканирование.html» и «сканирование.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() с ключевым аргументом формат. Все параметры по поводу того, как строится строка формата, см. в Объекты форматера.

Поток ведения логов

Поток информации о событиях ведения логов в логгерах и обработчиках показан на следующей диаграмме.

../_images/logging_flow.png

Логгеры

Объекты Logger имеют трехкратную задачу. Во-первых, они предоставляют несколько методов для кода приложения, чтобы приложения могли регистрировать сообщения во время выполнения. Во-вторых, объекты логгеров определяют, какие сообщения логов обрабатывать, исходя из степени важности (стандартная система фильтрации) или объектов фильтров. В-третьих, объекты логгеров передают соответствующие сообщения логов всем заинтересованным обработчикам логов.

Наиболее часто используемые методы объектов логгеров делятся на две категории: конфигурация и отправка сообщений.

Вот наиболее распространенные методы конфигурации:

  • Logger.setLevel() указывает минимальную степень важности сообщения лога, которое будет обрабатывать логгер, где «отладка» — самый низкий встроенный уровень серьезности, а «критический» — самый высокий встроенный уровень серьезности. Например, если уровень серьезности — «информация», логгер будет обрабатывать только сообщения «информация», «предупреждение», «ошибка» и «критическая» и проигнорирует сообщения «отладки».
  • 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(). Например, приложение может захотеть отправить все сообщения журнала в файл журнала, все сообщения об ошибках или более высокого уровня в стандартный вывод, а все сообщения критического уровня — на адрес электронной почты. Для этого сценария требуются три отдельных обработчика, каждый из которых отвечает за отправку сообщений определенного уровня серьезности в определенное место.

Стандартная библиотека включает в себя довольно много типов обработчиков (см. Полезные обработчики); в учебниках в основном используются 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 стилей; возможные ключи описаны в Атрибуты записи журнала. Если стиль равен ‘{’, предполагается, что строка формата сообщения совместима с 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 для отображения по Гринвичу).

Настройка регистрации

Программисты могут настроить регистрацию тремя способами:

  1. Создание регистраторов, обработчиков и форматировщиков явно с помощью кода Python, вызывающего методы конфигурации, перечисленные выше.
  2. Создание файла конфигурации регистрации и чтение его с помощью функции fileConfig().
  3. Создание словаря с информацией о конфигурации и передача его функции 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
datefmt=

Вывод почти идентичен выводу примера без файла конфигурации:

$ 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]

Для получения дополнительной информации о регистрации с использованием словаря см. Функции конфигурации.

Что происходит, если конфигурация не задана

Если конфигурация ведения журнала не задана, может возникнуть ситуация, когда событие ведения журнала необходимо вывести, но обработчики для вывода этого события не найдены. Поведение пакета ведения журнала в таких обстоятельствах зависит от версии Python.

Для версий Python до 3.2 поведение следующее:

  • Если logging.raiseExceptions равно False (режим производства), событие отбрасывается без сообщений.
  • Если logging.raiseExceptions равно True (режим разработки), сообщение «Не удалось найти обработчики для логгера X.Y.Z» выводится один раз.

В Python 3.2 и более поздних версиях поведение следующее:

  • Событие выводится с помощью «обработчика последней инстанции», хранящегося в logging.lastResort. Этот внутренний обработчик не связан ни с одним логгером и действует как StreamHandler, который записывает сообщение об описании события в текущее значение sys.stderr (следовательно, учитываются все перенаправления, которые могут быть в действии). Никакой форматирования сообщения не выполняется — выводится только простое сообщение об описании события. Уровень обработчика установлен на значение WARNING, поэтому все события с таким и более высоким уровнем серьезности будут выведены.

Чтобы получить поведение до версии 3.2, logging.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».

Примечание

Настоятельно рекомендуется не добавлять никаких обработчиков, кроме NullHandler в логгеры вашей библиотеки. Это потому, что настройка обработчиков — прерогатива разработчика приложения, использующего вашу библиотеку. Разработчик приложения знает свою целевую аудиторию и какие обработчики наиболее подходят для его приложения: если вы добавляете обработчики «внутри», вы можете помешать их способности выполнять unit-тесты и предоставлять журналы, соответствующие их требованиям.

Уровни ведения журнала

Числовые значения уровней ведения журнала приведены в следующей таблице. Они в первую очередь представляют интерес, если вы хотите определить свои собственные уровни и нуждаетесь в них, чтобы иметь конкретные значения по отношению к предопределенным уровням. Если вы определите уровень с таким же числовым значением, он перезапишет предопределенное значение; предопределенное имя будет утеряно.

Уровень

Числовое значение

CRITICAL

50

ERROR

40

WARNING

30

INFO

20

DEBUG

10

NOTSET

0

Уровни также могут быть связаны с логгерами, настраиваясь либо разработчиком, либо при загрузке сохраненной конфигурации ведения журнала. Когда вызывается метод ведения журнала для логгера, логгер сравнивает свой собственный уровень с уровнем, связанным с вызовом метода. Если уровень логгера выше уровня вызова метода, сообщение ведения журнала фактически не генерируется. Это основной механизм управления подробностью вывода ведения журнала.

Сообщения ведения журнала закодированы как экземпляры класса LogRecord. Когда логгер принимает решение фактически залогировать событие, экземпляр LogRecord создается из сообщения ведения журнала.

Сообщения ведения журнала передаются по механизму распределения с помощью обработчиков, которые являются экземплярами подклассов класса Handler. Обработчики отвечают за обеспечение того, чтобы записанное сообщение (в форме экземпляра LogRecord) оказывалось в определенном месте (или наборах мест), полезном для целевой аудитории этого сообщения (таких как конечные пользователи, сотрудники службы поддержки, системные администраторы, разработчики). Обработчикам передаются экземпляры LogRecord, предназначенные для определенных пунктов назначения. Каждый логгер может иметь ноль, один или несколько обработчиков, связанных с ним (через метод addHandler() объекта Logger). В дополнение к любым обработчикам, непосредственно связанным с логгером, вызываются все обработчики, связанные со всеми предками логгера, для обработки сообщения (если флаг propagate для логгера установлен в значение false, передача обработчикам предков прекращается).

Так же, как и для логгеров, обработчики могут иметь связанные с ними уровни. Уровень обработчика действует как фильтр таким же образом, как и уровень логгера. Если обработчик принимает решение фактически обработать событие, используется метод emit(), чтобы отправить сообщение в его пункт назначения. Большинству пользовательских подклассов Handler потребуется переопределить этот метод emit().

Пользовательские уровни

Определение собственных уровней возможно, но не обязательно, поскольку существующие уровни были выбраны на основе практического опыта. Однако, если вы убеждены, что вам нужны пользовательские уровни, следует проявлять особую осторожность при этом, и это, возможно, очень плохая идея определять пользовательские уровни, если вы разрабатываете библиотеку. Это связано с тем, что если несколько авторов библиотек определят свои собственные пользовательские уровни, существует вероятность того, что вывод ведения журнала таких нескольких библиотек, используемых вместе, будет сложно контролировать и/или интерпретировать для разработчика, использующего их, потому что данное числовое значение может означать разные вещи для разных библиотек.

Полезные обработчики

Помимо базового класса Handler, предоставляются многие полезные подклассы:

  1. StreamHandler экземпляры отправляют сообщения в потоки (объекты, подобные файлам).
  2. FileHandler экземпляры отправляют сообщения в файлы на диске.
  3. BaseRotatingHandler является базовым классом для обработчиков, которые вращают файлы журналов в определённый момент. Он не предназначен для прямого создания экземпляров. Вместо этого используйте RotatingFileHandler или TimedRotatingFileHandler.
  4. RotatingFileHandler экземпляры отправляют сообщения в файлы на диске, с поддержкой максимального размера файлов журналов и вращения файлов журналов.
  5. TimedRotatingFileHandler экземпляры отправляют сообщения в файлы на диске, вращая файл журнала через определённые временные интервалы.
  6. SocketHandler экземпляры отправляют сообщения в TCP/IP сокеты. С версии 3.4 также поддерживаются сокеты Unix-доменных сокетов.
  7. DatagramHandler экземпляры отправляют сообщения в UDP сокеты. С версии 3.4 также поддерживаются сокеты Unix-доменных сокетов.
  8. SMTPHandler экземпляры отправляют сообщения на указанный адрес электронной почты.
  9. SysLogHandler экземпляры отправляют сообщения в демона Unix syslog, возможно, на удалённой машине.
  10. NTEventLogHandler экземпляры отправляют сообщения в журнал событий Windows NT/2000/XP.
  11. MemoryHandler экземпляры отправляют сообщения в буфер в оперативной памяти, который сбрасывается всякий раз, когда выполняются определённые условия.
  12. HTTPHandler экземпляры отправляют сообщения на HTTP-сервер, используя либо GET , либо POST семантику.
  13. WatchedFileHandler экземпляры наблюдают за файлом, в который они ведут журнал. Если файл изменяется, он закрывается и переоткрывается с использованием имени файла. Этот обработчик полезен только на системах Unix-подобных системах; Windows не поддерживает используемый подлежащий механизм.
  14. QueueHandler экземпляры отправляют сообщения в очередь, такие как те, которые реализованы в модулях queue или multiprocessing.
  15. NullHandler экземпляры не выполняют никаких действий с сообщениями об ошибках. Они используются разработчиками библиотек, которые хотят использовать ведение журнала, но хотят избежать сообщения «No handlers could be found for 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(), который принимает аргумент уровня и возвращает true, если событие будет создано регистратором для этого уровня вызова. Вы можете написать код такого типа:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

Таким образом, если порог регистратора установлен выше DEBUG, вызовы expensive_func1() и expensive_func2() никогда не будут сделаны.

Примечание

В некоторых случаях isEnabledFor() может быть более дорогостоящим, чем хотелось бы (например, для глубоко вложенных регистраторов, где явный уровень устанавливается только высоко в иерархии регистраторов). В таких случаях (или если вы хотите избежать вызова метода в узких циклах), вы можете кэшировать результат вызова isEnabledFor() в локальной или экземпляровом переменной и использовать её вместо вызова метода каждый раз. Такое кэшированное значение нужно пересчитывать только при динамических изменениях конфигурации регистрации во время работы приложения (что не так уж и часто).

Существуют и другие оптимизации, которые можно выполнить для конкретных приложений, которым требуется более точный контроль над собираемой информацией регистрации. Вот список вещей, которые вы можете сделать, чтобы избежать обработки во время регистрации, которая вам не нужна:

Информация, которую вы не хотите собирать

Как избежать её сбора

Информация о том, откуда были сделаны вызовы.

Установите logging._srcfile на None. Это позволяет избежать вызова sys._getframe(), что может помочь ускорить ваш код в средах, таких как PyPy (который не может ускорить код, использующий sys._getframe()), если и когда PyPy поддерживает Python 3.x.

Информация о потоках.

Установите logging.logThreads на 0.

Информация о процессе.

Установите logging.logProcesses на 0.

Также обратите внимание, что основной модуль регистрации включает только базовые обработчики. Если вы не импортируете logging.handlers и logging.config, они не будут занимать память.

См. также

Module logging

Справочник по API модуля регистрации.

Module logging.config

API конфигурации для модуля регистрации.

Module logging.handlers

Полезные обработчики, включенные в модуль регистрации.

Руководство по регистрации

© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/howto/logging.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API