Один из способов облегчить разработку Telegram-бота — логирование

Продолжаю делиться своим небольшим опытом с такими же, как и я начинающими разработчиками Telegram-ботов на Python.

Один из способов облегчить разработку Telegram-бота — логирование

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

Для начала создадим отдельный модуль log.py (не называйте модуль logging, так как называть модуль аналогично библиотеке приводит к не очевидной ошибке).

import logging from logging.handlers import TimedRotatingFileHandler import os from datetime import time logger = logging.getLogger() logger.setLevel(logging.WARNING) def start_logging(): if not os.path.exists('logs'): os.makedirs('logs') handler = TimedRotatingFileHandler( filename='logs/log', when='H', interval=1, atTime=time(1, 0), backupCount=5, encoding='utf-8' ) handler.suffix = '%Y-%m-%d.log' handler.setLevel(logging.WARNING) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler)

Для начала рекомендую выставлять уровень логирования DEBUG, так как будут записываться все действия бота, вот такая иерархия:

  • Debug — самый низкий уровень логирования, предназначенный для отладки.
  • Info — этот уровень предназначен для вывода данных о фрагментах кода, работающих так, как ожидается.
  • Warning — этот уровень логирования предусматривает вывод предупреждений, он применяется для записи сведений о событиях, на которые программист обычно обращает внимание. Такие события вполне могут привести к проблемам при работе приложения. Если явно не задать уровень логирования - по умолчанию используется именно warning.
  • Error — этот уровень логирования предусматривает вывод сведений об ошибках - о том, что часть приложения работает не так как ожидается, о том, что программа не смогла правильно выполниться.
  • Critical — этот уровень используется для вывода сведений об очень серьёзных ошибках, наличие которых угрожает нормальному функционированию всего приложения. Если не исправить такую ошибку - это может привести к тому, что приложение прекратит работу.

Я использую стандартный уровень WARNING. Так же я указал папку куда сохранять логи и настроил создание нового файла каждый день для удобства чтения.

Теперь я могу добавить запись нужных мне событий в лог с помощью конструкции try-except:

async def get_twitch_token(): try: response = requests.post( f"https://id.twitch.tv/oauth2/token?client_id={twitch_id}&client_secret={twitch_key}&grant_type=client_credentials" ) response.raise_for_status() return (response.json())["access_token"] except Exception as e: log.logger.exception(f"Ошибка в функции get_twitch_token: {e}") return None

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

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

async def log_message(sender, user_id, message): current_time = datetime.datetime.now() formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S") log_entry = f"{formatted_time}: {sender} - {message}" log_folder = "message_history" # Создаем папку, если она не существует if not os.path.exists(log_folder): os.makedirs(log_folder) # Создайте файл или откройте существующий для записи log_file_path = os.path.join(log_folder, f"user_{user_id}_chat_log.txt") with open(log_file_path, "a", encoding="utf-8") as log_file: log_file.write(log_entry + "\n")

Для удобства пусть название файла будет user_id пользователя с которым идёт чат, а сами файлы сохраняются в отдельную папку. В итоге имеем файл "user_116918991_chat_log.txt"

Теперь нам нужно вызвать эту функцию в той части кода, где нам необходимо записать какое-либо сообщение:

@dp.message_handler(commands='start') async def send_start(message: types.Message, user_id=None): # Принимаем user_id как аргумент user_id = user_id or message.from_user.id # Если user_id не передан, используем его из сообщения add_and_follow = InlineKeyboardMarkup(row_width=1).add(keyboard.add_channel, keyboard.send_follow) text = f"🤖 Не пропусти стримы на Twitch!" \ f"\n\n📺Я буду следить за твоими любимыми каналами и уведомлю тебя о начале стримов. 🎮🔔" \ f"\n\nПолучай актуальную информацию о названии, категории и продолжительности стрима прямо в уведомлениях!" \ f"Вся информация обновляется в реальном времени. 💥" await message.answer(text, reply_markup=add_and_follow) await log_message("System", user_id, "Отправлена команда /start") await log_message("Bot", user_id, text)
  • await log_message("System", user_id, "Отправлена команда /start") — тут я записываю системное сообщение, а именно какое именно действие спровоцировало бота отправить сообщение.
  • await log_message("Bot", user_id, text) — тут я уже записываю непосредственно сообщение отправленное ботом.

2023-09-18 20:28:32: System - Отправлена команда /start
2023-09-18 20:28:32: Bot - 🤖 Не пропусти стримы на Twitch!

📺Я буду следить за твоими любимыми каналами и уведомлю тебя о начале стримов. 🎮🔔

Получай актуальную информацию о названии, категории и продолжительности стрима прямо в уведомлениях!Вся информация обновляется в реальном времени. 💥

Таким образом необходимо вызвать функцию log_message везде где нам необходимо записать какое-либо действие бота или юзера.

Напомню, моего бота зовут TwitchNotifier, он уведомляет о начале стримов на Twitch. Недавно я реализовал возможность отправки уведомлений в telegram-каналы, о чём планирую рассказать в одном из следующих гайдов. Также он отправляет умные уведомления, которые в реальном времени отображают длительность стрима, название, категорию.

Один из способов облегчить разработку Telegram-бота — логирование
1515
8 комментариев

Кажется пора переписывать в нормальный вид своего бота, полезно. Спасибо.

1
Ответить

С подключением, лол.
А по факту возьми просто loguru

1
Ответить

У питонячего логгера рекомендуется не использовать f строки, а передавать через аргументы и ыорматировать через % или {}. Например logger.info("task %s completed", task_uuid)
В телеграм боте мб и не будет проблем с производительностью, но лучше соблюдать лучшие практики сразу

Ответить

И кстати не понял, зачем ты создаешь файл логов в log_message, когда при инициализации логгера у тя уже есть вывод в файл

Ответить