218 lines
10 KiB
Python
218 lines
10 KiB
Python
"""
|
||
Глобальная обработка ошибок
|
||
"""
|
||
import traceback
|
||
from aiogram import Router
|
||
from aiogram.types import ErrorEvent, Message, CallbackQuery
|
||
from aiogram.exceptions import TelegramBadRequest, TelegramNetworkError, TelegramRetryAfter
|
||
|
||
from config import config
|
||
from services.infrastructure.logger import get_logger
|
||
from services.infrastructure.metrics import get_metrics_service
|
||
from keyboards.reply import get_main_keyboard_for_user
|
||
|
||
logger = get_logger(__name__)
|
||
router = Router()
|
||
|
||
|
||
@router.error()
|
||
async def error_handler(event: ErrorEvent):
|
||
"""Глобальный обработчик ошибок"""
|
||
error = event.exception
|
||
update = event.update
|
||
|
||
# Записываем метрику ошибки
|
||
metrics_service = get_metrics_service()
|
||
metrics_service.increment_errors(type(error).__name__, "global_handler")
|
||
|
||
# Логируем ошибку
|
||
logger.error(f"💥 Ошибка в обработчике: {error}")
|
||
logger.error(f"🔍 Тип ошибки: {type(error).__name__}")
|
||
logger.error(f"📋 Детали: {traceback.format_exc()}")
|
||
|
||
# Определяем тип обновления
|
||
if update.message:
|
||
await handle_message_error(update.message, error)
|
||
elif update.callback_query:
|
||
await handle_callback_error(update.callback_query, error)
|
||
else:
|
||
logger.error(f"❓ Неизвестный тип обновления: {update}")
|
||
|
||
|
||
async def handle_message_error(message: Message, error: Exception):
|
||
"""Обработка ошибок в сообщениях"""
|
||
try:
|
||
# Определяем тип ошибки и отправляем соответствующее сообщение
|
||
if isinstance(error, TelegramRetryAfter):
|
||
# Ошибка rate limiting
|
||
await message.answer(
|
||
f"⏳ Слишком много запросов. Попробуйте через {error.retry_after} секунд.",
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||
)
|
||
elif isinstance(error, TelegramBadRequest):
|
||
# Некорректный запрос
|
||
if "message is not modified" in str(error):
|
||
# Сообщение не изменилось - это не критическая ошибка
|
||
logger.warning("Сообщение не изменилось")
|
||
elif "chat not found" in str(error):
|
||
# Чат не найден
|
||
logger.warning(f"Чат не найден: {message.chat.id}")
|
||
else:
|
||
await message.answer(
|
||
"❌ Произошла ошибка при обработке запроса. Попробуйте позже.",
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||
)
|
||
elif isinstance(error, TelegramNetworkError):
|
||
# Сетевая ошибка
|
||
await message.answer(
|
||
"🌐 Проблемы с сетью. Проверьте подключение к интернету.",
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||
)
|
||
elif isinstance(error, ValueError):
|
||
# Ошибка валидации
|
||
await message.answer(
|
||
"❌ Некорректные данные. Проверьте введенную информацию.",
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||
)
|
||
elif isinstance(error, KeyError):
|
||
# Ошибка ключа (обычно в FSM)
|
||
await message.answer(
|
||
"❌ Ошибка состояния. Попробуйте начать заново с команды /start.",
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||
)
|
||
else:
|
||
# Неизвестная ошибка
|
||
if config.DEBUG:
|
||
# В режиме отладки показываем детали ошибки
|
||
error_text = f"🐛 <b>Ошибка отладки:</b>\n\n"
|
||
error_text += f"<code>{type(error).__name__}: {str(error)}</code>"
|
||
|
||
await message.answer(
|
||
error_text,
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id),
|
||
parse_mode="HTML"
|
||
)
|
||
else:
|
||
# В продакшене показываем общее сообщение
|
||
await message.answer(
|
||
"❌ Произошла неожиданная ошибка. Попробуйте позже или обратитесь к администратору.",
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||
)
|
||
|
||
# Уведомляем администраторов о критических ошибках
|
||
if not isinstance(error, (TelegramRetryAfter, TelegramBadRequest)):
|
||
await notify_admins_about_error(error, message)
|
||
|
||
except Exception as e:
|
||
# Если даже обработка ошибки не удалась
|
||
logger.critical(f"Критическая ошибка в обработчике ошибок: {e}")
|
||
try:
|
||
await message.answer(
|
||
"❌ Критическая ошибка. Бот временно недоступен.",
|
||
reply_markup=get_main_keyboard_for_user(message.from_user.id)
|
||
)
|
||
except:
|
||
pass # Если даже это не удалось, ничего не делаем
|
||
|
||
|
||
async def handle_callback_error(callback: CallbackQuery, error: Exception):
|
||
"""Обработка ошибок в callback запросах"""
|
||
try:
|
||
# Определяем тип ошибки и отправляем соответствующее сообщение
|
||
if isinstance(error, TelegramRetryAfter):
|
||
# Ошибка rate limiting
|
||
await callback.answer(
|
||
f"⏳ Слишком много запросов. Попробуйте через {error.retry_after} секунд.",
|
||
show_alert=True
|
||
)
|
||
elif isinstance(error, TelegramBadRequest):
|
||
# Некорректный запрос
|
||
if "message is not modified" in str(error):
|
||
# Сообщение не изменилось - это не критическая ошибка
|
||
logger.warning("Сообщение не изменилось в callback")
|
||
elif "query is too old" in str(error):
|
||
# Запрос слишком старый
|
||
await callback.answer(
|
||
"⏰ Запрос устарел. Обновите страницу и попробуйте снова.",
|
||
show_alert=True
|
||
)
|
||
else:
|
||
await callback.answer(
|
||
"❌ Произошла ошибка при обработке запроса.",
|
||
show_alert=True
|
||
)
|
||
elif isinstance(error, TelegramNetworkError):
|
||
# Сетевая ошибка
|
||
await callback.answer(
|
||
"🌐 Проблемы с сетью. Проверьте подключение.",
|
||
show_alert=True
|
||
)
|
||
else:
|
||
# Неизвестная ошибка
|
||
if config.DEBUG:
|
||
# В режиме отладки показываем детали ошибки
|
||
error_text = f"🐛 <b>Ошибка отладки:</b>\n\n"
|
||
error_text += f"<code>{type(error).__name__}: {str(error)}</code>"
|
||
|
||
await callback.message.edit_text(
|
||
error_text,
|
||
parse_mode="HTML"
|
||
)
|
||
else:
|
||
# В продакшене показываем общее сообщение
|
||
await callback.answer(
|
||
"❌ Произошла неожиданная ошибка.",
|
||
show_alert=True
|
||
)
|
||
|
||
# Уведомляем администраторов о критических ошибках
|
||
if not isinstance(error, (TelegramRetryAfter, TelegramBadRequest)):
|
||
await notify_admins_about_error(error, callback.message)
|
||
|
||
except Exception as e:
|
||
# Если даже обработка ошибки не удалась
|
||
logger.critical(f"Критическая ошибка в обработчике callback ошибок: {e}")
|
||
try:
|
||
await callback.answer(
|
||
"❌ Критическая ошибка.",
|
||
show_alert=True
|
||
)
|
||
except:
|
||
pass # Если даже это не удалось, ничего не делаем
|
||
|
||
|
||
async def notify_admins_about_error(error: Exception, message: Message):
|
||
"""Уведомление администраторов об ошибке"""
|
||
if not config.ADMINS:
|
||
return
|
||
|
||
try:
|
||
error_text = f"🚨 <b>Ошибка в боте</b>\n\n"
|
||
error_text += f"🐛 <b>Тип:</b> {type(error).__name__}\n"
|
||
error_text += f"📝 <b>Сообщение:</b> {str(error)}\n"
|
||
error_text += f"👤 <b>Пользователь:</b> {message.from_user.id}\n"
|
||
error_text += f"💬 <b>Чат:</b> {message.chat.id}\n"
|
||
error_text += f"📅 <b>Время:</b> {message.date.strftime('%d.%m.%Y %H:%M:%S')}\n\n"
|
||
|
||
if config.DEBUG:
|
||
error_text += f"🔍 <b>Трассировка:</b>\n<code>{traceback.format_exc()}</code>"
|
||
|
||
# Отправляем уведомление всем администраторам
|
||
from dependencies import get_message_service
|
||
message_service = get_message_service()
|
||
|
||
for admin_id in config.ADMINS:
|
||
try:
|
||
await message_service.send_bot_message(
|
||
message.bot,
|
||
admin_id,
|
||
error_text
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Не удалось отправить уведомление админу {admin_id}: {e}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при уведомлении администраторов: {e}")
|
||
|
||
|