diff --git a/.gitignore b/.gitignore index 734ea09..39feebe 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /database/tg-bot-database.db /database/tg-bot-database.db-shm /database/tg-bot-database.db-wm +/database/tg-bot-database.db-wal /database/test.db /database/test.db-shm /database/test.db-wal diff --git a/Makefile b/Makefile index cfaf346..2314fb2 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,19 @@ restart: ## Перезапустить все сервисы (с пересбо docker-compose build docker-compose up -d +restart-bot: ## Перезапустить только бота + docker-compose stop telegram-bot + docker-compose build telegram-bot + docker-compose up -d telegram-bot + +restart-prometheus: ## Перезапустить только Prometheus + docker-compose stop prometheus + docker-compose up -d prometheus + +restart-grafana: ## Перезапустить только Grafana + docker-compose stop grafana + docker-compose up -d grafana + status: ## Показать статус контейнеров docker-compose ps diff --git a/grafana/dashboards/telegram-bot-dashboard.json b/grafana/dashboards/telegram-bot-dashboard.json index 0533ea8..951311a 100644 --- a/grafana/dashboards/telegram-bot-dashboard.json +++ b/grafana/dashboards/telegram-bot-dashboard.json @@ -284,7 +284,7 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "rate(errors_total[5m])", + "expr": "sum(rate(errors_total[5m]))", "refId": "A" } ], @@ -371,7 +371,7 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "expr": "active_users", + "expr": "sum(active_users)", "refId": "A" } ], diff --git a/helper_bot/examples/metrics_usage_examples.py b/helper_bot/examples/metrics_usage_examples.py deleted file mode 100644 index b705ba8..0000000 --- a/helper_bot/examples/metrics_usage_examples.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -Examples of how to use metrics decorators in your bot handlers. -These examples show how to integrate metrics without modifying existing logic. -""" - -from aiogram import Router, F -from aiogram.types import Message, CallbackQuery -from aiogram.filters import Command -from aiogram.fsm.context import FSMContext - -# Import metrics decorators -from ..utils.metrics import track_time, track_errors, db_query_time, metrics - -router = Router() - - -# Example 1: Basic command handler with timing and error tracking -@router.message(Command("start")) -@track_time("start_command", "private_handler") -@track_errors("private_handler", "start_command") -async def start_command(message: Message, state: FSMContext): - """Start command handler with metrics.""" - # Your existing logic here - await message.answer("Welcome! Bot started.") - - # Optionally record custom metrics - metrics.record_command("start", "private_handler", "user") - - -# Example 2: Group command handler with custom labels -@router.message(Command("help"), F.chat.type.in_({"group", "supergroup"})) -@track_time("help_command", "group_handler") -@track_errors("group_handler", "help_command") -async def help_command(message: Message): - """Help command handler for groups.""" - await message.answer("Group help information.") - - # Record command with group context - metrics.record_command("help", "group_handler", "group_user") - - -# Example 3: Callback handler with timing -@router.callback_query(F.data.startswith("menu:")) -@track_time("menu_callback", "callback_handler") -@track_errors("callback_handler", "menu_callback") -async def menu_callback(callback: CallbackQuery): - """Menu callback handler.""" - data = callback.data - await callback.answer(f"Menu: {data}") - - # Record callback processing - metrics.record_message("callback_query", "callback", "callback_handler") - - -# Example 4: Database operation with query timing -@db_query_time("user_lookup", "users", "select") -async def get_user_info(user_id: int): - """Example database function with timing.""" - # Your database query here - # result = await db.fetch_one("SELECT * FROM users WHERE id = ?", user_id) - return {"user_id": user_id, "status": "active"} - - -# Example 5: Complex handler with multiple metrics -@router.message(Command("stats")) -@track_time("stats_command", "admin_handler") -@track_errors("admin_handler", "stats_command") -async def stats_command(message: Message): - """Stats command with detailed metrics.""" - try: - # Record command execution - metrics.record_command("stats", "admin_handler", "admin_user") - - # Your stats logic here - stats = await get_bot_stats() - - # Record successful execution - await message.answer(f"Bot stats: {stats}") - - except Exception as e: - # Error is automatically tracked by decorator - await message.answer("Error getting stats") - raise - - -# Example 6: Message handler with message type tracking -@router.message() -@track_time("message_processing", "general_handler") -async def handle_message(message: Message): - """General message handler.""" - # Message type is automatically detected by middleware - # But you can add custom tracking - - if message.photo: - # Custom metric for photo processing - metrics.record_message("photo", "general", "photo_handler") - - # Your message handling logic - await message.answer("Message received") - - -# Example 7: Error-prone operation with custom error tracking -@track_errors("file_handler", "file_upload") -async def upload_file(file_data: bytes, filename: str): - """File upload with error tracking.""" - try: - # Your file upload logic - # result = await upload_service.upload(file_data, filename) - return {"status": "success", "filename": filename} - - except Exception as e: - # Custom error metric - metrics.record_error( - type(e).__name__, - "file_handler", - "file_upload" - ) - raise - - -# Example 8: Background task with metrics -async def background_metrics_collection(): - """Background task for collecting periodic metrics.""" - while True: - try: - # Collect custom metrics - active_users = await count_active_users() - metrics.set_active_users(active_users, "current") - - # Wait before next collection - await asyncio.sleep(300) # 5 minutes - - except Exception as e: - metrics.record_error( - type(e).__name__, - "background_task", - "metrics_collection" - ) - await asyncio.sleep(60) # Wait 1 minute on error - - -# Example 9: Custom metric collection in service -class UserService: - """Example service with integrated metrics.""" - - @db_query_time("user_creation", "users", "insert") - async def create_user(self, user_data: dict): - """Create user with database timing.""" - # Your user creation logic - # user_id = await self.db.execute("INSERT INTO users ...") - return {"user_id": 123, "status": "created"} - - @track_time("user_update", "user_service") - async def update_user(self, user_id: int, updates: dict): - """Update user with timing.""" - # Your update logic - # await self.db.execute("UPDATE users SET ...") - return {"user_id": user_id, "status": "updated"} - - -# Example 10: Middleware integration example -async def custom_middleware(handler, event, data): - """Custom middleware that works with metrics system.""" - from ..utils.metrics import track_middleware - - async with track_middleware("custom_middleware"): - # Your middleware logic - result = await handler(event, data) - return result - - -# Helper function for stats (placeholder) -async def get_bot_stats(): - """Get bot statistics.""" - return { - "total_users": 1000, - "active_today": 150, - "commands_processed": 5000 - } - - -# Helper function for user counting (placeholder) -async def count_active_users(): - """Count active users.""" - return 150 - - -# Import asyncio for background task -import asyncio diff --git a/helper_bot/handlers/group/group_handlers.py b/helper_bot/handlers/group/group_handlers.py index 10cf058..af1bc08 100644 --- a/helper_bot/handlers/group/group_handlers.py +++ b/helper_bot/handlers/group/group_handlers.py @@ -16,6 +16,12 @@ from .exceptions import UserNotFoundError # Local imports - utilities from logs.custom_logger import logger +# Local imports - metrics +from helper_bot.utils.metrics import ( + metrics, + track_time, + track_errors +) class GroupHandlers: """Main handler class for group messages""" @@ -41,6 +47,7 @@ class GroupHandlers: @error_handler async def handle_message(self, message: types.Message, state: FSMContext): """Handle admin reply to user through group chat""" + logger.info( f'Получено сообщение в группе {message.chat.title} (ID: {message.chat.id}) ' f'от пользователя {message.from_user.full_name} (ID: {message.from_user.id}): "{message.text}"' diff --git a/helper_bot/handlers/group/services.py b/helper_bot/handlers/group/services.py index 134259b..2c546b9 100644 --- a/helper_bot/handlers/group/services.py +++ b/helper_bot/handlers/group/services.py @@ -11,6 +11,14 @@ from helper_bot.utils.helper_func import send_text_message from .exceptions import NoReplyToMessageError, UserNotFoundError from logs.custom_logger import logger +# Local imports - metrics +from helper_bot.utils.metrics import ( + metrics, + track_time, + track_errors, + db_query_time +) + class DatabaseProtocol(Protocol): """Protocol for database operations""" diff --git a/helper_bot/handlers/private/private_handlers.py b/helper_bot/handlers/private/private_handlers.py index 7a91ea6..506107d 100644 --- a/helper_bot/handlers/private/private_handlers.py +++ b/helper_bot/handlers/private/private_handlers.py @@ -28,8 +28,7 @@ from helper_bot.utils.helper_func import ( from helper_bot.utils.metrics import ( metrics, track_time, - track_errors, - db_query_time + track_errors ) # Local imports - modular components @@ -99,14 +98,8 @@ class PrivateHandlers: await message.answer('Я перезапущен!', reply_markup=markup, parse_mode='HTML') @error_handler - @track_time("start_message_handler", "private_handler") - @track_errors("private_handler", "start_message_handler") async def handle_start_message(self, message: types.Message, state: FSMContext, **kwargs): """Handle start command and return to bot button with metrics tracking""" - # Record start command metrics - metrics.record_command("start", "private_handler", "user" if not message.from_user.is_bot else "bot") - metrics.record_message("command", "private", "private_handler") - # User service operations with metrics await self.user_service.log_user_message(message) await self.user_service.ensure_user_exists(message) @@ -123,6 +116,7 @@ class PrivateHandlers: @error_handler async def suggest_post(self, message: types.Message, state: FSMContext, **kwargs): """Handle suggest post button""" + # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) await self.user_service.log_user_message(message) await state.set_state(FSM_STATES["SUGGEST"]) @@ -134,6 +128,7 @@ class PrivateHandlers: @error_handler async def end_message(self, message: types.Message, state: FSMContext, **kwargs): """Handle goodbye button""" + # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) await self.user_service.log_user_message(message) @@ -149,6 +144,7 @@ class PrivateHandlers: @error_handler async def suggest_router(self, message: types.Message, state: FSMContext, album: list = None, **kwargs): """Handle post submission in suggest state""" + # Post service operations with metrics await self.post_service.process_post(message, album) # Send success message and return to start state @@ -160,6 +156,7 @@ class PrivateHandlers: @error_handler async def stickers(self, message: types.Message, state: FSMContext, **kwargs): """Handle stickers request""" + # User service operations with metrics markup = get_reply_keyboard(self.db, message.from_user.id) self.db.update_info_about_stickers(user_id=message.from_user.id) await self.user_service.log_user_message(message) @@ -172,6 +169,7 @@ class PrivateHandlers: @error_handler async def connect_with_admin(self, message: types.Message, state: FSMContext, **kwargs): """Handle connect with admin button""" + # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) admin_message = messages.get_message(get_first_name(message), 'CONNECT_WITH_ADMIN') await message.answer(admin_message, parse_mode="html") @@ -181,6 +179,7 @@ class PrivateHandlers: @error_handler async def resend_message_in_group_for_message(self, message: types.Message, state: FSMContext, **kwargs): """Handle messages in admin chat states""" + # User service operations with metrics await self.user_service.update_user_activity(message.from_user.id) await message.forward(chat_id=self.settings.group_for_message) diff --git a/helper_bot/handlers/private/services.py b/helper_bot/handlers/private/services.py index 2950034..7f3638c 100644 --- a/helper_bot/handlers/private/services.py +++ b/helper_bot/handlers/private/services.py @@ -138,6 +138,8 @@ class PostService: self.db = db self.settings = settings + @track_time("handle_text_post", "post_service") + @track_errors("post_service", "handle_text_post") async def handle_text_post(self, message: types.Message, first_name: str) -> None: """Handle text post submission""" post_text = get_text_message(message.text.lower(), first_name, message.from_user.username) @@ -146,6 +148,8 @@ class PostService: sent_message_id = await send_text_message(self.settings.group_for_posts, message, post_text, markup) self.db.add_post_in_db(sent_message_id, message.text, message.from_user.id) + @track_time("handle_photo_post", "post_service") + @track_errors("post_service", "handle_photo_post") async def handle_photo_post(self, message: types.Message, first_name: str) -> None: """Handle photo post submission""" post_caption = "" @@ -160,6 +164,8 @@ class PostService: self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) await add_in_db_media(sent_message, self.db) + @track_time("handle_video_post", "post_service") + @track_errors("post_service", "handle_video_post") async def handle_video_post(self, message: types.Message, first_name: str) -> None: """Handle video post submission""" post_caption = "" @@ -174,6 +180,8 @@ class PostService: self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) await add_in_db_media(sent_message, self.db) + @track_time("handle_video_note_post", "post_service") + @track_errors("post_service", "handle_video_note_post") async def handle_video_note_post(self, message: types.Message) -> None: """Handle video note post submission""" markup = get_reply_keyboard_for_post() @@ -184,6 +192,8 @@ class PostService: self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) await add_in_db_media(sent_message, self.db) + @track_time("handle_audio_post", "post_service") + @track_errors("post_service", "handle_audio_post") async def handle_audio_post(self, message: types.Message, first_name: str) -> None: """Handle audio post submission""" post_caption = "" @@ -198,6 +208,8 @@ class PostService: self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) await add_in_db_media(sent_message, self.db) + @track_time("handle_voice_post", "post_service") + @track_errors("post_service", "handle_voice_post") async def handle_voice_post(self, message: types.Message) -> None: """Handle voice post submission""" markup = get_reply_keyboard_for_post() @@ -208,6 +220,8 @@ class PostService: self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) await add_in_db_media(sent_message, self.db) + @track_time("handle_media_group_post", "post_service") + @track_errors("post_service", "handle_media_group_post") async def handle_media_group_post(self, message: types.Message, album: list, first_name: str) -> None: """Handle media group post submission""" post_caption = " " @@ -229,6 +243,8 @@ class PostService: message_id=media_group_message_id, helper_message_id=help_message_id ) + @track_time("process_post", "post_service") + @track_errors("post_service", "process_post") async def process_post(self, message: types.Message, album: Union[list, None] = None) -> None: """Process post based on content type""" first_name = get_first_name(message) diff --git a/helper_bot/main.py b/helper_bot/main.py index 051c991..93b7c7c 100644 --- a/helper_bot/main.py +++ b/helper_bot/main.py @@ -17,18 +17,14 @@ async def start_bot(bdf): bot = Bot(token=token, default=DefaultBotProperties( parse_mode='HTML', link_preview_is_disabled=bdf.settings['Telegram']['preview_link'] - ), timeout=30.0) # Добавляем таймаут для предотвращения зависаний + ), timeout=30.0) dp = Dispatcher(storage=MemoryStorage(), fsm_strategy=FSMStrategy.GLOBAL_USER) - # ✅ Middleware для метрик (добавляем первыми) - dp.message.middleware(MetricsMiddleware()) - dp.callback_query.middleware(MetricsMiddleware()) - dp.message.middleware(ErrorMetricsMiddleware()) - dp.callback_query.middleware(ErrorMetricsMiddleware()) - - # ✅ Глобальная middleware для всех роутеров + # ✅ Оптимизированная регистрация middleware dp.update.outer_middleware(DependenciesMiddleware()) - + dp.update.outer_middleware(MetricsMiddleware()) + dp.update.outer_middleware(BlacklistMiddleware()) + dp.include_routers(admin_router, private_router, callback_router, group_router) await bot.delete_webhook(drop_pending_updates=True) await dp.start_polling(bot, skip_updates=True) diff --git a/helper_bot/main_with_metrics.py b/helper_bot/main_with_metrics.py deleted file mode 100644 index 902763e..0000000 --- a/helper_bot/main_with_metrics.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Example integration of metrics monitoring in the main bot file. -This shows how to integrate the metrics system without modifying existing handlers. -""" - -import asyncio -import logging -from aiogram import Bot, Dispatcher -from aiogram.enums import ParseMode -from aiogram.fsm.storage.memory import MemoryStorage - -# Import metrics components -from .utils.metrics import metrics -from .utils.metrics_exporter import MetricsManager -from .middlewares.metrics_middleware import MetricsMiddleware, ErrorMetricsMiddleware - -# Import your existing bot components -# from .handlers import ... # Your existing handlers -# from .database.db import BotDB # Your existing database class - - -class BotWithMetrics: - """Bot class with integrated metrics monitoring.""" - - def __init__(self, token: str, metrics_port: int = 8000): - self.bot = Bot(token=token, parse_mode=ParseMode.HTML) - self.storage = MemoryStorage() - self.dp = Dispatcher(storage=self.storage) - - # Initialize metrics manager - # You can pass your database instance here if needed - # self.metrics_manager = MetricsManager(port=metrics_port, db=your_db_instance) - self.metrics_manager = MetricsManager(port=metrics_port) - - # Setup middlewares - self._setup_middlewares() - - # Setup handlers (your existing handlers) - # self._setup_handlers() - - self.logger = logging.getLogger(__name__) - - def _setup_middlewares(self): - """Setup metrics middlewares.""" - # Add metrics middleware first to capture all events - self.dp.message.middleware(MetricsMiddleware()) - self.dp.callback_query.middleware(MetricsMiddleware()) - - # Add error tracking middleware - self.dp.message.middleware(ErrorMetricsMiddleware()) - self.dp.callback_query.middleware(ErrorMetricsMiddleware()) - - # Your existing middlewares can go here - # self.dp.message.middleware(YourExistingMiddleware()) - - def _setup_handlers(self): - """Setup bot handlers.""" - # Import and register your existing handlers here - # from .handlers.admin import admin_router - # from .handlers.private import private_router - # from .handlers.group import group_router - # from .handlers.callback import callback_router - # - # self.dp.include_router(admin_router) - # self.dp.include_router(private_router) - # self.dp.include_router(group_router) - # self.dp.include_router(callback_router) - pass - - async def start(self): - """Start the bot with metrics.""" - try: - # Start metrics collection - await self.metrics_manager.start() - self.logger.info("Metrics system started") - - # Start bot polling - await self.dp.start_polling(self.bot) - - except Exception as e: - self.logger.error(f"Error starting bot: {e}") - raise - finally: - await self.stop() - - async def stop(self): - """Stop the bot and metrics.""" - try: - # Stop metrics collection - await self.metrics_manager.stop() - self.logger.info("Metrics system stopped") - - # Stop bot - await self.bot.session.close() - self.logger.info("Bot stopped") - - except Exception as e: - self.logger.error(f"Error stopping bot: {e}") - - -# Example usage function -async def main(): - """Main function to run the bot with metrics.""" - # Your bot token - TOKEN = "YOUR_BOT_TOKEN_HERE" - - # Create and start bot - bot = BotWithMetrics(TOKEN) - - try: - await bot.start() - except KeyboardInterrupt: - await bot.stop() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/helper_bot/middlewares/blacklist_middleware.py b/helper_bot/middlewares/blacklist_middleware.py index e8e8530..18e82f3 100644 --- a/helper_bot/middlewares/blacklist_middleware.py +++ b/helper_bot/middlewares/blacklist_middleware.py @@ -2,6 +2,7 @@ from typing import Dict, Any import html from aiogram import BaseMiddleware, types +from aiogram.types import TelegramObject, Message, CallbackQuery from helper_bot.utils.base_dependency_factory import get_global_instance from logs.custom_logger import logger @@ -10,17 +11,38 @@ BotDB = bdf.get_db() class BlacklistMiddleware(BaseMiddleware): - async def __call__(self, handler, event: types.Message, data: Dict[str, Any]) -> Any: - logger.info(f'Вызов BlacklistMiddleware для пользователя {event.from_user.username}') + async def __call__(self, handler, event: TelegramObject, data: Dict[str, Any]) -> Any: + # Проверяем тип события и получаем пользователя + user = None + if isinstance(event, Message): + user = event.from_user + elif isinstance(event, CallbackQuery): + user = event.from_user + + # Если это не сообщение или callback, пропускаем проверку + if not user: + return await handler(event, data) + + logger.info(f'Вызов BlacklistMiddleware для пользователя {user.username}') + # Используем асинхронную версию для предотвращения блокировки - if await BotDB.check_user_in_blacklist_async(user_id=event.from_user.id): - logger.info(f'BlacklistMiddleware результат для пользователя: {event.from_user.username} заблокирован!') - user_info = await BotDB.get_blacklist_users_by_id_async(event.from_user.id) + if await BotDB.check_user_in_blacklist_async(user_id=user.id): + logger.info(f'BlacklistMiddleware результат для пользователя: {user.username} заблокирован!') + user_info = await BotDB.get_blacklist_users_by_id_async(user.id) # Экранируем потенциально проблемные символы reason = html.escape(str(user_info[2])) if user_info[2] else "Не указана" date_unban = html.escape(str(user_info[3])) if user_info[3] else "Не указана" - await event.answer( - f"Ты заблокирован.\nПричина блокировки: {reason}\nДата разбана: {date_unban}") + + # Отправляем сообщение в зависимости от типа события + if isinstance(event, Message): + await event.answer( + f"Ты заблокирован.\nПричина блокировки: {reason}\nДата разбана: {date_unban}") + elif isinstance(event, CallbackQuery): + await event.answer( + f"Ты заблокирован.\nПричина блокировки: {reason}\nДата разбана: {date_unban}", + show_alert=True) + return False - logger.info(f'BlacklistMiddleware результат для пользователя: {event.from_user.username} доступ разрешен') + + logger.info(f'BlacklistMiddleware результат для пользователя: {user.username} доступ разрешен') return await handler(event, data) diff --git a/helper_bot/middlewares/metrics_middleware.py b/helper_bot/middlewares/metrics_middleware.py index 76a3277..10984ac 100644 --- a/helper_bot/middlewares/metrics_middleware.py +++ b/helper_bot/middlewares/metrics_middleware.py @@ -8,7 +8,7 @@ from aiogram import BaseMiddleware from aiogram.types import TelegramObject, Message, CallbackQuery from aiogram.enums import ChatType import time -from ..utils.metrics import metrics, track_middleware +from ..utils.metrics import metrics class MetricsMiddleware(BaseMiddleware): @@ -22,50 +22,57 @@ class MetricsMiddleware(BaseMiddleware): ) -> Any: """Process event and collect metrics.""" - async with track_middleware("metrics_middleware"): - # Record message processing - if isinstance(event, Message): - await self._record_message_metrics(event, data) - elif isinstance(event, CallbackQuery): - await self._record_callback_metrics(event, data) + # Record basic event metrics + if isinstance(event, Message): + await self._record_message_metrics(event) + elif isinstance(event, CallbackQuery): + await self._record_callback_metrics(event) + + # Execute handler with timing + start_time = time.time() + try: + result = await handler(event, data) + duration = time.time() - start_time - # Execute handler and collect timing - start_time = time.time() - try: - result = await handler(event, data) - duration = time.time() - start_time - - # Record successful execution - handler_name = handler.__name__ if hasattr(handler, '__name__') else "unknown" - metrics.record_method_duration( - handler_name, - duration, - "handler", - "success" - ) - - return result - - except Exception as e: - duration = time.time() - start_time - - # Record error and timing - handler_name = handler.__name__ if hasattr(handler, '__name__') else "unknown" - metrics.record_method_duration( - handler_name, - duration, - "handler", - "error" - ) - metrics.record_error( - type(e).__name__, - "handler", - handler_name - ) - raise + # Record successful execution + handler_name = self._get_handler_name(handler) + metrics.record_method_duration( + handler_name, + duration, + "handler", + "success" + ) + + return result + + except Exception as e: + duration = time.time() - start_time + + # Record error and timing + handler_name = self._get_handler_name(handler) + metrics.record_method_duration( + handler_name, + duration, + "handler", + "error" + ) + metrics.record_error( + type(e).__name__, + "handler", + handler_name + ) + raise - async def _record_message_metrics(self, message: Message, data: Dict[str, Any]): - """Record metrics for message processing.""" + def _get_handler_name(self, handler: Callable) -> str: + """Extract handler name efficiently.""" + if hasattr(handler, '__name__'): + return handler.__name__ + elif hasattr(handler, '__qualname__'): + return handler.__qualname__ + return "unknown" + + async def _record_message_metrics(self, message: Message): + """Record message metrics efficiently.""" # Determine message type message_type = "text" if message.photo: @@ -92,41 +99,25 @@ class MetricsMiddleware(BaseMiddleware): elif message.chat.type == ChatType.CHANNEL: chat_type = "channel" - # Determine handler type - handler_type = "unknown" - if message.text and message.text.startswith('/'): - handler_type = "command" - # Record command specifically - command = message.text.split()[0][1:] # Remove '/' and get command name - metrics.record_command( - command, - "message_handler", - "user" if message.from_user else "unknown" - ) - # Record message processing - metrics.record_message(message_type, chat_type, handler_type) - - async def _record_callback_metrics(self, callback: CallbackQuery, data: Dict[str, Any]): - """Record metrics for callback query processing.""" - # Record callback processing - metrics.record_message( - "callback_query", - "callback", - "callback_handler" - ) + metrics.record_message(message_type, chat_type, "message_handler") + + # Record command if applicable + if message.text and message.text.startswith('/'): + command = message.text.split()[0][1:] # Remove '/' and get command name + user_type = "user" if message.from_user else "unknown" + metrics.record_command(command, "message_handler", user_type) + + async def _record_callback_metrics(self, callback: CallbackQuery): + """Record callback metrics efficiently.""" + metrics.record_message("callback_query", "callback", "callback_handler") - # Record callback command if available if callback.data: - # Extract command from callback data (assuming format like "command:param") parts = callback.data.split(':', 1) if parts: command = parts[0] - metrics.record_command( - command, - "callback_handler", - "user" if callback.from_user else "unknown" - ) + user_type = "user" if callback.from_user else "unknown" + metrics.record_command(command, "callback_handler", user_type) class DatabaseMetricsMiddleware(BaseMiddleware): diff --git a/helper_bot/utils/metrics.py b/helper_bot/utils/metrics.py index 3261f5e..8bc97b1 100644 --- a/helper_bot/utils/metrics.py +++ b/helper_bot/utils/metrics.py @@ -31,7 +31,8 @@ class BotMetrics: 'method_duration_seconds', 'Time spent executing methods', ['method_name', 'handler_type', 'status'], - buckets=[0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0], + # Оптимизированные buckets для Telegram API (обычно < 1 сек) + buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0], registry=self.registry ) @@ -56,7 +57,8 @@ class BotMetrics: 'db_query_duration_seconds', 'Time spent executing database queries', ['query_type', 'table_name', 'operation'], - buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5], + # Оптимизированные buckets для SQLite/PostgreSQL + buckets=[0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5], registry=self.registry ) @@ -73,7 +75,16 @@ class BotMetrics: 'middleware_duration_seconds', 'Time spent in middleware execution', ['middleware_name', 'status'], - buckets=[0.01, 0.05, 0.1, 0.25, 0.5], + # Middleware должен быть быстрым + buckets=[0.001, 0.005, 0.01, 0.05, 0.1, 0.25], + registry=self.registry + ) + + # Rate limiting metrics + self.rate_limit_hits_total = Counter( + 'rate_limit_hits_total', + 'Total number of rate limit hits', + ['limit_type', 'handler_type'], registry=self.registry )