Основные концепции журналирования в Python
Понимание принципов работы модуля журналирования, поставляемого в стандартной библиотеке Python, поможет разработчику упростить процесс создания приложения.
21К открытий22К показов
По мере того, как усложняется структура приложения, ведение логов (журналирование) становится всё полезнее для разработчика. Логи не только пригодятся в процессе отладки, но и помогут обнаружить скрытые проблемы проекта, а также отследить его производительность.
Модуль logging, входящий в состав стандартной библиотеки Python, предоставляет большую часть необходимых для журналирования функций. Если его правильно настроить, записи лога могут предоставить большое количество полезной информации о работе приложения: в каком месте кода была сформирована запись, какие потоки и процессы были запущены, каково состояние памяти.
Данный материал — руководство для начинающих разработчиков.
Обратите внимание, что весь размещённый в статье код подразумевает, что вы уже импортировали модуль:
Основные концепции журналирования на Python
В данном разделе рассмотрим уровни журналирования, форматирование журналов, обработчики и диспетчер журналирования.
Уровни журналирования
Уровни журналирования соотносятся с важностью лога: запись об ошибке должна быть важнее предупреждения, а отладочный журнал должен быть полезен только при отладке приложения.
В Python существует шесть уровней лога. Каждому уровню присвоено целое число, указывающее на важность лога: NOTSET=0,
DEBUG=10
, INFO=20
, WARN=30
, ERROR=40
и CRITICAL=50
.
Все уровни последовательно упорядочены (DEBUG
< INFO
< WARN
), кроме NOTSET
, который мы отдельно разберём далее.
Форматирование журналов
Форматирование журналов дополняет сообщения контекстной информацией. Это помогает установить, когда была создана запись, из какого участка приложения (файл, строка, метод и т. п.), а также поток и процесс, что может оказаться полезным при отладке многопоточных проектов.
Например, при форматировании записи «hello world»:
в логе она отобразится таким образом:
Обработчик журналирования
Обработчик журналов — это компонент, который записывает и отображает логи. Например, StreamHandler
выводит записи на консоль, FileHandler
— в файл, SMTPHandler
отправляет на электронную почту.
В каждом обработчике есть два важных поля:
- Форматер, добавляющий в лог контекстную информацию.
- Уровень лога, который отфильтровывает журналы низшего уровня. Обработчик уровня INFO не будет работать с журналами уровня DEBUG.
Стандартная библиотека содержит обработчики, которых в большинстве случаев должно быть достаточно. Самые распространённые — StreamHandler
и FileHandler
.
Диспетчер журналирования
Вероятно, диспетчер журналирования — то, что используется в коде чаще всего, и в то же время является источником наибольших трудностей. Новый диспетчер журналирования можно создать следующим образом:
В диспетчере три основных поля.
- Распространение. Определяет, должен ли лог обрабатываться родительским диспетчером (по умолчанию эта функция активна).
- Уровень. Как и в обработчике, уровень отфильтровывает менее важные журналы. Отличие заключается в том, что уровень проверяется только в дочерних диспетчерах, при распространении вверх уровень не учитывается.
- Список обработчиков, в которые направляется попавшая в диспетчер запись. Он позволяет гибко настраивать обработку записей. К примеру, можно создать обработчик, записывающий всё с уровнем
DEBUG
, и обработчик для электронной почты, работающий только с уровнемCRITICAL
. Взаимоотношения диспетчера с обработчиками строятся по принципу издатель-потребитель. Запись передаётся всем обработчикам, как только пройдёт проверку уровня в диспетчере.
Имя диспетчера уникально, таким образом, если диспетчер с именем «toto» уже создан, последующие вызовы logging.getLogger("toto")
будут возвращать один и тот же объект.
Как можно догадаться, диспетчеры выстраиваются в соответствии с иерархией. На вершине находится корневой диспетчер, получить доступ к которому можно с помощью logging.root()
. Он вызывается при использовании методов, подобных logging.debug()
. По умолчанию корневому журналу присваивается уровень WARN
, поэтому каждый журнал с меньшим уровнем (например logging.info("info")
) игнорируется. Кроме того, обработчик по умолчанию для корневого диспетчера создаётся автоматически, когда добавляется первая запись в журнал уровня выше WARN
. Не рекомендуется обращаться к корневому диспетчеру прямо или косвенно с помощью методов, подобных logging.debug()
.
При создании нового диспетчера в качестве родительского будет установлен корневой.
Диспетчер использует «запись с точкой», то есть объект с именем «a.b» будет дочерним по отношению к «a». Однако это правило действительно только при существовании «a», иначе родительским всё равно будет корневой диспетчер.
Когда диспетчер определяет, что запись проходит фильтр по уровню (если уровень лога ниже уровня диспетчера, запись игнорируется), применяется не действительный, а эффективный уровень. Эффективный уровень обычно равен уровню диспетчера, однако при этом уровень NOTSET
является исключением. В таком случае эффективный уровень будет равен уровню первого диспетчера, расположенного выше в вертикали наследования, уровень которого не равен NOTSET
.
По умолчанию уровень нового диспетчера NOTSET
, и, поскольку уровень корневого равен WARN
, то и эффективный уровень нового будет WARN
. Таким образом, если даже к новому диспетчеру прикреплены обработчики, они не вызываются, если уровень лога не превышает WARN.
Уровень диспетчера используется по умолчанию для пропуска записи, которая будет проигнорирована, если её уровень недостаточно высок.
Практические советы
Модуль журналирования весьма полезен, однако некоторые его особенности могут вызвать головную боль даже у опытных разработчиков. Есть несколько практических советов по применению этого модуля.
- Настроить корневой диспетчер, но не использовать его в коде, к примеру, никогда не вызывать функцию
logging.info()
, которая скрыто обращается к корневому диспетчеру. Если вы хотите отловить сообщения об ошибках из используемых библиотек, настройте этот диспетчер, например, на запись в файл, чтобы облегчить отладку. По умолчанию сообщения выводятся только вstderr
, поэтому журнал легко потерять. - Для использования журналирования создайте новый диспетчер с помощью
logging.getLogger(logger name)
. Обратите внимание на валидность имени. Чтобы добавить обработчики, можно использовать метод, возвращающий диспетчер.
После этого можно создать и использовать новый диспетчер:
- Используйте классы RotatingFileHandler, например так, как применён TimedRotatingFileHandler в приведённом выше примере вместо FileHandler. Благодаря этому смена файла журнала будет происходить автоматически при достижении максимального размера или по расписанию.
- Для автоматического отслеживания сообщений об ошибках используйте такие инструменты, как Sentry, Airbrake, Raygun и тому подобные. Особенно это полезно при работе над веб-приложениями, где логи могут быть весьма многословными и ошибки легко теряются в общей массе. Ещё одно преимущество этих инструментов в том, что они отображают значения переменных при обнаружении ошибок. Таким образом можно определить, какой URL или пользователь спровоцировал проблему и тому подобное.
21К открытий22К показов