Архив метки: logging

Продвинутое руководство по логированию (Перевод)

Библиотека logging использует модульный подход и предлагает несколько категорий компонентов: loggers, handlers, filters, и formatters.
  • Logger представляет интерфейс, который использует непосредственно код приложения.
  • Handler посылает запись лога (созданную logger'ом) в соответствующее расположение.
  • Filter позволяет определить, какую запись лога выводить.
  • Formatter определяет расположение записи лога в итоговом выводе.
Информация логирования передаётся между logger, handler, filter и formatter в экземпляре LogRecord.
Логирование осуществляется вызовом методов экземпляра класса Logger (тут и далее он будет называться loggers). Каждый экземпляр имеет своё имя и эти имена располагаются в иерархии пространства имён, используя точки в качестве разделителей. Например, logger с именем ‘scan’ является родительским logger'ом для logger'ов ‘scan.text’, ‘scan.html’ и ‘scan.pdf’. Имена logger'ов могут быть любыми, как Вы хотите, и отображают место, где было создано сообщение лога.
Общепринято использовать  для имён logger'ов имена модулей:
logger = logging.getLogger(__name__)
В таком случае имя logger'a соответствует иерархии пакетов/модулей и интуитивно становится понято, где именно события логируются уже из имени logger'a.
Корень иерархии logger'ов называется root logger. Этот тот logger, который используется функциями debug(), info(), warning(), error() и critical(), которые просто вызывают одноимённые меоды root logger'a. Функции и методы имеют одну и ту же подпись. Имя root logger’а отображается как ‘root’ в выводе логов.
Конечно же возможно логировать сообщения в разные места. Вы можете записывать сообщения в файлы, передавать по HTTP GET/POST, email'y через SMTP, сокеты или OS-специфичные механизмы логирования, такие как syslog или Windows NT event log. Место, куда отправляются логи, определяется классами handler. Вы можете создать свой собственный класс направления логов, если у Вас есть какие-то свои особые потребности.
По умолчанию место направления не задано для сообщение логов. Вы можете определить его при помощи basicConfig(), как в примерах руководства. Если Вы вызываете функции debug(), info(), warning(), error() и critical(), они будут проверять, задано ли это место, и если оно не задано, то вывод направляется в консколь (sys.stderr) и используется формат отображения сообщений по умолчанию.
Формат отображения, задаваемый по умолчанию в basicConfig() для сообщений:
severity:logger name:message
Вы можете изменить его передав строку формата в basicConfig(
)
в аргументе format. Более подробно все опции форматирования описаны в Formatter Objects.

Поток форматирования

иллюстрация тут

Перемещение информации лога между logger'ами и handler'ами иллюстрирован диаграммой по ссылке выше.

Logger'ы

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

  • Logger.setLevel() определяет минимальный уровень сообщений, которые будут обработаны; debug — минимальный встроенный уровень, а critical — максимальный. Например, если установлен уровень INFO, logger будет обрабатывать сообщения уровня INFO, WARNING, ERROR и CRITICAL и игнорировать сообщения уровня DEBUG.
  • Logger.addHandler() и Logger.removeHandler() добавляют и удаляют объекты handler из объекта logger. Handler'ы более подробно обсуждены в Handlers.
  • Logger.addFilter() и Logger.removeFilter() добавляют и удалют объекты filter из объекта logger. Filter'ы более подробно обсуждаются в Filter Objects.

Вам не нужно вызывать эти методы каждый раз для каждого logger, который Вы создаёте. Смотрите последние два абазца этого раздела.
Когда объект 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() возвращает ссылку на экземпляр logger'а с именем, если оно задано, или root, если нет. Имена представляют из себя иерархическую структуру с точками в качестве разделителей. Множественные вызовы getLogger() с одним и тем же имененм будут возвращать ссылку на один и тот же объект logger'а. Logger'ы, находящиеся ниже в иерархии являются дочерними для logger'ов, которые находятся выше. Например, для logger'а с имененм foo logger'ы с именами foo.bar, foo.bar.baz и foo.bam будут дочерними.
Logger'ы поддерживают концепцию эффективного уровня. Если для logger'а не задан уровень явно, то используется уровень его родителя. Если и его родитель не имеет заданного уровня — смотрится родитель родителя, и так далее. Корневой logger всегда имеет явно заданный уровень (по умолчанию это WARNING). При решении вопроса обрабатывать ли событи используется именно эффективный уровень.
Дочерние logger'ы распространяют сообщения handler'ам, связанным с родительским logger'ом. Из-за этого нет необходимости определять и настраивать handler'ы для всех logger'ов в приложении. Но важно сконфигурировать handler'ы для logger'ов верхнего уровня и уже потом создавать при необходимости дочерние logger'ы. (Однако, распространение сообщений можно отключить, задав значением атрибута propagate logger'a равным False.)

Handler'ы

Объекты Handler отвечают за отправку соответствующего сообщения (соответствующего уровня) к его месту назначения, определённого в handler'e. Объекты logger могут добавить себе ноль или более handler'ов при помощи метода addHandler(). Например, приложение может хотеть отправлять все сообщения в файл логов, сообщения уровня ошибки и выше в stdout, а все критические сообщения отправлять на почту. Этот сценарий требует три handler'a, каждый из которых отвечает за отправку сообщений определённого уровня в определённое место.
Стандартная библиотека включает в себя несколько типов handler'ов (см Useful Handlers); это руководство по большей части использует в примерах StreamHandler и FileHandler.
Есть несколько методов у handler'a для разработчиков приложений, о которых стоит позаботиться. Методы, которые нужны тем, кто будет пользоваться встроенными handler'ами (то есть, не будет использовать самописные) следующие:

  • Метод Handler.setLevel() аналогичен методу объекта logger, он определяет минимальный уровень, который будет направлен в соответствующее место. Зачем нужно два метода setLevel()? Уровень, заданный в logger'e определяет уровень сообщений, который будет передан в handler'ы. Уровень, заданный в каждом handler'e определяет сообщения, которые этот handler будут посылать.
  • setFormatter() определяет объект Formatter, который будет использовать этот handler.
  • addFilter() и removeFilter() соответственно добавляют и удаляют объекты фильтров из handler'a.

Код приложения не должен напрямую инициализировать и использовать экземпляры Handler. Вместо этого, класс Handler является базовым классом, который определяет интерфейс, которым должны обладать все handler'ы и определяет некоторое поведение по умолчанию, которое могут использовать дочерние классы (или переопределять их).

Formatter'ы

Хватит использовать “print” для отладки: пятиминутное введение в модуль logging Python’а (Перевод)

  • Это руководство короткое
  • Для поиска ошибок в коде Вы можете использовать print для просмотра значений переменных
  • НЕ делайте этого. Используйте модуль logging
Модуль logging предпочтительнее, так как:
  • Легко можно поместить указатель времени в каждое сообщение
  • Вы можете использовать разные уровни срочности ваших сообщений и фильтровать их по этому уровню
  • Когда Вы захотите позже найти / изменить лог-сообщения Вы не перепутаете их с другим выводом команды print
  • Если Вы хотите вывести лог в файл, то очень легко будет игнорировать вывод лог-сообщений
Использование pirnt подходит для тех программистов, у которых достаточно времени. Но лучше использовать logging. Кроме того, научитесь использовать отладчик Python для отладки ошибок и Pylint для предотвращения появления ошибок и для того, чтобы сделать код более читаемым.
Для того, чтобы отобразить лог-сообщения на экране, используйте такой код:

import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug('This is a log message.')

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

import logging
logging.basicConfig(filename='log_filename.txt',level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug('This is a log message.')

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

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh = logging.FileHandler('log_filename.txt')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
logger.addHandler(fh)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
logger.addHandler(ch)
logger.debug('This is a test log message.')

Убедитесь, что переменная logging глобальная, так что её смогут видеть все функции (использовать в них gloval logger не нужно, так как эта переменная только для чтения, не для записи и изменения).

Уровни логирования, от более срочных до менее срочных:
  1. CRITICAL
  2. ERROR
  3. WARNING
  4. INFO
  5. DEBUG
Вызов setLevel() устанавливает минимальный уровень логирования, нужный Вам. Например, если Вы используете fh.setLevel(logging.ERROR), тогда будут сообщения  с уровнем WARNING, INFO и DEBUG не будут записываться в файл (так как fh - обработчик лог-файла в нашем примере, в отличие от ch, который обрабатывает логи для отображения на экране).
Для записи сообщен
ий разного уровня используйте
:
  1. logger.critical('This is a critical message.')
  2. logger.error('This is an error message.')
  3. logger.warning('This is a warning message.')
  4. logger.info('This is an informative message.')
  5. logger.debug('This is a low-level debug message.')
На самом деле Вы можете делать гораздо больше, но это всё, что Вам надо знать, чтобы больше не использовать print для отладки программ.
Кроме того, функция pprint.pprint() хороша для вывода словарей и списков, особенно вложенных. Попробуйте и увидите.

Последний совет: Вы можете использовать команду tail -f logfile.txt чтобы посмотреть файл, куда будет записываться информация. Опция -f означает “follow (следовать)”. Просто оставьте окно терминала открытым с запущенной командой, и в нем автоматически будет появляться записываемый в файл текст. Этот способ избавит Вас от необходимости открывать текстовый редактор.
Команда tail присутствует на Mac OS X и Linux / *nix. На Windows Вы можете использовать для её получения проект Cygwin.

Автор: Ishayahu Lastov