Основы работы библиотеки Python Logging

Лог-журнал – это реестр, в котором хранятся проблемы, которые возникают по ходу выполнения приложения. Любая программа, разрабатываемая человеком, со временем усложняется, и отследить, в каком конкретно месте возникла проблема, не всегда является возможным. В таких случаях лог-журнал сильно выручает.

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

Хотя модуль logging имеет много положительных сторон, его настроить довольно непросто. Хотя у него есть официальная документация, она не учитывает всех нюансов его использования.

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

import logging

Концепции Python Logging

Здесь вы найдете описание некоторых концепций, используемых в этом модуле.

Уровни Python Logging

Чтобы задать значимость лога, обозначается его уровень. Проще говоря, лог ERROR имеет выше приоритетность по сравнению с WARNING. Есть еще один уровень – DEBUG, который применяется исключительно во время отладки программы. 

В арсенале Python таких уровней шесть:

NOTSET=0, DEBUG=10, INFO=20, WARNING=30, ERROR=40 и CRITICAL=50.

В целом, можно приоритетность понять интуитивно. То есть, информирующий лог имеет выше приоритетность, чем отладочный. Единственное исключение, о котором надо поговорить отдельно – NOTSET.

Форматирование лога в Python

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

«%(time)s — %(log_name)s — %(level)s — %(func_name)s:%(line_no)d — %(message)s»

Такой формат лога в записи будет выглядеть следующим образом:

2019-01-16 10:35:12,468 — keyboards — ERROR — <module>:1 — привет мир

Обработчик ведения лога в Python

В Python есть отдельный компонент, который называется обработчиком лога. Он – главный, поскольку отвечает как за отображение, так и за запись его данных. Отображение лога осуществляется через StreamHandler (в консоли), в файле (через FileHandler). Также для этого может использоваться электронная почта (SMTPHandler) или другие методы. 

Каждый обработчик состоит из двух важных полей.

  1. Форматировщик. С его помощью происходит добавление контекстной информации в лог. 
  2. Степень значимости лога. Здесь как раз и задаются уровни Python Logging, о которых мы говорили ранее. Вернее, с какими из них будет работать обработчик (а также теми, которые выше). Если он отвечает за работу с уровнем WARNING, он не будет ничего делать с уровнем INFO. То есть, если уровень находится ниже, с ним обработчик ничего не делает. Если выше – обрабатывает его.

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

Найти их описание можно в официальной документации Python: https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers.

Наиболее часто используются StreamHandler и FileHandler. Работают они следующим образом.

console_handler = logging.StreamHandler()

file_handler = logging.FileHandler(«MyLogFile.txt»)

Python Logger

В большинстве случаев в коде будет использоваться логгер. Чтобы получить новый Logger, необходимо набрать строку кода по этому шаблону.

toto_logger = logging.getLogger(«Privacy»)

Логгер включает три поля:

  1. Propagate: обозначает необходимость лога распространяться на родителя журнала. По умолчанию он это делает. Это булевое значение, которое может быть равно True (то есть, распространяется) или False (не распространяется).
  2. Level. Аналогично уровню обработчика лога, уровень логгера применяется, чтобы фильтровать логи, значимость которых меньше заданного. Помимо этого, в противопоставление обработчику лога, проверка уровня осуществляется исключительно на «детях» логгера. И лишь после его распространения на родителей, проверка уровня не будет осуществляться. Это контринтуитивное поведение, но обработчик действует именно таким образом. 
  3. Handlers. Это обработчики, которые будут работать с логом. Это позволяет более тонко обрабатывать логи, создавать несколько обработчиков лога. Например, для регистрации логов DEBUG будет использоваться обработчик, записывающий в файл. А вот критические логи отправляются по электронной почте. 

Для определение логгера используется имя. То есть, при создании лога, называющегося foo, все остальные вызовы logging.getLogger (“foo”), будут возвращать одинаковый объект.

assert id(logging.getLogger(«foo»)) == id(logging.getLogger(«foo»))

Как вы могли понять, каждый логгер подчиняется другим. Сверху этой лестницы находится корневой лог. Чтобы его получить, используется метод logging.root. Вызов этого лога осуществляется при использовании таких методов, как logging.debug(). При стандартных параметрах, корневой лог имеет уровень WARNING. Следовательно, все логи, имеющие не такую высокую значимость, просто игнорируются. Например, пропускаться будет лог, имеющий значимость INFO, поскольку он просто выдает сообщение, а не что-то важное, такое как предупреждение или информацию об ошибке.

Еще одна характеристика корневого логгера в том, что обработчик создается при первичном входе в лог с уровнем, который превышает WARNING. Корневой логгер может не рекомендуется, если применять методы logging.debug()

lab = logging.getLogger(«f.r»)

assert lab.parent == logging.root # lab.parent действительно корневой логгер

При этом, логгер применяет «точечную запись». Это означает, что логгер с именем f.r будет использоваться в качестве дочернего элемента по отношению к логгеру f. Но о справедливости этого утверждения можно говорить исключительно если создан лог f. В другом случае, родитель fr будет корнем. 

la = logging.getLogger(«f»)

assert lab.parent == la # родитель lab теперь la вместо корня

Что подразумевается под эффективным уровнем логгера?

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

В большинстве случаев эффективный уровень и уровень логгера – это одно и то же. Единственное исключение – если уровень не равен NOTSET. При этом, если уровень логгера равен этому, то тогда эффективным будет первый уровень родителя, у которого уровень важности не равняется NOTSET.

При стандартных параметрах, любой новый логгер будет иметь этот уровень. Так, как корневой логгер имеет уровень WARNING, эффективный уровень также будет таким.

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

foo_logger = logging.getLogger("foo")

assert foo_logger.level == logging.NOTSET # у нового логгера уровень NOTSET

assert foo_logger.getEffectiveLevel() == logging.WARNING # и его эффективный уровень не является уровнем корневого логгера

# прикрепите консольный обработчик к foo_logger

console_handler = logging.StreamHandler()

foo_logger.addHandler(console_handler)

foo_logger.debug("debug level") # ничего не отображается, так как уровень лога DEBUG меньше чем эффективный уровень foo 

foo_logger.setLevel(logging.DEBUG)

foo_logger.debug("debug message") # теперь вы увидите на экране "debug message"

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

Советы по работе с этим модулем

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

  1. Никогда не используйте корневой логгер в своей программе, несмотря на то, что его нужно настроить. Никогда не вызывайте функцию, подобную logging.info, оставляющую корневой логгер без дела. Если необходимо убрать информацию об ошибках из библиотек, которые используются, необходимо осуществить настройку корневого лога для записи в файл. Это полезно в случаях, когда отладка слишком сложная, и нужно ее упростить. Стандартные настройки установлены так, что корневой логгер выводит исключительно на stderr. Следовательно, можно легко потерять журнал. 
  2. Для использования логгирования необходимо сгенерировать новый логгер, используя logging.getLogger(имя логгера).
  3. Рекомендуется применять классы RotatingFileHandler вместо FileHandler, поскольку последний после достижения максимального размера изменяет лог. В большинстве случаев в этом нет необходимости.
  4. Рекомендуется пользоваться специализированными инструментами для того, чтобы ловить сообщения об ошибке. Это такие, как Sentry, Airbrake, Raygun и другие. Они хороши тем, что это позволяет упростить чтение лога. Кроме этого, эти инструменты дают возможность получить дополнительные данные о значениях переменных, связанных с возникновением исключения.
ОфисГуру
Adblock
detector