Логирование в Python

В переводе с английского log — это «журнал». Логирование — это ведение «бортового журнала», автоматическая запись событий в специальный файл или вывод таких записей в терминал. Логи — это дополнительная система мониторинга, и разработчик может настроить логирование в тех местах, за которыми нужно присматривать.

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

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

Всегда лучше работать через логи, а не выводить на печать через print().

Библиотека logging в Python

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

«Внимание, красный уровень!»

По умолчанию в библиотеке logging задано пять «уровней» сообщений, по степени важности. Обычно их бывает достаточно, но при необходимости можно создать свой уровень.

Запустите файл с таким кодом:

import logging

logging.debug('123')  # Когда нужна отладочная информация 
logging.info('Сообщение отправлено')  # Когда нужна дополнительная информация
logging.warning('Большая нагрузка, хелп')  # Когда что-то идёт не так, но работает
logging.error('Бот не смог отправить сообщение')  # Когда что-то сломалось
logging.critical('Всё упало! Зовите админа!1!111')  # Когда всё совсем плохо 

Логи будут выведены так:

УРОВЕНЬ_ВАЖНОСТИ:текущий_пользователь:сообщение

WARNING:root:Большая нагрузка, хелп
ERROR:root:Бот не смог отправить сообщение
CRITICAL:root:Всё упало! Зовите админа!1!111 

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

Разные уровни логирования нужны для сортировки сообщений по важности. Это облегчает поиск и работу с сообщениями.

DEBUG — уровень отладки: на этом уровне выводится всякая служебная информация: «Произошёл запуск функции», «переменная содержит такое-то значение». Это сообщения о том, что происходит в коде, информация для разработчика.

INFO — информация о текущих событиях: этот уровень применяют, если нужно убедиться, что всё идёт по плану: «Письмо отправлено», «Запись в базе создана».

WARNING — «тревожный звоночек»: проблемы нет, но есть что-то, что может привести к проблеме; что-то, на что следует обратить внимание.

ERROR — это ошибка: что-то работает не так, как нужно. Требуется вмешательство и исправление ошибки.

CRITICAL — случилось что-то совсем критичное: надо всё бросать и бежать к компьютеру; всё сломалось. Не очень часто используется на практике, обычно бывает достаточно ERROR.

Настройка логов

По умолчанию в терминал выводятся только наиболее важные логи, от уровня WARNING и выше: WARNING → ERROR → CRITICAL.

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

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

logging.basicConfig(level=logging.DEBUG)  

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

Без предварительной настройки логи записываются в таком формате:

УРОВЕНЬ ВАЖНОСТИ:текущий пользователь:сообщение 

Этот формат можно изменить: в метод basicConfig() передаётся параметр format, а в нём описывается содержимое лога:

logging.basicConfig(format='%(asctime)s, %(levelname)s, %(name)s, %(message)s') 

asctime — это время события,
levelname — уровень важности,
name — имя логера,
message — текст сообщения.

Для описания атрибутов используется «%-форматирование»: атрибут берётся в скобки, перед скобками ставится символ %, а после скобок указывают тип данных, например:

s — строка (string),
d — число (digit).

Помимо времени и уровня есть и другие полезные атрибуты для форматирования логов:

filename — имя файла, из которого отправлено сообщение в лог;
funcName — имя функции, из которой отправлено сообщение в лог;
lineno — номер строки в том файле, из которого отправлено сообщение в лог.

Полный список атрибутов приведён в официальной документации.

Сохранение логов в файл

Чтобы сохранять лог-сообщения в файл, нужно передать соответствующие параметры в метод basicConfig(), указав имя файла с расширением .log и режим записи:

logging.basicConfig(filename='main.log', filemode='w') 

Значения параметра filemode:

w — содержимое файла перезаписывается при каждом запуске программы;
x — создать файл и записывать логи в него; если файл с таким именем уже существует — будет ошибка;
a — дописывать новые логи в конец указанного файла.

Ротация логов

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

Когда размер первого файла достигнет установленного предела — будет создан следующий файл, а когда количество файлов дойдёт до заданного количества — начнёт перезаписываться самый первый файл. И так по кругу: это называется ротацией логов.

Логер — это такая коробка или корзина, в которую Python скидывает лог-сообщения. Логер обрабатывает эти сообщения тем способом, который для него установлен.

import logging
from logging.handlers import RotatingFileHandler

# здесь мы задаем глобальную конфигурацию для всех логеров
logging.basicConfig(
    level=logging.DEBUG,
    filename='program.log', 
    format='%(asctime)s, %(levelname)s, %(message)s, %(name)s'
)

# а тут настраиваем логгер для текущего файла .py
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = RotatingFileHandler('my_logger.log', maxBytes=50000000, backupCount=5)
logger.addHandler(handler) 

Логеров может быть несколько: обычно пишут отдельный логер для каждого пакета. Имя логерам традиционно дают по имени __name__ пакета, для которого он создан. Когда в проекте больше одного пакета — такая структура логирования упрощает работу.

Далее в коде создан handler и добавлен в логер. Handler — это диспетчер логов, он берёт готовые логи, переданные в логер и обрабатывает их нужным образом.

В листинге применён RotatingFileHandler, он управляет ротацией логов: добавляет новые записи в файл, следит за объёмом и количеством лог-файлов. В параметрах RotatingFileHandler указывается максимальный размер одного лог-файла и предельное количество таких файлов. Также в нём указывается путь и имя файлов, так что при использовании RotatingFileHandler не нужно создавать файл через basicConfig.

Увидеть логи можно не только в файле, куда они сохранены: можно читать их в реальном времени при выполнении программы:

tail -f main.log 

Логи из файла main.log будут выводиться в терминал, отображая самые последние события.

Логирование исключений

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

Самый простой способ логирования исключений:

try:
    42 / 0
except Exception as error:
    logging.error(error, exc_info=True) 

Без параметра exc_info в лог запишется только текст исключения. Существует более компактная запись, с помощью метода logging.exception():

try:
    42 / 0
except Exception:
    logging.exception() 

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



Вы можете оставить комментарий, или Трекбэк с вашего сайта.

Оставить комментарий