From 650acd5bce627c8eb079bfaf49980bc0ff94c7e6 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 3 Sep 2025 17:35:51 +0300 Subject: [PATCH] Add cancel ban process handler in admin handlers - Introduced a new handler for canceling the ban process in `admin_handlers.py`, allowing users to easily abort ongoing ban actions. - Enhanced error handling and logging for the cancellation process to improve user experience and debugging. - Removed obsolete comments and cleaned up the code for better readability in the admin handlers. - Updated metrics middleware to streamline event processing and improve logging clarity, ensuring comprehensive metrics collection for all events. --- helper_bot/handlers/admin/admin_handlers.py | 101 +++-------- helper_bot/middlewares/metrics_middleware.py | 175 ++++--------------- 2 files changed, 57 insertions(+), 219 deletions(-) diff --git a/helper_bot/handlers/admin/admin_handlers.py b/helper_bot/handlers/admin/admin_handlers.py index 52c3fe8..9f16fbd 100644 --- a/helper_bot/handlers/admin/admin_handlers.py +++ b/helper_bot/handlers/admin/admin_handlers.py @@ -53,6 +53,29 @@ async def admin_panel( await handle_admin_error(message, e, state, "admin_panel") +# ============================================================================ +# ХЕНДЛЕР ОТМЕНЫ +# ============================================================================ + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"), + F.text == 'Отменить' +) +async def cancel_ban_process( + message: types.Message, + state: FSMContext, + **kwargs + ): + """Отмена процесса блокировки""" + try: + current_state = await state.get_state() + logger.info(f"Отмена процедуры блокировки из состояния: {current_state}") + await return_to_admin_menu(message, state) + except Exception as e: + await handle_admin_error(message, e, state, "cancel_ban_process") + + @admin_router.message( ChatTypeFilter(chat_type=["private"]), StateFilter("ADMIN"), @@ -321,81 +344,3 @@ async def confirm_ban( await return_to_admin_menu(message, state) except Exception as e: await handle_admin_error(message, e, state, "confirm_ban") - - -# ============================================================================ -# ХЕНДЛЕРЫ ОТМЕНЫ И НАВИГАЦИИ -# ============================================================================ - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"), - F.text == 'Отменить' -) -async def cancel_ban_process( - message: types.Message, - state: FSMContext, - **kwargs - ): - """Отмена процесса блокировки""" - try: - current_state = await state.get_state() - logger.info(f"Отмена процедуры блокировки из состояния: {current_state}") - await return_to_admin_menu(message, state) - except Exception as e: - await handle_admin_error(message, e, state, "cancel_ban_process") - - -@admin_router.message(Command("test_metrics")) -async def test_metrics_handler( - message: types.Message, - bot_db: MagicData("bot_db"), - **kwargs -): - """Тестовый хендлер для проверки метрик""" - from helper_bot.utils.metrics import metrics - - try: - # Принудительно записываем тестовые метрики - metrics.record_command("test_metrics", "admin_handler", "admin", "success") - metrics.record_message("text", "private", "admin_handler") - metrics.record_error("TestError", "admin_handler", "test_metrics_handler") - - # Проверяем активных пользователей - if hasattr(bot_db, 'connect') and hasattr(bot_db, 'cursor'): - # Используем UNIX timestamp для сравнения с date_changed - import time - current_timestamp = int(time.time()) - one_day_ago = current_timestamp - (24 * 60 * 60) # 24 часа назад - - active_users_query = """ - SELECT COUNT(DISTINCT user_id) as active_users - FROM our_users - WHERE date_changed > ? - """ - try: - await bot_db.connect() - await bot_db.cursor.execute(active_users_query, (one_day_ago,)) - result = await bot_db.cursor.fetchone() - active_users = result[0] if result else 0 - finally: - await bot_db.close() - else: - active_users = "N/A" - - # ВАЖНО: Записываем метрику активных пользователей - if isinstance(active_users, int): - metrics.set_active_users(active_users, "daily") - metrics.set_active_users(active_users, "total") - - await message.answer( - f"✅ Тестовые метрики записаны\n" - f"📊 Активных пользователей: {active_users}\n" - f"🔧 Проверьте Prometheus метрики\n" - f"📈 Метрика active_users обновлена" - ) - - except Exception as e: - await message.answer(f"❌ Ошибка тестирования метрик: {e}") - # Записываем ошибку в метрики - metrics.record_error(str(type(e).__name__), "admin_handler", "test_metrics_handler") diff --git a/helper_bot/middlewares/metrics_middleware.py b/helper_bot/middlewares/metrics_middleware.py index d1ea0b0..291d893 100644 --- a/helper_bot/middlewares/metrics_middleware.py +++ b/helper_bot/middlewares/metrics_middleware.py @@ -22,11 +22,7 @@ try: COMMAND_MAPPING as VOICE_COMMAND_MAPPING, CALLBACK_COMMAND_MAPPING as VOICE_CALLBACK_COMMAND_MAPPING ) - print(f"✅ Constants imported successfully: ADMIN_COMMANDS={len(ADMIN_COMMANDS)}, BUTTON_COMMAND_MAPPING={len(BUTTON_COMMAND_MAPPING)}") - print(f"✅ ADMIN_COMMANDS: {list(ADMIN_COMMANDS.keys())}") - print(f"✅ BUTTON_COMMAND_MAPPING: {list(BUTTON_COMMAND_MAPPING.keys())}") -except ImportError as e: - print(f"❌ Failed to import constants: {e}") +except ImportError: # Fallback if constants not available BUTTON_COMMAND_MAPPING = {} CALLBACK_COMMAND_MAPPING = {} @@ -35,7 +31,6 @@ except ImportError as e: VOICE_BUTTON_COMMAND_MAPPING = {} VOICE_COMMAND_MAPPING = {} VOICE_CALLBACK_COMMAND_MAPPING = {} - print("⚠️ Using empty constants fallback") class MetricsMiddleware(BaseMiddleware): @@ -44,16 +39,10 @@ class MetricsMiddleware(BaseMiddleware): def __init__(self): super().__init__() self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.DEBUG) # Enable debug logging for metrics tracking # Metrics update intervals self.last_active_users_update = 0 self.active_users_update_interval = 300 # 5 minutes - self.last_middleware_metrics_update = 0 - self.middleware_metrics_interval = 60 # 1 minute - - # Middleware performance tracking - self.middleware_start_time = None async def __call__( self, @@ -62,70 +51,47 @@ class MetricsMiddleware(BaseMiddleware): data: Dict[str, Any] ) -> Any: """Process event and collect comprehensive metrics.""" - - # Simple print to verify middleware is called - print("=" * 50) - print("🔍 MetricsMiddleware CALLED!") - print(f"🔍 Event type: {type(event).__name__}") - print(f"🔍 Event: {event}") - print(f"🔍 Handler: {handler}") - print("=" * 50) - - # Start middleware timing - middleware_start = time.time() - + # Update active users periodically current_time = time.time() if current_time - self.last_active_users_update > self.active_users_update_interval: await self._update_active_users_metric() self.last_active_users_update = current_time - # Extract comprehensive command and event info + # Extract command and event info command_info = None event_metrics = {} - - # Debug: Log event type and structure - self.logger.info(f"📊 Event type: {type(event).__name__}") - self.logger.info(f"📊 Event: {event}") - - # Correct order for aiogram 3.x: first check Update structure, then fallback to direct types + + # Process event based on type if hasattr(event, 'message') and event.message: - # Handle aiogram 3.x Update.message - self.logger.info(f"📊 Processing Update.message event: {event.message.text[:50] if event.message.text else 'No text'}") event_metrics = await self._record_comprehensive_message_metrics(event.message) command_info = self._extract_command_info_with_fallback(event.message) elif hasattr(event, 'callback_query') and event.callback_query: - # Handle aiogram 3.x Update.callback_query - self.logger.info(f"📊 Processing Update.callback_query event: {event.callback_query.data[:50] if event.callback_query.data else 'No data'}") event_metrics = await self._record_comprehensive_callback_metrics(event.callback_query) command_info = self._extract_callback_command_info_with_fallback(event.callback_query) elif isinstance(event, Message): - # Fallback for direct Message objects (if they exist) - self.logger.info(f"📊 Processing direct Message event: {event.text[:50] if event.text else 'No text'}") event_metrics = await self._record_comprehensive_message_metrics(event) command_info = self._extract_command_info_with_fallback(event) elif isinstance(event, CallbackQuery): - # Fallback for direct CallbackQuery objects (if they exist) - self.logger.info(f"📊 Processing direct CallbackQuery event: {event.data[:50] if event.data else 'No data'}") event_metrics = await self._record_comprehensive_callback_metrics(event) command_info = self._extract_callback_command_info_with_fallback(event) else: - # Unknown event type - log for debugging - self.logger.info(f"📊 Processing unknown event type: {type(event).__name__}") - self.logger.info(f"📊 Event attributes: {[attr for attr in dir(event) if not attr.startswith('_')]}") event_metrics = await self._record_unknown_event_metrics(event) + if command_info: + self.logger.info(f"📊 Command info extracted: {command_info}") + else: + self.logger.warning(f"📊 No command info extracted for event type: {type(event).__name__}") + # Execute handler with comprehensive timing and metrics start_time = time.time() try: result = await handler(event, data) duration = time.time() - start_time - # Record comprehensive successful execution metrics + # Record successful execution metrics handler_name = self._get_handler_name(handler) - self.logger.debug(f"📊 Recording successful execution: {handler_name} ({duration:.3f}s)") - # Record method duration metrics.record_method_duration( handler_name, duration, @@ -133,7 +99,6 @@ class MetricsMiddleware(BaseMiddleware): "success" ) - # Record command metrics if available if command_info: metrics.record_command( command_info['command'], @@ -142,7 +107,6 @@ class MetricsMiddleware(BaseMiddleware): "success" ) - # Record additional event metrics await self._record_additional_success_metrics(event, event_metrics, handler_name) return result @@ -150,12 +114,10 @@ class MetricsMiddleware(BaseMiddleware): except Exception as e: duration = time.time() - start_time - # Record comprehensive error metrics + # Record error metrics handler_name = self._get_handler_name(handler) error_type = type(e).__name__ - self.logger.debug(f"📊 Recording error execution: {handler_name}, error: {error_type} ({duration:.3f}s)") - # Record method duration with error metrics.record_method_duration( handler_name, duration, @@ -163,14 +125,12 @@ class MetricsMiddleware(BaseMiddleware): "error" ) - # Record error metrics.record_error( error_type, "handler", handler_name ) - # Record command with error status if applicable if command_info: metrics.record_command( command_info['command'], @@ -179,96 +139,49 @@ class MetricsMiddleware(BaseMiddleware): "error" ) - # Record additional error metrics await self._record_additional_error_metrics(event, event_metrics, handler_name, error_type) raise finally: # Record middleware execution time - middleware_duration = time.time() - middleware_start + middleware_duration = time.time() - start_time metrics.record_middleware("MetricsMiddleware", middleware_duration, "success") async def _update_active_users_metric(self): """Periodically update active users metric from database.""" try: - self.logger.debug("📊 Updating active users metric...") - - # Get database instance + #TODO: Должна подключаться к базе данных, а не к глобальному экземпляру from ..utils.base_dependency_factory import get_global_instance bdf = get_global_instance() bot_db = bdf.get_db() - # Count active users (last 24 hours) - date_changed is INTEGER (UNIX timestamp) - import time - current_timestamp = int(time.time()) - one_day_ago = current_timestamp - (24 * 60 * 60) - - # First, let's check if table exists and has data - check_table_query = """ - SELECT name FROM sqlite_master - WHERE type='table' AND name='our_users' - """ - await bot_db.connect() - await bot_db.cursor.execute(check_table_query) - table_exists = await bot_db.cursor.fetchone() - if not table_exists: - self.logger.warning("⚠️ Table 'our_users' does not exist") - metrics.set_active_users(1, "daily") # Fallback to 1 - metrics.set_active_users(1, "total") - await bot_db.close() - return - - # Check total users first - total_users_query = "SELECT COUNT(*) FROM our_users" + # Простой подсчет всех пользователей в базе + total_users_query = "SELECT COUNT(DISTINCT user_id) FROM our_users" await bot_db.cursor.execute(total_users_query) total_users_result = await bot_db.cursor.fetchone() - total_users = total_users_result[0] if total_users_result else 0 - - self.logger.debug(f"📊 Total users in database: {total_users}") - - if total_users == 0: - self.logger.warning("⚠️ No users in database") - metrics.set_active_users(1, "daily") # Fallback to 1 - metrics.set_active_users(1, "total") - await bot_db.close() - return - - # Now count active users (last 24 hours) - active_users_query = """ - SELECT COUNT(DISTINCT user_id) as active_users - FROM our_users - WHERE date_changed > ? - """ - - await bot_db.cursor.execute(active_users_query, (one_day_ago,)) - result = await bot_db.cursor.fetchone() - active_users = result[0] if result else 0 - - # If no active users in last 24h, use total users as fallback - if active_users == 0: - self.logger.warning("⚠️ No active users in last 24h, using total users as fallback") - active_users = total_users + total_users = total_users_result[0] if total_users_result else 1 + daily_users_query = "SELECT COUNT(DISTINCT user_id) as active_users FROM our_users WHERE date_changed > datetime('now', '-1 day'))" + await bot_db.cursor.execute(daily_users_query) + daily_users_result = await bot_db.cursor.fetchone() + daily_users = daily_users_result[0] if daily_users_result else 1 await bot_db.close() - # Update metrics - metrics.set_active_users(active_users, "daily") + metrics.set_active_users(daily_users, "daily") metrics.set_active_users(total_users, "total") + self.logger.info(f"📊 Active users metric updated: {daily_users} (daily), {total_users} (total)") - self.logger.debug(f"📊 Active users updated: daily={active_users}, total={total_users}") except Exception as e: - self.logger.error(f"❌ Failed to update active users metric: {e}") - # Set fallback values + self.logger.error(f"❌ Failed to update users metric: {e}") + # Устанавливаем 1 как fallback metrics.set_active_users(1, "daily") metrics.set_active_users(1, "total") - + async def _record_comprehensive_message_metrics(self, message: Message) -> Dict[str, Any]: """Record comprehensive message metrics.""" - metrics_data = {} - # Determine message type message_type = "text" if message.photo: @@ -298,46 +211,33 @@ class MetricsMiddleware(BaseMiddleware): # Record message processing metrics.record_message(message_type, chat_type, "message_handler") - # Store metrics data for later use - metrics_data.update({ + return { 'message_type': message_type, 'chat_type': chat_type, 'user_id': message.from_user.id if message.from_user else None, 'is_bot': message.from_user.is_bot if message.from_user else False - }) - - return metrics_data + } async def _record_comprehensive_callback_metrics(self, callback: CallbackQuery) -> Dict[str, Any]: """Record comprehensive callback metrics.""" - metrics_data = {} - # Record callback message metrics.record_message("callback_query", "callback", "callback_handler") - # Store metrics data for later use - metrics_data.update({ + return { 'callback_data': callback.data, 'user_id': callback.from_user.id if callback.from_user else None, 'is_bot': callback.from_user.is_bot if callback.from_user else False - }) - - return metrics_data + } async def _record_unknown_event_metrics(self, event: TelegramObject) -> Dict[str, Any]: """Record metrics for unknown event types.""" - metrics_data = {} - # Record unknown event metrics.record_message("unknown", "unknown", "unknown_handler") - # Store event type for later use - metrics_data.update({ + return { 'event_type': type(event).__name__, 'event_data': str(event)[:100] if hasattr(event, '__str__') else "unknown" - }) - - return metrics_data + } def _extract_command_info_with_fallback(self, message: Message) -> Optional[Dict[str, str]]: """Extract command information with fallback for unknown commands.""" @@ -364,7 +264,6 @@ class MetricsMiddleware(BaseMiddleware): } else: # FALLBACK: Record unknown command - self.logger.debug(f"📊 Unknown command detected: /{command_name}") return { 'command': command_name, 'user_type': "user" if message.from_user else "unknown", @@ -397,13 +296,8 @@ class MetricsMiddleware(BaseMiddleware): # FALLBACK: Record ANY text message as a command for metrics if message.text and len(message.text.strip()) > 0: - # Clean text for command name (remove special chars, limit length) - clean_text = message.text.strip()[:30].replace(' ', '_').replace('\n', '_') - clean_text = ''.join(c for c in clean_text if c.isalnum() or c in '_') - - self.logger.debug(f"📊 Text message recorded as command: {clean_text}") return { - 'command': f"text_{clean_text}", + 'command': f"text", 'user_type': "user" if message.from_user else "unknown", 'handler_type': "text_message_handler" } @@ -434,7 +328,6 @@ class MetricsMiddleware(BaseMiddleware): # FALLBACK: Record unknown callback if parts: - self.logger.debug(f"📊 Unknown callback detected: {parts[0]}") return { 'command': f"callback_{parts[0][:20]}", 'user_type': "user" if callback.from_user else "unknown", @@ -472,7 +365,7 @@ class MetricsMiddleware(BaseMiddleware): def _get_handler_name(self, handler: Callable) -> str: """Extract handler name efficiently.""" - # Проверяем различные способы получения имени хендлера + # Check various ways to get handler name if hasattr(handler, '__name__') and handler.__name__ != '': return handler.__name__ elif hasattr(handler, '__qualname__') and handler.__qualname__ != '':