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
)