Enhance Makefile and update metrics handling in bot

- Added new commands in the Makefile for restarting individual services: `restart-bot`, `restart-prometheus`, and `restart-grafana`.
- Updated Prometheus and Grafana dashboard expressions for better metrics aggregation.
- Removed the `main_with_metrics.py` file and integrated metrics handling directly into the main bot file.
- Refactored middleware to improve metrics tracking and error handling across message and callback processing.
- Optimized metrics recording with enhanced bucket configurations for better performance monitoring.
This commit is contained in:
2025-08-29 18:23:17 +03:00
parent c68db87901
commit f097d69dd4
13 changed files with 166 additions and 408 deletions

View File

@@ -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

View File

@@ -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}"'

View File

@@ -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"""

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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())

View File

@@ -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"<b>Ты заблокирован.</b>\n<b>Причина блокировки:</b> {reason}\n<b>Дата разбана:</b> {date_unban}")
# Отправляем сообщение в зависимости от типа события
if isinstance(event, Message):
await event.answer(
f"<b>Ты заблокирован.</b>\n<b>Причина блокировки:</b> {reason}\n<b>Дата разбана:</b> {date_unban}")
elif isinstance(event, CallbackQuery):
await event.answer(
f"<b>Ты заблокирован.</b>\n<b>Причина блокировки:</b> {reason}\n<b>Дата разбана:</b> {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)

View File

@@ -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):

View File

@@ -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
)