"""
Глобальная обработка ошибок
"""
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"🐛 Ошибка отладки:\n\n"
error_text += f"{type(error).__name__}: {str(error)}"
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"🐛 Ошибка отладки:\n\n"
error_text += f"{type(error).__name__}: {str(error)}"
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"🚨 Ошибка в боте\n\n"
error_text += f"🐛 Тип: {type(error).__name__}\n"
error_text += f"📝 Сообщение: {str(error)}\n"
error_text += f"👤 Пользователь: {message.from_user.id}\n"
error_text += f"💬 Чат: {message.chat.id}\n"
error_text += f"📅 Время: {message.date.strftime('%d.%m.%Y %H:%M:%S')}\n\n"
if config.DEBUG:
error_text += f"🔍 Трассировка:\n{traceback.format_exc()}"
# Отправляем уведомление всем администраторам
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}")