Эффективное логирование в Python
⏱ Время чтения текста – 5 минутВ Python существует встроенный модуль logging, который позволяет журналировать этапы выполнения программы. Логирование полезно когда, например, нужно оставить большой скрипт сбора / обработки данных на длительное время, а в случае возникновения непредвиденных ошибок выяснить, с чем они могут быть связаны. Анализ логов позволяет быстро и эффективно выявлять проблемные места в коде, но для удобного использования модуля следует написать несколько функций по взаимодействию с ним и вынести их в отдельный файл — сегодня мы этим и займёмся.
Пишем логгер
Создадим файл loggers.py. Для начала импортируем модули и задаём пару значений по умолчанию — директорию для файла с логом и наименование конфигурационного файла, содержащего шаблоны логирования. Его мы опишем следом.
import os
import json
import logging
import logging.config
FOLDER_LOG = "log"
LOGGING_CONFIG_FILE = 'loggers.json'
Опишем функцию для создания папки с логом: она принимает наименование для папки, но по умолчанию будет называть её «log». Директорию создаём при помощи модуля os и только в том случае, если такой директории ещё не существует.
def create_log_folder(folder=FOLDER_LOG):
if not os.path.exists(folder):
os.mkdir(folder)
Теперь опишем функцию создания нового логгера по заданному шаблону. Функция должна создать директорию для логирования, открыть конфигурационный файл и достать нужный шаблон. Затем по шаблону при помощи модуля logging создаём новый логгер:
def get_logger(name, template='default'):
create_log_folder()
with open(LOGGING_CONFIG_FILE, "r") as f:
dict_config = json.load(f)
dict_config["loggers"][name] = dict_config["loggers"][template]
logging.config.dictConfig(dict_config)
return logging.getLogger(name)
Для удобства опишем ещё одну функцию — получение стандартного лога. Она ничего не принимает и нужна только для инициализации лога с шаблоном default:
def get_default_logger():
create_log_folder()
with open(LOGGING_CONFIG_FILE, "r") as f:
logging.config.dictConfig(json.load(f))
return logging.getLogger("default")
Описываем конфигурационный файл
Создадим по соседству файл loggers.json — он будет содержать настройки логгера. Внутри указываем такие настройки, как версию логгера, форматы логирования для разных уровней, наименование выходного файла и его максимальный размер:
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"default": {
"format": "%(asctime)s - %(processName)-10s - %(name)-10s - %(levelname)-8s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "default"
},
"rotating_file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "default",
"filename": "log/main.log",
"maxBytes": 10485760,
"backupCount": 20
}
},
"loggers": {
"default": {
"handlers": ["console", "rotating_file"],
"level": "DEBUG"
}
}
}
Использование логгера
Теперь давайте представим, что вы выгружаете данные по API и складываете их в базу данных на примере нашего материала про транзакции в SQLAlchemy. Рассмотрим заключительную часть кода: добавим строку с инициализацией стандартного логгера и изменим код так, чтобы сначала в лог выводился offset, затем в случае успеха предложение «Successfully inserted data», а в случае ошибки выводилась сама ошибка и предложение: «Error: tried to insert data but got an error».
logger = get_logger('main')
offset = 0
subs_count = get_subs_count(group_id)
while offset < subs_count:
with engine.connect() as conn:
transaction = conn.begin()
try:
logger.info(f"{offset} / {subs_count}")
df = get_subs_info(group_id, offset)
df.to_sql('subscribers', con=conn, if_exists='append', index=False)
if offset == 10:
raise(ValueError("This is a test errror"))
transaction.commit()
logger.info(f"Successfully inserted data")
except Exception as E:
transaction.rollback()
logger.error(f"Error: tried to insert {df} but got an error: {E}")
time.sleep(1)
offset += 10
Теперь во время работы программы будет отображаться такой вывод, который также будет записан в файл main.log папки log в директории проекта. После завершения работы программы можно исследовать логи, посмотреть, на каких offset возникли проблемы, какие данные не удалось вставить и прочитать текст ошибки:
