При росте проекта использовать функции print для сообщения о запуске Python-скрипта или его падение уже становится неудобно. В этом случае используйте логирование (logging). В этой статье мы расскажем, как лучше всего создавать логи в Python, когда их стоит применять, а также покажем основы библиотеки logging.
Что такое логирование и какими должны быть логи
Логирование — это способ записи информации о состоянии программы. Логами называют сами записи. Логи должны быть описательными, контекстными, реактивными [1]. Следовательно, они описывают, что произошло; они предоставляют информацию о текущем состоянии в момент открытия лога; они позволяют узнать, какие действия нужно предпринять, если это требуется. Поэтому в логах рекомендуется писать только подобную информацию, иначе рискуете создать только шум, запутав тем самым себя и коллег.
Например, лог вроде: “operation connect failed” — это шум. В нем есть описание, но нет контекста (ведь не ясно какое именно соединение и в какой момент произошла авария), а также нет информации о действии, которые нужно предпринять. Можно пойти ещё дальше и сделать лог таким: “An error happened”.
Когда создавать лог
Логи должны быть ясными и легкими для чтения. Пользователь может пропускать некоторые строчки, так как они понятны, и сосредотачивать внимание на необходимых ему аспектах. Например, логи оркестратора Apache Airflow информативны и объёмны, но вам быть может нужен только вывод, значения настроек конфигурации, время выполнения или появившаяся ошибка.
Логи должны быть подобны рассказу, у которого есть начало, кульминация и конец. Поэтому создавать их рекомендуется в следующие моменты:
- в начале выполнения операции (например, при соединении с внешней сетью и т.д.);
- после выполнения важных и релевантных операций (например, аутентификация выполнилась, код выполнения и т.д.);
- в конце выполнения операции: либо выполнилась, либо нет.
Создание логов в Python
Нам повезло, ведь в Python есть стандартная библиотека logging [2]. В нем есть специальные функции, которые названы в соответствии с уровнем или серьезностью событий. Эти уровни следующие:
DEBUG
— подробная информация, обычно интересующая только при диагностике проблем.INFO
— подтверждение того, что все работает должным образом.WARNING
— предупреждение, что произошло действие, которое не ждали, при этом программа все равно работает.ERROR
— ошибка, из-за которой программа не работает.CRITICAL
— серьезная ошибка, при которой программа не может продолжить работать.
Все эти уровни можно вызывать, используя соответствующие функции, например, logging.error
. Но лучше пользоваться объектом logger
(логер):
import logging logger = logging.getLogger(__name__) def myfunc(): ... logger.info("Something relevant happened") ...
Объект logger
предоставляет интерфейс для логирования. Также есть объекты handler
(обработчик), filter
(фильтр), formatter
(объект формата вывода). Обработчики отправляют записи логов в соответствующее место назначения, например, в стандартный поток ошибок (stderr) или в файл. Фильтры предоставляют более детальное средство для определения, какие записи логов нужно выводить, а объекты формата вывода каким образом (по какому шаблону) должны отображаться сами логи. Рассмотрим эти объекты подробнее.
Объекты logging
Обработчики отправляют сообщения журнала в места назначения, такие как стандартный выходной поток или айл, или через HTTP, или на вашу электронную почту через SMTP. Логер может иметь несколько обработчиков, поэтому логи могут быть и сохранены в файл, и отправлены на электронную почту.
Обработчики создаются в Python следующим образом:
s_handler = logging.StreamHandler() f_handler = logging.FileHandler('log') s_handler.setLevel(logging.WARNING) f_handler.setLevel(logging.ERROR)
Итак, у нас есть два обработчика: первый со статусом WARNING
записывает в стандартный поток, второй со статусом ERROR
в файл.
Для форматированного вида используется шаблоны сходные с теми, которые используются в языке Си: %s
— это строка, %d
— это целое число. Только между процентом и символом в скобках ставится имя параметра.
s_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s') f_format = logging.Formatter('%(name)s - %(asctime)s - %(message)s') s_handler.setFormatter(s_format) f_handler.setFormatter(f_format)
Вывод второго обработчика покажет ещё и время генерации записи лога через asctime
.
Осталось только добавить обработчики к нашему объекту logger
:
logger.addHandler(c_handler) logger.addHandler(f_handler)
Теперь, когда вы вызовите функцию warning
, то в стандартном выводе увидите лог заданного формата:
>>> logger.warning('This is a warning') __main__ - WARNING - This is a warning
А вот при вызове error
вы получите ещё и файл, в котором будет содержаться форматированный вывод:
>>> logger.error('This is an error') __main__ - ERROR - This is an error $ cat log 2021-09-17 16:36:59,684 - __main__ - ERROR - This is an error
Каждый раз, когда будет вызываться функция error
в файл будет добавляться соответствующая строчка. Используйте подобный прием при запуске своих Python-скриптов. Логи можно применять и в тестах, о них тут.
О грамотном создании логов и о том, как работать с Python для решения задач Data Science вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и ИТ-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве:
- FUNP: Основы языка Python для анализа данных и решения задач машинного обучения
- DPREP: Подготовка данных для Data Mining на Python
- PYML: Машинное обучение на Python