Refactor Docker and configuration files for improved structure and functionality

- Updated `.dockerignore` to include additional development and temporary files, enhancing build efficiency.
- Modified `.gitignore` to remove unnecessary entries and streamline ignored files.
- Enhanced `docker-compose.yml` with health checks, resource limits, and improved environment variable handling for better service management.
- Refactored `Dockerfile.bot` to utilize a multi-stage build for optimized image size and security.
- Improved `Makefile` with new commands for deployment, migration, and backup, along with enhanced help documentation.
- Updated `requirements.txt` to include new dependencies for environment variable management.
- Refactored metrics handling in the bot to ensure proper initialization and collection.
This commit is contained in:
2025-08-29 23:15:06 +03:00
parent f097d69dd4
commit 8f338196b7
27 changed files with 1499 additions and 370 deletions

View File

@@ -8,12 +8,17 @@ from aiogram import BaseMiddleware
from aiogram.types import TelegramObject, Message, CallbackQuery
from aiogram.enums import ChatType
import time
import logging
from ..utils.metrics import metrics
class MetricsMiddleware(BaseMiddleware):
"""Middleware for automatic metrics collection in aiogram handlers."""
def __init__(self):
super().__init__()
self.logger = logging.getLogger(__name__)
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
@@ -22,11 +27,33 @@ class MetricsMiddleware(BaseMiddleware):
) -> Any:
"""Process event and collect metrics."""
# Record basic event metrics
# Добавляем логирование для диагностики
self.logger.info(f"📊 MetricsMiddleware called for event type: {type(event).__name__}")
# Extract command info before execution
command_info = None
if isinstance(event, Message):
self.logger.info(f"📊 Processing Message event")
await self._record_message_metrics(event)
if event.text and event.text.startswith('/'):
command_info = {
'command': event.text.split()[0][1:], # Remove '/' and get command name
'user_type': "user" if event.from_user else "unknown",
'handler_type': "message_handler"
}
elif isinstance(event, CallbackQuery):
self.logger.info(f"📊 Processing CallbackQuery event")
await self._record_callback_metrics(event)
if event.data:
parts = event.data.split(':', 1)
if parts:
command_info = {
'command': parts[0],
'user_type': "user" if event.from_user else "unknown",
'handler_type': "callback_handler"
}
else:
self.logger.info(f"📊 Processing unknown event type: {type(event).__name__}")
# Execute handler with timing
start_time = time.time()
@@ -36,6 +63,7 @@ class MetricsMiddleware(BaseMiddleware):
# Record successful execution
handler_name = self._get_handler_name(handler)
self.logger.info(f"📊 Recording successful execution: {handler_name}")
metrics.record_method_duration(
handler_name,
duration,
@@ -43,6 +71,15 @@ class MetricsMiddleware(BaseMiddleware):
"success"
)
# Record command with success status if applicable
if command_info:
metrics.record_command(
command_info['command'],
command_info['handler_type'],
command_info['user_type'],
"success"
)
return result
except Exception as e:
@@ -50,6 +87,7 @@ class MetricsMiddleware(BaseMiddleware):
# Record error and timing
handler_name = self._get_handler_name(handler)
self.logger.error(f"📊 Recording error execution: {handler_name}, error: {type(e).__name__}")
metrics.record_method_duration(
handler_name,
duration,
@@ -61,15 +99,39 @@ class MetricsMiddleware(BaseMiddleware):
"handler",
handler_name
)
# Record command with error status if applicable
if command_info:
metrics.record_command(
command_info['command'],
command_info['handler_type'],
command_info['user_type'],
"error"
)
raise
def _get_handler_name(self, handler: Callable) -> str:
"""Extract handler name efficiently."""
if hasattr(handler, '__name__'):
# Проверяем различные способы получения имени хендлера
if hasattr(handler, '__name__') and handler.__name__ != '<lambda>':
return handler.__name__
elif hasattr(handler, '__qualname__'):
elif hasattr(handler, '__qualname__') and handler.__qualname__ != '<lambda>':
return handler.__qualname__
return "unknown"
elif hasattr(handler, 'callback') and hasattr(handler.callback, '__name__'):
return handler.callback.__name__
elif hasattr(handler, 'view') and hasattr(handler.view, '__name__'):
return handler.view.__name__
else:
# Пытаемся получить имя из строкового представления
handler_str = str(handler)
if 'function' in handler_str:
# Извлекаем имя функции из строки
import re
match = re.search(r'function\s+(\w+)', handler_str)
if match:
return match.group(1)
return "unknown"
async def _record_message_metrics(self, message: Message):
"""Record message metrics efficiently."""
@@ -101,23 +163,10 @@ class MetricsMiddleware(BaseMiddleware):
# Record message processing
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")
if callback.data:
parts = callback.data.split(':', 1)
if parts:
command = parts[0]
user_type = "user" if callback.from_user else "unknown"
metrics.record_command(command, "callback_handler", user_type)
class DatabaseMetricsMiddleware(BaseMiddleware):