Refactor metrics handling and remove scheduler

- Removed the metrics scheduler functionality from the bot, transitioning to real-time metrics updates via middleware.
- Enhanced logging for metrics operations across various handlers to improve monitoring and debugging capabilities.
- Integrated metrics tracking for user activities and database errors, providing better insights into bot performance.
- Cleaned up code by removing obsolete comments and unused imports, improving overall readability and maintainability.
This commit is contained in:
2025-09-03 19:18:04 +03:00
parent 650acd5bce
commit ae7bd476bb
14 changed files with 248 additions and 219 deletions

View File

@@ -25,6 +25,13 @@ from helper_bot.handlers.admin.utils import (
) )
from logs.custom_logger import logger from logs.custom_logger import logger
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors
)
# Создаем роутер с middleware для проверки доступа # Создаем роутер с middleware для проверки доступа
admin_router = Router() admin_router = Router()
admin_router.message.middleware(AdminAccessMiddleware()) admin_router.message.middleware(AdminAccessMiddleware())
@@ -38,6 +45,8 @@ admin_router.message.middleware(AdminAccessMiddleware())
ChatTypeFilter(chat_type=["private"]), ChatTypeFilter(chat_type=["private"]),
Command('admin') Command('admin')
) )
@track_time("admin_panel", "admin_handlers")
@track_errors("admin_handlers", "admin_panel")
async def admin_panel( async def admin_panel(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -62,6 +71,8 @@ async def admin_panel(
StateFilter("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"), StateFilter("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"),
F.text == 'Отменить' F.text == 'Отменить'
) )
@track_time("cancel_ban_process", "admin_handlers")
@track_errors("admin_handlers", "cancel_ban_process")
async def cancel_ban_process( async def cancel_ban_process(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -81,6 +92,8 @@ async def cancel_ban_process(
StateFilter("ADMIN"), StateFilter("ADMIN"),
F.text == 'Бан (Список)' F.text == 'Бан (Список)'
) )
@track_time("get_last_users", "admin_handlers")
@track_errors("admin_handlers", "get_last_users")
async def get_last_users( async def get_last_users(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -112,6 +125,8 @@ async def get_last_users(
StateFilter("ADMIN"), StateFilter("ADMIN"),
F.text == 'Разбан (список)' F.text == 'Разбан (список)'
) )
@track_time("get_banned_users", "admin_handlers")
@track_errors("admin_handlers", "get_banned_users")
async def get_banned_users( async def get_banned_users(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -141,6 +156,8 @@ async def get_banned_users(
StateFilter("ADMIN"), StateFilter("ADMIN"),
F.text.in_(['Бан по нику', 'Бан по ID']) F.text.in_(['Бан по нику', 'Бан по ID'])
) )
@track_time("start_ban_process", "admin_handlers")
@track_errors("admin_handlers", "start_ban_process")
async def start_ban_process( async def start_ban_process(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -162,6 +179,8 @@ async def start_ban_process(
ChatTypeFilter(chat_type=["private"]), ChatTypeFilter(chat_type=["private"]),
StateFilter("AWAIT_BAN_TARGET") StateFilter("AWAIT_BAN_TARGET")
) )
@track_time("process_ban_target", "admin_handlers")
@track_errors("admin_handlers", "process_ban_target")
async def process_ban_target( async def process_ban_target(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -232,6 +251,8 @@ async def process_ban_target(
ChatTypeFilter(chat_type=["private"]), ChatTypeFilter(chat_type=["private"]),
StateFilter("AWAIT_BAN_DETAILS") StateFilter("AWAIT_BAN_DETAILS")
) )
@track_time("process_ban_reason", "admin_handlers")
@track_errors("admin_handlers", "process_ban_reason")
async def process_ban_reason( async def process_ban_reason(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -272,6 +293,8 @@ async def process_ban_reason(
ChatTypeFilter(chat_type=["private"]), ChatTypeFilter(chat_type=["private"]),
StateFilter("AWAIT_BAN_DURATION") StateFilter("AWAIT_BAN_DURATION")
) )
@track_time("process_ban_duration", "admin_handlers")
@track_errors("admin_handlers", "process_ban_duration")
async def process_ban_duration( async def process_ban_duration(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,
@@ -315,6 +338,8 @@ async def process_ban_duration(
StateFilter("BAN_CONFIRMATION"), StateFilter("BAN_CONFIRMATION"),
F.text == 'Подтвердить' F.text == 'Подтвердить'
) )
@track_time("confirm_ban", "admin_handlers")
@track_errors("admin_handlers", "confirm_ban")
async def confirm_ban( async def confirm_ban(
message: types.Message, message: types.Message,
state: FSMContext, state: FSMContext,

View File

@@ -5,6 +5,14 @@ from helper_bot.utils.helper_func import add_days_to_date, get_banned_users_butt
from helper_bot.handlers.admin.exceptions import UserAlreadyBannedError, InvalidInputError from helper_bot.handlers.admin.exceptions import UserAlreadyBannedError, InvalidInputError
from logs.custom_logger import logger from logs.custom_logger import logger
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors,
db_query_time
)
class User: class User:
"""Модель пользователя""" """Модель пользователя"""
@@ -29,6 +37,8 @@ class AdminService:
def __init__(self, bot_db): def __init__(self, bot_db):
self.bot_db = bot_db self.bot_db = bot_db
@track_time("get_last_users", "admin_service")
@track_errors("admin_service", "get_last_users")
async def get_last_users(self) -> List[User]: async def get_last_users(self) -> List[User]:
"""Получить список последних пользователей""" """Получить список последних пользователей"""
try: try:
@@ -45,6 +55,8 @@ class AdminService:
logger.error(f"Ошибка при получении списка последних пользователей: {e}") logger.error(f"Ошибка при получении списка последних пользователей: {e}")
raise raise
@track_time("get_banned_users", "admin_service")
@track_errors("admin_service", "get_banned_users")
async def get_banned_users(self) -> List[BannedUser]: async def get_banned_users(self) -> List[BannedUser]:
"""Получить список заблокированных пользователей""" """Получить список заблокированных пользователей"""
try: try:
@@ -68,6 +80,8 @@ class AdminService:
logger.error(f"Ошибка при получении списка заблокированных пользователей: {e}") logger.error(f"Ошибка при получении списка заблокированных пользователей: {e}")
raise raise
@track_time("get_user_by_username", "admin_service")
@track_errors("admin_service", "get_user_by_username")
async def get_user_by_username(self, username: str) -> Optional[User]: async def get_user_by_username(self, username: str) -> Optional[User]:
"""Получить пользователя по username""" """Получить пользователя по username"""
try: try:
@@ -85,6 +99,8 @@ class AdminService:
logger.error(f"Ошибка при поиске пользователя по username {username}: {e}") logger.error(f"Ошибка при поиске пользователя по username {username}: {e}")
raise raise
@track_time("get_user_by_id", "admin_service")
@track_errors("admin_service", "get_user_by_id")
async def get_user_by_id(self, user_id: int) -> Optional[User]: async def get_user_by_id(self, user_id: int) -> Optional[User]:
"""Получить пользователя по ID""" """Получить пользователя по ID"""
try: try:
@@ -101,6 +117,8 @@ class AdminService:
logger.error(f"Ошибка при поиске пользователя по ID {user_id}: {e}") logger.error(f"Ошибка при поиске пользователя по ID {user_id}: {e}")
raise raise
@track_time("ban_user", "admin_service")
@track_errors("admin_service", "ban_user")
async def ban_user(self, user_id: int, username: str, reason: str, ban_days: Optional[int]) -> None: async def ban_user(self, user_id: int, username: str, reason: str, ban_days: Optional[int]) -> None:
"""Заблокировать пользователя""" """Заблокировать пользователя"""
try: try:
@@ -122,6 +140,8 @@ class AdminService:
logger.error(f"Ошибка при блокировке пользователя {user_id}: {e}") logger.error(f"Ошибка при блокировке пользователя {user_id}: {e}")
raise raise
@track_time("unban_user", "admin_service")
@track_errors("admin_service", "unban_user")
async def unban_user(self, user_id: int) -> None: async def unban_user(self, user_id: int) -> None:
"""Разблокировать пользователя""" """Разблокировать пользователя"""
try: try:
@@ -131,6 +151,8 @@ class AdminService:
logger.error(f"Ошибка при разблокировке пользователя {user_id}: {e}") logger.error(f"Ошибка при разблокировке пользователя {user_id}: {e}")
raise raise
@track_time("validate_user_input", "admin_service")
@track_errors("admin_service", "validate_user_input")
async def validate_user_input(self, input_text: str) -> int: async def validate_user_input(self, input_text: str) -> int:
"""Валидация введенного ID пользователя""" """Валидация введенного ID пользователя"""
try: try:
@@ -141,6 +163,8 @@ class AdminService:
except ValueError: except ValueError:
raise InvalidInputError("ID пользователя должен быть числом") raise InvalidInputError("ID пользователя должен быть числом")
@track_time("get_banned_users_for_display", "admin_service")
@track_errors("admin_service", "get_banned_users_for_display")
async def get_banned_users_for_display(self, page: int = 0) -> tuple[str, list]: async def get_banned_users_for_display(self, page: int = 0) -> tuple[str, list]:
"""Получить данные заблокированных пользователей для отображения""" """Получить данные заблокированных пользователей для отображения"""
try: try:

View File

@@ -24,10 +24,20 @@ from .constants import (
) )
from logs.custom_logger import logger from logs.custom_logger import logger
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors,
db_query_time
)
callback_router = Router() callback_router = Router()
@callback_router.callback_query(F.data == CALLBACK_PUBLISH) @callback_router.callback_query(F.data == CALLBACK_PUBLISH)
@track_time("post_for_group", "callback_handlers")
@track_errors("callback_handlers", "post_for_group")
async def post_for_group( async def post_for_group(
call: CallbackQuery, call: CallbackQuery,
settings: MagicData("settings") settings: MagicData("settings")
@@ -59,6 +69,8 @@ async def post_for_group(
@callback_router.callback_query(F.data == CALLBACK_DECLINE) @callback_router.callback_query(F.data == CALLBACK_DECLINE)
@track_time("decline_post_for_group", "callback_handlers")
@track_errors("callback_handlers", "decline_post_for_group")
async def decline_post_for_group( async def decline_post_for_group(
call: CallbackQuery, call: CallbackQuery,
settings: MagicData("settings") settings: MagicData("settings")
@@ -89,6 +101,8 @@ async def decline_post_for_group(
@callback_router.callback_query(F.data == CALLBACK_BAN) @callback_router.callback_query(F.data == CALLBACK_BAN)
@track_time("ban_user_from_post", "callback_handlers")
@track_errors("callback_handlers", "ban_user_from_post")
async def ban_user_from_post(call: CallbackQuery, **kwargs): async def ban_user_from_post(call: CallbackQuery, **kwargs):
ban_service = get_ban_service() ban_service = get_ban_service()
# TODO: переделать на MagicData # TODO: переделать на MagicData
@@ -109,6 +123,8 @@ async def ban_user_from_post(call: CallbackQuery, **kwargs):
@callback_router.callback_query(F.data.contains(CALLBACK_BAN)) @callback_router.callback_query(F.data.contains(CALLBACK_BAN))
@track_time("process_ban_user", "callback_handlers")
@track_errors("callback_handlers", "process_ban_user")
async def process_ban_user(call: CallbackQuery, state: FSMContext, **kwargs): async def process_ban_user(call: CallbackQuery, state: FSMContext, **kwargs):
ban_service = get_ban_service() ban_service = get_ban_service()
# TODO: переделать на MagicData # TODO: переделать на MagicData
@@ -142,6 +158,8 @@ async def process_ban_user(call: CallbackQuery, state: FSMContext, **kwargs):
@callback_router.callback_query(F.data.contains(CALLBACK_UNLOCK)) @callback_router.callback_query(F.data.contains(CALLBACK_UNLOCK))
@track_time("process_unlock_user", "callback_handlers")
@track_errors("callback_handlers", "process_unlock_user")
async def process_unlock_user(call: CallbackQuery, **kwargs): async def process_unlock_user(call: CallbackQuery, **kwargs):
ban_service = get_ban_service() ban_service = get_ban_service()
# TODO: переделать на MagicData # TODO: переделать на MagicData
@@ -166,6 +184,8 @@ async def process_unlock_user(call: CallbackQuery, **kwargs):
@callback_router.callback_query(F.data == CALLBACK_RETURN) @callback_router.callback_query(F.data == CALLBACK_RETURN)
@track_time("return_to_main_menu", "callback_handlers")
@track_errors("callback_handlers", "return_to_main_menu")
async def return_to_main_menu(call: CallbackQuery, **kwargs): async def return_to_main_menu(call: CallbackQuery, **kwargs):
await call.message.delete() await call.message.delete()
logger.info(f"Запуск админ панели для пользователя: {call.message.from_user.id}") logger.info(f"Запуск админ панели для пользователя: {call.message.from_user.id}")
@@ -174,6 +194,8 @@ async def return_to_main_menu(call: CallbackQuery, **kwargs):
@callback_router.callback_query(F.data.contains(CALLBACK_PAGE)) @callback_router.callback_query(F.data.contains(CALLBACK_PAGE))
@track_time("change_page", "callback_handlers")
@track_errors("callback_handlers", "change_page")
async def change_page( async def change_page(
call: CallbackQuery, call: CallbackQuery,
bot_db: MagicData("bot_db"), bot_db: MagicData("bot_db"),
@@ -214,6 +236,8 @@ async def change_page(
@callback_router.callback_query(F.data == CALLBACK_SAVE) @callback_router.callback_query(F.data == CALLBACK_SAVE)
@track_time("save_voice_message", "callback_handlers")
@track_errors("callback_handlers", "save_voice_message")
async def save_voice_message( async def save_voice_message(
call: CallbackQuery, call: CallbackQuery,
bot_db: MagicData("bot_db"), bot_db: MagicData("bot_db"),
@@ -257,6 +281,8 @@ async def save_voice_message(
@callback_router.callback_query(F.data == CALLBACK_DELETE) @callback_router.callback_query(F.data == CALLBACK_DELETE)
@track_time("delete_voice_message", "callback_handlers")
@track_errors("callback_handlers", "delete_voice_message")
async def delete_voice_message( async def delete_voice_message(
call: CallbackQuery, call: CallbackQuery,
bot_db: MagicData("bot_db"), bot_db: MagicData("bot_db"),

View File

@@ -23,6 +23,14 @@ from .constants import (
) )
from logs.custom_logger import logger from logs.custom_logger import logger
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors,
db_query_time
)
class PostPublishService: class PostPublishService:
def __init__(self, bot: Bot, db, settings: Dict[str, Any]): def __init__(self, bot: Bot, db, settings: Dict[str, Any]):
@@ -40,6 +48,8 @@ class PostPublishService:
return self.bot return self.bot
return message.bot return message.bot
@track_time("publish_post", "post_publish_service")
@track_errors("post_publish_service", "publish_post")
async def publish_post(self, call: CallbackQuery) -> None: async def publish_post(self, call: CallbackQuery) -> None:
"""Основной метод публикации поста""" """Основной метод публикации поста"""
# Проверяем, является ли сообщение частью медиагруппы # Проверяем, является ли сообщение частью медиагруппы
@@ -64,6 +74,8 @@ class PostPublishService:
else: else:
raise PublishError(f"Неподдерживаемый тип контента: {content_type}") raise PublishError(f"Неподдерживаемый тип контента: {content_type}")
@track_time("_publish_text_post", "post_publish_service")
@track_errors("post_publish_service", "_publish_text_post")
async def _publish_text_post(self, call: CallbackQuery) -> None: async def _publish_text_post(self, call: CallbackQuery) -> None:
"""Публикация текстового поста""" """Публикация текстового поста"""
text_post = html.escape(str(call.message.text)) text_post = html.escape(str(call.message.text))
@@ -73,6 +85,8 @@ class PostPublishService:
await self._delete_post_and_notify_author(call, author_id) await self._delete_post_and_notify_author(call, author_id)
logger.info(f'Текст сообщения опубликован в канале {self.main_public}.') logger.info(f'Текст сообщения опубликован в канале {self.main_public}.')
@track_time("_publish_photo_post", "post_publish_service")
@track_errors("post_publish_service", "_publish_photo_post")
async def _publish_photo_post(self, call: CallbackQuery) -> None: async def _publish_photo_post(self, call: CallbackQuery) -> None:
"""Публикация поста с фото""" """Публикация поста с фото"""
text_post_with_photo = html.escape(str(call.message.caption)) text_post_with_photo = html.escape(str(call.message.caption))
@@ -82,6 +96,8 @@ class PostPublishService:
await self._delete_post_and_notify_author(call, author_id) await self._delete_post_and_notify_author(call, author_id)
logger.info(f'Пост с фото опубликован в канале {self.main_public}.') logger.info(f'Пост с фото опубликован в канале {self.main_public}.')
@track_time("_publish_video_post", "post_publish_service")
@track_errors("post_publish_service", "_publish_video_post")
async def _publish_video_post(self, call: CallbackQuery) -> None: async def _publish_video_post(self, call: CallbackQuery) -> None:
"""Публикация поста с видео""" """Публикация поста с видео"""
text_post_with_photo = html.escape(str(call.message.caption)) text_post_with_photo = html.escape(str(call.message.caption))
@@ -91,6 +107,8 @@ class PostPublishService:
await self._delete_post_and_notify_author(call, author_id) await self._delete_post_and_notify_author(call, author_id)
logger.info(f'Пост с видео опубликован в канале {self.main_public}.') logger.info(f'Пост с видео опубликован в канале {self.main_public}.')
@track_time("_publish_video_note_post", "post_publish_service")
@track_errors("post_publish_service", "_publish_video_note_post")
async def _publish_video_note_post(self, call: CallbackQuery) -> None: async def _publish_video_note_post(self, call: CallbackQuery) -> None:
"""Публикация поста с кружком""" """Публикация поста с кружком"""
author_id = await self._get_author_id(call.message.message_id) author_id = await self._get_author_id(call.message.message_id)
@@ -99,6 +117,8 @@ class PostPublishService:
await self._delete_post_and_notify_author(call, author_id) await self._delete_post_and_notify_author(call, author_id)
logger.info(f'Пост с кружком опубликован в канале {self.main_public}.') logger.info(f'Пост с кружком опубликован в канале {self.main_public}.')
@track_time("_publish_audio_post", "post_publish_service")
@track_errors("post_publish_service", "_publish_audio_post")
async def _publish_audio_post(self, call: CallbackQuery) -> None: async def _publish_audio_post(self, call: CallbackQuery) -> None:
"""Публикация поста с аудио""" """Публикация поста с аудио"""
text_post_with_photo = html.escape(str(call.message.caption)) text_post_with_photo = html.escape(str(call.message.caption))
@@ -108,6 +128,8 @@ class PostPublishService:
await self._delete_post_and_notify_author(call, author_id) await self._delete_post_and_notify_author(call, author_id)
logger.info(f'Пост с аудио опубликован в канале {self.main_public}.') logger.info(f'Пост с аудио опубликован в канале {self.main_public}.')
@track_time("_publish_voice_post", "post_publish_service")
@track_errors("post_publish_service", "_publish_voice_post")
async def _publish_voice_post(self, call: CallbackQuery) -> None: async def _publish_voice_post(self, call: CallbackQuery) -> None:
"""Публикация поста с войсом""" """Публикация поста с войсом"""
author_id = await self._get_author_id(call.message.message_id) author_id = await self._get_author_id(call.message.message_id)
@@ -116,6 +138,8 @@ class PostPublishService:
await self._delete_post_and_notify_author(call, author_id) await self._delete_post_and_notify_author(call, author_id)
logger.info(f'Пост с войсом опубликован в канале {self.main_public}.') logger.info(f'Пост с войсом опубликован в канале {self.main_public}.')
@track_time("_publish_media_group", "post_publish_service")
@track_errors("post_publish_service", "_publish_media_group")
async def _publish_media_group(self, call: CallbackQuery) -> None: async def _publish_media_group(self, call: CallbackQuery) -> None:
"""Публикация медиагруппы""" """Публикация медиагруппы"""
logger.info(f"Начинаю публикацию медиагруппы. Helper message ID: {call.message.message_id}") logger.info(f"Начинаю публикацию медиагруппы. Helper message ID: {call.message.message_id}")
@@ -161,6 +185,8 @@ class PostPublishService:
logger.error(f"Ошибка при публикации медиагруппы: {e}") logger.error(f"Ошибка при публикации медиагруппы: {e}")
raise PublishError(f"Не удалось опубликовать медиагруппу: {str(e)}") raise PublishError(f"Не удалось опубликовать медиагруппу: {str(e)}")
@track_time("decline_post", "post_publish_service")
@track_errors("post_publish_service", "decline_post")
async def decline_post(self, call: CallbackQuery) -> None: async def decline_post(self, call: CallbackQuery) -> None:
"""Отклонение поста""" """Отклонение поста"""
logger.info(f"Начинаю отклонение поста. Message ID: {call.message.message_id}, Content type: {call.message.content_type}") logger.info(f"Начинаю отклонение поста. Message ID: {call.message.message_id}, Content type: {call.message.content_type}")
@@ -180,6 +206,8 @@ class PostPublishService:
logger.error(f"Неподдерживаемый тип контента для отклонения: {content_type}") logger.error(f"Неподдерживаемый тип контента для отклонения: {content_type}")
raise PublishError(f"Неподдерживаемый тип контента для отклонения: {content_type}") raise PublishError(f"Неподдерживаемый тип контента для отклонения: {content_type}")
@track_time("_decline_single_post", "post_publish_service")
@track_errors("post_publish_service", "_decline_single_post")
async def _decline_single_post(self, call: CallbackQuery) -> None: async def _decline_single_post(self, call: CallbackQuery) -> None:
"""Отклонение одиночного поста""" """Отклонение одиночного поста"""
logger.debug(f"Отклоняю одиночный пост. Message ID: {call.message.message_id}") logger.debug(f"Отклоняю одиночный пост. Message ID: {call.message.message_id}")
@@ -200,6 +228,8 @@ class PostPublishService:
raise raise
logger.info(f'Сообщение отклонено админом {call.from_user.full_name} (ID: {call.from_user.id}).') logger.info(f'Сообщение отклонено админом {call.from_user.full_name} (ID: {call.from_user.id}).')
@track_time("_decline_media_group", "post_publish_service")
@track_errors("post_publish_service", "_decline_media_group")
async def _decline_media_group(self, call: CallbackQuery) -> None: async def _decline_media_group(self, call: CallbackQuery) -> None:
"""Отклонение медиагруппы""" """Отклонение медиагруппы"""
logger.debug(f"Отклоняю медиагруппу. Helper message ID: {call.message.message_id}") logger.debug(f"Отклоняю медиагруппу. Helper message ID: {call.message.message_id}")
@@ -225,6 +255,8 @@ class PostPublishService:
logger.error(f"Ошибка при отправке уведомления автору медиагруппы {author_id}: {e}") logger.error(f"Ошибка при отправке уведомления автору медиагруппы {author_id}: {e}")
raise raise
@track_time("_get_author_id", "post_publish_service")
@track_errors("post_publish_service", "_get_author_id")
async def _get_author_id(self, message_id: int) -> int: async def _get_author_id(self, message_id: int) -> int:
"""Получение ID автора по ID сообщения""" """Получение ID автора по ID сообщения"""
author_id = await self.db.get_author_id_by_message_id(message_id) author_id = await self.db.get_author_id_by_message_id(message_id)
@@ -232,6 +264,8 @@ class PostPublishService:
raise PostNotFoundError(f"Автор не найден для сообщения {message_id}") raise PostNotFoundError(f"Автор не найден для сообщения {message_id}")
return author_id return author_id
@track_time("_get_author_id_for_media_group", "post_publish_service")
@track_errors("post_publish_service", "_get_author_id_for_media_group")
async def _get_author_id_for_media_group(self, message_id: int) -> int: async def _get_author_id_for_media_group(self, message_id: int) -> int:
"""Получение ID автора для медиагруппы""" """Получение ID автора для медиагруппы"""
# Сначала пытаемся найти автора по helper_message_id # Сначала пытаемся найти автора по helper_message_id
@@ -259,6 +293,8 @@ class PostPublishService:
raise PostNotFoundError(f"Автор не найден для медиагруппы {message_id}") raise PostNotFoundError(f"Автор не найден для медиагруппы {message_id}")
return author_id return author_id
@track_time("_delete_post_and_notify_author", "post_publish_service")
@track_errors("post_publish_service", "_delete_post_and_notify_author")
async def _delete_post_and_notify_author(self, call: CallbackQuery, author_id: int) -> None: async def _delete_post_and_notify_author(self, call: CallbackQuery, author_id: int) -> None:
"""Удаление поста и уведомление автора""" """Удаление поста и уведомление автора"""
await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id) await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
@@ -270,6 +306,8 @@ class PostPublishService:
raise UserBlockedBotError("Пользователь заблокировал бота") raise UserBlockedBotError("Пользователь заблокировал бота")
raise raise
@track_time("_delete_media_group_and_notify_author", "post_publish_service")
@track_errors("post_publish_service", "_delete_media_group_and_notify_author")
async def _delete_media_group_and_notify_author(self, call: CallbackQuery, author_id: int) -> None: async def _delete_media_group_and_notify_author(self, call: CallbackQuery, author_id: int) -> None:
"""Удаление медиагруппы и уведомление автора""" """Удаление медиагруппы и уведомление автора"""
post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id) post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id)
@@ -293,6 +331,8 @@ class BanService:
self.group_for_posts = settings['Telegram']['group_for_posts'] self.group_for_posts = settings['Telegram']['group_for_posts']
self.important_logs = settings['Telegram']['important_logs'] self.important_logs = settings['Telegram']['important_logs']
@track_time("ban_user_from_post", "ban_service")
@track_errors("ban_service", "ban_user_from_post")
async def ban_user_from_post(self, call: CallbackQuery) -> None: async def ban_user_from_post(self, call: CallbackQuery) -> None:
"""Бан пользователя за спам""" """Бан пользователя за спам"""
author_id = await self.db.get_author_id_by_message_id(call.message.message_id) author_id = await self.db.get_author_id_by_message_id(call.message.message_id)
@@ -321,6 +361,8 @@ class BanService:
logger.info(f"Пользователь {author_id} заблокирован за спам до {date_str}") logger.info(f"Пользователь {author_id} заблокирован за спам до {date_str}")
@track_time("ban_user", "ban_service")
@track_errors("ban_service", "ban_user")
async def ban_user(self, user_id: str, user_name: str) -> str: async def ban_user(self, user_id: str, user_name: str) -> str:
"""Бан пользователя по ID""" """Бан пользователя по ID"""
user_name = await self.db.get_username(int(user_id)) user_name = await self.db.get_username(int(user_id))
@@ -329,6 +371,8 @@ class BanService:
return user_name return user_name
@track_time("unlock_user", "ban_service")
@track_errors("ban_service", "unlock_user")
async def unlock_user(self, user_id: str) -> str: async def unlock_user(self, user_id: str) -> str:
"""Разблокировка пользователя""" """Разблокировка пользователя"""
user_name = await self.db.get_username(int(user_id)) user_name = await self.db.get_username(int(user_id))

View File

@@ -46,6 +46,8 @@ class GroupHandlers:
) )
@error_handler @error_handler
@track_errors("group_handlers", "handle_message")
@track_time("handle_message", "group_handlers")
async def handle_message(self, message: types.Message, state: FSMContext, **kwargs): async def handle_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle admin reply to user through group chat""" """Handle admin reply to user through group chat"""

View File

@@ -32,6 +32,8 @@ class AdminReplyService:
def __init__(self, db: DatabaseProtocol) -> None: def __init__(self, db: DatabaseProtocol) -> None:
self.db = db self.db = db
@track_time("get_user_id_for_reply", "admin_reply_service")
@track_errors("admin_reply_service", "get_user_id_for_reply")
async def get_user_id_for_reply(self, message_id: int) -> int: async def get_user_id_for_reply(self, message_id: int) -> int:
""" """
Get user ID for reply by message ID. Get user ID for reply by message ID.
@@ -50,6 +52,8 @@ class AdminReplyService:
raise UserNotFoundError(f"User not found for message_id: {message_id}") raise UserNotFoundError(f"User not found for message_id: {message_id}")
return user_id return user_id
@track_time("send_reply_to_user", "admin_reply_service")
@track_errors("admin_reply_service", "send_reply_to_user")
async def send_reply_to_user( async def send_reply_to_user(
self, self,
chat_id: int, chat_id: int,

View File

@@ -29,7 +29,8 @@ from helper_bot.utils.helper_func import (
from helper_bot.utils.metrics import ( from helper_bot.utils.metrics import (
metrics, metrics,
track_time, track_time,
track_errors track_errors,
db_query_time
) )
# Local imports - modular components # Local imports - modular components
@@ -81,6 +82,8 @@ class PrivateHandlers:
self.router.message.register(self.resend_message_in_group_for_message, StateFilter(FSM_STATES["CHAT"]), ChatTypeFilter(chat_type=["private"])) self.router.message.register(self.resend_message_in_group_for_message, StateFilter(FSM_STATES["CHAT"]), ChatTypeFilter(chat_type=["private"]))
@error_handler @error_handler
@track_errors("private_handlers", "handle_emoji_message")
@track_time("handle_emoji_message", "private_handlers")
async def handle_emoji_message(self, message: types.Message, state: FSMContext, **kwargs): async def handle_emoji_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle emoji command""" """Handle emoji command"""
await self.user_service.log_user_message(message) await self.user_service.log_user_message(message)
@@ -90,6 +93,8 @@ class PrivateHandlers:
await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML') await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML')
@error_handler @error_handler
@track_errors("private_handlers", "handle_restart_message")
@track_time("handle_restart_message", "private_handlers")
async def handle_restart_message(self, message: types.Message, state: FSMContext, **kwargs): async def handle_restart_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle restart command""" """Handle restart command"""
markup = await get_reply_keyboard(self.db, message.from_user.id) markup = await get_reply_keyboard(self.db, message.from_user.id)
@@ -100,6 +105,8 @@ class PrivateHandlers:
await message.answer('Я перезапущен!', reply_markup=markup, parse_mode='HTML') await message.answer('Я перезапущен!', reply_markup=markup, parse_mode='HTML')
@error_handler @error_handler
@track_errors("private_handlers", "handle_start_message")
@track_time("handle_start_message", "private_handlers")
async def handle_start_message(self, message: types.Message, state: FSMContext, **kwargs): async def handle_start_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle start command and return to bot button with metrics tracking""" """Handle start command and return to bot button with metrics tracking"""
# User service operations with metrics # User service operations with metrics
@@ -116,6 +123,8 @@ class PrivateHandlers:
await message.answer(hello_message, reply_markup=markup, parse_mode='HTML') await message.answer(hello_message, reply_markup=markup, parse_mode='HTML')
@error_handler @error_handler
@track_errors("private_handlers", "suggest_post")
@track_time("suggest_post", "private_handlers")
async def suggest_post(self, message: types.Message, state: FSMContext, **kwargs): async def suggest_post(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle suggest post button""" """Handle suggest post button"""
# User service operations with metrics # User service operations with metrics
@@ -128,6 +137,8 @@ class PrivateHandlers:
await message.answer(suggest_news, reply_markup=markup) await message.answer(suggest_news, reply_markup=markup)
@error_handler @error_handler
@track_errors("private_handlers", "end_message")
@track_time("end_message", "private_handlers")
async def end_message(self, message: types.Message, state: FSMContext, **kwargs): async def end_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle goodbye button""" """Handle goodbye button"""
# User service operations with metrics # User service operations with metrics
@@ -144,6 +155,8 @@ class PrivateHandlers:
await state.set_state(FSM_STATES["START"]) await state.set_state(FSM_STATES["START"])
@error_handler @error_handler
@track_errors("private_handlers", "suggest_router")
@track_time("suggest_router", "private_handlers")
async def suggest_router(self, message: types.Message, state: FSMContext, album: list = None, **kwargs): async def suggest_router(self, message: types.Message, state: FSMContext, album: list = None, **kwargs):
"""Handle post submission in suggest state""" """Handle post submission in suggest state"""
# Post service operations with metrics # Post service operations with metrics
@@ -158,6 +171,8 @@ class PrivateHandlers:
await state.set_state(FSM_STATES["START"]) await state.set_state(FSM_STATES["START"])
@error_handler @error_handler
@track_errors("private_handlers", "stickers")
@track_time("stickers", "private_handlers")
async def stickers(self, message: types.Message, state: FSMContext, **kwargs): async def stickers(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle stickers request""" """Handle stickers request"""
# User service operations with metrics # User service operations with metrics
@@ -171,6 +186,8 @@ class PrivateHandlers:
await state.set_state(FSM_STATES["START"]) await state.set_state(FSM_STATES["START"])
@error_handler @error_handler
@track_errors("private_handlers", "connect_with_admin")
@track_time("connect_with_admin", "private_handlers")
async def connect_with_admin(self, message: types.Message, state: FSMContext, **kwargs): async def connect_with_admin(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle connect with admin button""" """Handle connect with admin button"""
# User service operations with metrics # User service operations with metrics
@@ -181,6 +198,8 @@ class PrivateHandlers:
await state.set_state(FSM_STATES["PRE_CHAT"]) await state.set_state(FSM_STATES["PRE_CHAT"])
@error_handler @error_handler
@track_errors("private_handlers", "resend_message_in_group_for_message")
@track_time("resend_message_in_group_for_message", "private_handlers")
async def resend_message_in_group_for_message(self, message: types.Message, state: FSMContext, **kwargs): async def resend_message_in_group_for_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle messages in admin chat states""" """Handle messages in admin chat states"""
# User service operations with metrics # User service operations with metrics

View File

@@ -74,7 +74,6 @@ class UserService:
@track_time("update_user_activity", "user_service") @track_time("update_user_activity", "user_service")
@track_errors("user_service", "update_user_activity") @track_errors("user_service", "update_user_activity")
@db_query_time("update_user_activity", "user_service")
async def update_user_activity(self, user_id: int) -> None: async def update_user_activity(self, user_id: int) -> None:
"""Update user's last activity timestamp with metrics tracking""" """Update user's last activity timestamp with metrics tracking"""
await self.db.update_user_date(user_id) await self.db.update_user_date(user_id)

View File

@@ -14,6 +14,13 @@ from helper_bot.handlers.voice.constants import (
) )
from logs.custom_logger import logger from logs.custom_logger import logger
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors,
db_query_time
)
class VoiceMessage: class VoiceMessage:
"""Модель голосового сообщения""" """Модель голосового сообщения"""
@@ -30,6 +37,8 @@ class VoiceBotService:
self.bot_db = bot_db self.bot_db = bot_db
self.settings = settings self.settings = settings
@track_time("get_welcome_sticker", "voice_bot_service")
@track_errors("voice_bot_service", "get_welcome_sticker")
async def get_welcome_sticker(self) -> Optional[FSInputFile]: async def get_welcome_sticker(self) -> Optional[FSInputFile]:
"""Получить случайный приветственный стикер""" """Получить случайный приветственный стикер"""
try: try:
@@ -47,6 +56,8 @@ class VoiceBotService:
await self._send_error_to_logs(f'Отправка приветственных стикеров лажает. Ошибка: {e}') await self._send_error_to_logs(f'Отправка приветственных стикеров лажает. Ошибка: {e}')
return None return None
@track_time("send_welcome_messages", "voice_bot_service")
@track_errors("voice_bot_service", "send_welcome_messages")
async def send_welcome_messages(self, message, user_emoji: str): async def send_welcome_messages(self, message, user_emoji: str):
"""Отправить приветственные сообщения""" """Отправить приветственные сообщения"""
try: try:
@@ -141,6 +152,8 @@ class VoiceBotService:
logger.error(f"Ошибка при отправке приветственных сообщений: {e}") logger.error(f"Ошибка при отправке приветственных сообщений: {e}")
raise VoiceMessageError(f"Не удалось отправить приветственные сообщения: {e}") raise VoiceMessageError(f"Не удалось отправить приветственные сообщения: {e}")
@track_time("get_random_audio", "voice_bot_service")
@track_errors("voice_bot_service", "get_random_audio")
async def get_random_audio(self, user_id: int) -> Optional[Tuple[str, str, str]]: async def get_random_audio(self, user_id: int) -> Optional[Tuple[str, str, str]]:
"""Получить случайное аудио для прослушивания""" """Получить случайное аудио для прослушивания"""
try: try:
@@ -165,6 +178,8 @@ class VoiceBotService:
logger.error(f"Ошибка при получении случайного аудио: {e}") logger.error(f"Ошибка при получении случайного аудио: {e}")
raise AudioProcessingError(f"Не удалось получить случайное аудио: {e}") raise AudioProcessingError(f"Не удалось получить случайное аудио: {e}")
@track_time("mark_audio_as_listened", "voice_bot_service")
@track_errors("voice_bot_service", "mark_audio_as_listened")
async def mark_audio_as_listened(self, file_name: str, user_id: int) -> None: async def mark_audio_as_listened(self, file_name: str, user_id: int) -> None:
"""Пометить аудио как прослушанное""" """Пометить аудио как прослушанное"""
try: try:
@@ -173,6 +188,8 @@ class VoiceBotService:
logger.error(f"Ошибка при пометке аудио как прослушанного: {e}") logger.error(f"Ошибка при пометке аудио как прослушанного: {e}")
raise DatabaseError(f"Не удалось пометить аудио как прослушанное: {e}") raise DatabaseError(f"Не удалось пометить аудио как прослушанное: {e}")
@track_time("clear_user_listenings", "voice_bot_service")
@track_errors("voice_bot_service", "clear_user_listenings")
async def clear_user_listenings(self, user_id: int) -> None: async def clear_user_listenings(self, user_id: int) -> None:
"""Очистить прослушивания пользователя""" """Очистить прослушивания пользователя"""
try: try:
@@ -181,6 +198,8 @@ class VoiceBotService:
logger.error(f"Ошибка при очистке прослушиваний: {e}") logger.error(f"Ошибка при очистке прослушиваний: {e}")
raise DatabaseError(f"Не удалось очистить прослушивания: {e}") raise DatabaseError(f"Не удалось очистить прослушивания: {e}")
@track_time("get_remaining_audio_count", "voice_bot_service")
@track_errors("voice_bot_service", "get_remaining_audio_count")
async def get_remaining_audio_count(self, user_id: int) -> int: async def get_remaining_audio_count(self, user_id: int) -> int:
"""Получить количество оставшихся непрослушанных аудио""" """Получить количество оставшихся непрослушанных аудио"""
try: try:
@@ -190,11 +209,15 @@ class VoiceBotService:
logger.error(f"Ошибка при получении количества аудио: {e}") logger.error(f"Ошибка при получении количества аудио: {e}")
raise DatabaseError(f"Не удалось получить количество аудио: {e}") raise DatabaseError(f"Не удалось получить количество аудио: {e}")
@track_time("get_main_keyboard", "voice_bot_service")
@track_errors("voice_bot_service", "get_main_keyboard")
def _get_main_keyboard(self): def _get_main_keyboard(self):
"""Получить основную клавиатуру""" """Получить основную клавиатуру"""
from helper_bot.keyboards.keyboards import get_main_keyboard from helper_bot.keyboards.keyboards import get_main_keyboard
return get_main_keyboard() return get_main_keyboard()
@track_time("send_error_to_logs", "voice_bot_service")
@track_errors("voice_bot_service", "send_error_to_logs")
async def _send_error_to_logs(self, message: str) -> None: async def _send_error_to_logs(self, message: str) -> None:
"""Отправить ошибку в логи""" """Отправить ошибку в логи"""
try: try:
@@ -215,6 +238,8 @@ class AudioFileService:
def __init__(self, bot_db): def __init__(self, bot_db):
self.bot_db = bot_db self.bot_db = bot_db
@track_time("generate_file_name", "audio_file_service")
@track_errors("audio_file_service", "generate_file_name")
async def generate_file_name(self, user_id: int) -> str: async def generate_file_name(self, user_id: int) -> str:
"""Сгенерировать имя файла для аудио""" """Сгенерировать имя файла для аудио"""
try: try:
@@ -245,6 +270,8 @@ class AudioFileService:
logger.error(f"Ошибка при генерации имени файла: {e}") logger.error(f"Ошибка при генерации имени файла: {e}")
raise FileOperationError(f"Не удалось сгенерировать имя файла: {e}") raise FileOperationError(f"Не удалось сгенерировать имя файла: {e}")
@track_time("save_audio_file", "audio_file_service")
@track_errors("audio_file_service", "save_audio_file")
async def save_audio_file(self, file_name: str, user_id: int, date_added: datetime, file_id: str) -> None: async def save_audio_file(self, file_name: str, user_id: int, date_added: datetime, file_id: str) -> None:
"""Сохранить информацию об аудио файле в базу данных""" """Сохранить информацию об аудио файле в базу данных"""
try: try:
@@ -253,6 +280,8 @@ class AudioFileService:
logger.error(f"Ошибка при сохранении аудио файла в БД: {e}") logger.error(f"Ошибка при сохранении аудио файла в БД: {e}")
raise DatabaseError(f"Не удалось сохранить аудио файл в БД: {e}") raise DatabaseError(f"Не удалось сохранить аудио файл в БД: {e}")
@track_time("download_and_save_audio", "audio_file_service")
@track_errors("audio_file_service", "download_and_save_audio")
async def download_and_save_audio(self, bot, message, file_name: str) -> None: async def download_and_save_audio(self, bot, message, file_name: str) -> None:
"""Скачать и сохранить аудио файл""" """Скачать и сохранить аудио файл"""
try: try:

View File

@@ -22,6 +22,13 @@ from helper_bot.keyboards import get_reply_keyboard
from helper_bot.handlers.private.constants import FSM_STATES from helper_bot.handlers.private.constants import FSM_STATES
from helper_bot.handlers.private.constants import BUTTON_TEXTS from helper_bot.handlers.private.constants import BUTTON_TEXTS
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors,
db_query_time
)
class VoiceHandlers: class VoiceHandlers:
def __init__(self, db, settings): def __init__(self, db, settings):
@@ -115,6 +122,8 @@ class VoiceHandlers:
F.text == "😊Узнать эмодзи" F.text == "😊Узнать эмодзи"
) )
@track_time("voice_bot_button_handler", "voice_handlers")
@track_errors("voice_handlers", "voice_bot_button_handler")
async def voice_bot_button_handler(self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings")): async def voice_bot_button_handler(self, message: types.Message, state: FSMContext, bot_db: MagicData("bot_db"), settings: MagicData("settings")):
"""Обработчик кнопки 'Голосовой бот' из основной клавиатуры""" """Обработчик кнопки 'Голосовой бот' из основной клавиатуры"""
try: try:
@@ -135,6 +144,8 @@ class VoiceHandlers:
# В случае ошибки вызываем start # В случае ошибки вызываем start
await self.start(message, state, bot_db, settings) await self.start(message, state, bot_db, settings)
@track_time("restart_function", "voice_handlers")
@track_errors("voice_handlers", "restart_function")
async def restart_function( async def restart_function(
self, self,
message: types.Message, message: types.Message,
@@ -150,6 +161,8 @@ class VoiceHandlers:
await message.answer(text='🎤 Записывайся или слушай!', reply_markup=markup) await message.answer(text='🎤 Записывайся или слушай!', reply_markup=markup)
await state.set_state(STATE_START) await state.set_state(STATE_START)
@track_time("handle_emoji_message", "voice_handlers")
@track_errors("voice_handlers", "handle_emoji_message")
async def handle_emoji_message( async def handle_emoji_message(
self, self,
message: types.Message, message: types.Message,
@@ -162,6 +175,8 @@ class VoiceHandlers:
if user_emoji is not None: if user_emoji is not None:
await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML') await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML')
@track_time("help_function", "voice_handlers")
@track_errors("voice_handlers", "help_function")
async def help_function( async def help_function(
self, self,
message: types.Message, message: types.Message,
@@ -177,6 +192,8 @@ class VoiceHandlers:
) )
await state.set_state(STATE_START) await state.set_state(STATE_START)
@track_time("start", "voice_handlers")
@track_errors("voice_handlers", "start")
async def start( async def start(
self, self,
message: types.Message, message: types.Message,
@@ -201,6 +218,8 @@ class VoiceHandlers:
except Exception as e: except Exception as e:
logger.error(f"Ошибка при отметке получения приветствия: {e}") logger.error(f"Ошибка при отметке получения приветствия: {e}")
@track_time("cancel_handler", "voice_handlers")
@track_errors("voice_handlers", "cancel_handler")
async def cancel_handler( async def cancel_handler(
self, self,
message: types.Message, message: types.Message,
@@ -215,6 +234,8 @@ class VoiceHandlers:
await message.answer(text='Добро пожаловать в меню!', reply_markup=markup, parse_mode='HTML') await message.answer(text='Добро пожаловать в меню!', reply_markup=markup, parse_mode='HTML')
await state.set_state(FSM_STATES["START"]) await state.set_state(FSM_STATES["START"])
@track_time("refresh_listen_function", "voice_handlers")
@track_errors("voice_handlers", "refresh_listen_function")
async def refresh_listen_function( async def refresh_listen_function(
self, self,
message: types.Message, message: types.Message,
@@ -239,6 +260,8 @@ class VoiceHandlers:
await state.set_state(STATE_START) await state.set_state(STATE_START)
@track_time("standup_write", "voice_handlers")
@track_errors("voice_handlers", "standup_write")
async def standup_write( async def standup_write(
self, self,
message: types.Message, message: types.Message,
@@ -261,6 +284,8 @@ class VoiceHandlers:
await state.set_state(STATE_STANDUP_WRITE) await state.set_state(STATE_STANDUP_WRITE)
@track_time("suggest_voice", "voice_handlers")
@track_errors("voice_handlers", "suggest_voice")
async def suggest_voice( async def suggest_voice(
self, self,
message: types.Message, message: types.Message,
@@ -299,6 +324,8 @@ class VoiceHandlers:
await state.set_state(STATE_STANDUP_WRITE) await state.set_state(STATE_STANDUP_WRITE)
@track_time("standup_listen_audio", "voice_handlers")
@track_errors("voice_handlers", "standup_listen_audio")
async def standup_listen_audio( async def standup_listen_audio(
self, self,
message: types.Message, message: types.Message,

View File

@@ -81,12 +81,8 @@ async def start_bot(bdf):
# Запускаем метрики сервер # Запускаем метрики сервер
await start_metrics_server(metrics_host, metrics_port) await start_metrics_server(metrics_host, metrics_port)
# Запускаем планировщик метрик для периодического обновления
from .utils.metrics_scheduler import start_metrics_scheduler
await start_metrics_scheduler()
logging.info(f"✅ Метрики сервер запущен на {metrics_host}:{metrics_port}") logging.info(f"✅ Метрики сервер запущен на {metrics_host}:{metrics_port}")
logging.info("Планировщик метрик запущен") logging.info("Метрики будут обновляться в реальном времени через middleware")
# Запускаем бота с retry логикой # Запускаем бота с retry логикой
await start_bot_with_retry(bot, dp) await start_bot_with_retry(bot, dp)

View File

@@ -162,7 +162,7 @@ class MetricsMiddleware(BaseMiddleware):
await bot_db.cursor.execute(total_users_query) await bot_db.cursor.execute(total_users_query)
total_users_result = await bot_db.cursor.fetchone() total_users_result = await bot_db.cursor.fetchone()
total_users = total_users_result[0] if total_users_result else 1 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'))" 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) await bot_db.cursor.execute(daily_users_query)
daily_users_result = await bot_db.cursor.fetchone() daily_users_result = await bot_db.cursor.fetchone()
daily_users = daily_users_result[0] if daily_users_result else 1 daily_users = daily_users_result[0] if daily_users_result else 1
@@ -328,8 +328,21 @@ class MetricsMiddleware(BaseMiddleware):
# FALLBACK: Record unknown callback # FALLBACK: Record unknown callback
if parts: if parts:
callback_data = parts[0]
# Группируем похожие callback'и по паттернам
if callback_data.startswith("ban_") and callback_data[4:].isdigit():
# callback_ban_123456 -> callback_ban
command = "callback_ban"
elif callback_data.startswith("page_") and callback_data[5:].isdigit():
# callback_page_2 -> callback_page
command = "callback_page"
else:
# Для остальных неизвестных callback'ов оставляем как есть
command = f"callback_{callback_data[:20]}"
return { return {
'command': f"callback_{parts[0][:20]}", 'command': command,
'user_type': "user" if callback.from_user else "unknown", 'user_type': "user" if callback.from_user else "unknown",
'handler_type': "unknown_callback_handler" 'handler_type': "unknown_callback_handler"
} }

View File

@@ -70,6 +70,14 @@ class BotMetrics:
registry=self.registry registry=self.registry
) )
# Database errors counter
self.db_errors_total = Counter(
'db_errors_total',
'Total number of database errors',
['error_type', 'query_type', 'table_name', 'operation'],
registry=self.registry
)
# Message processing metrics # Message processing metrics
self.messages_processed_total = Counter( self.messages_processed_total = Counter(
'messages_processed_total', 'messages_processed_total',
@@ -92,7 +100,14 @@ class BotMetrics:
self.rate_limit_hits_total = Counter( self.rate_limit_hits_total = Counter(
'rate_limit_hits_total', 'rate_limit_hits_total',
'Total number of rate limit hits', 'Total number of rate limit hits',
['limit_type', 'handler_type'], ['limit_type', 'user_id', 'action'],
registry=self.registry
)
# User activity metrics
self.user_activity_total = Counter(
'user_activity_total',
'Total user activity events',
['activity_type', 'user_type', 'chat_type'],
registry=self.registry registry=self.registry
) )
@@ -121,8 +136,8 @@ class BotMetrics:
status=status status=status
).observe(duration) ).observe(duration)
def set_active_users(self, count: int, user_type: str = "total"): def set_active_users(self, count: int, user_type: str = "daily"):
"""Set the number of active users.""" """Set the number of active users for a specific type."""
self.active_users.labels(user_type=user_type).set(count) self.active_users.labels(user_type=user_type).set(count)
def record_db_query(self, query_type: str, duration: float, table_name: str = "unknown", operation: str = "unknown"): def record_db_query(self, query_type: str, duration: float, table_name: str = "unknown", operation: str = "unknown"):
@@ -275,6 +290,12 @@ def db_query_time(query_type: str = "unknown", table_name: str = "unknown", oper
except Exception as e: except Exception as e:
duration = time.time() - start_time duration = time.time() - start_time
metrics.record_db_query(query_type, duration, table_name, operation) metrics.record_db_query(query_type, duration, table_name, operation)
metrics.record_db_error(
type(e).__name__,
query_type,
table_name,
operation
)
metrics.record_error( metrics.record_error(
type(e).__name__, type(e).__name__,
"database", "database",
@@ -293,6 +314,12 @@ def db_query_time(query_type: str = "unknown", table_name: str = "unknown", oper
except Exception as e: except Exception as e:
duration = time.time() - start_time duration = time.time() - start_time
metrics.record_db_query(query_type, duration, table_name, operation) metrics.record_db_query(query_type, duration, table_name, operation)
metrics.record_db_error(
type(e).__name__,
query_type,
table_name,
operation
)
metrics.record_error( metrics.record_error(
type(e).__name__, type(e).__name__,
"database", "database",

View File

@@ -1,206 +0,0 @@
"""
Metrics Scheduler for periodic updates of bot metrics.
Automatically updates active users, system metrics, and other periodic data.
"""
import asyncio
import logging
from datetime import datetime, timezone, timedelta
from typing import Optional
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger
from .metrics import metrics
from .base_dependency_factory import get_global_instance
class MetricsScheduler:
"""Scheduler for periodic metrics updates."""
def __init__(self):
self.scheduler = AsyncIOScheduler()
self.logger = logging.getLogger(__name__)
self.bdf = None
self.bot_db = None
self.is_running = False
async def initialize(self):
"""Initialize scheduler with database connection."""
try:
self.bdf = get_global_instance()
self.bot_db = self.bdf.get_db()
self.logger.info("✅ Metrics scheduler initialized successfully")
except Exception as e:
self.logger.error(f"❌ Failed to initialize metrics scheduler: {e}")
def start_scheduler(self):
"""Start the metrics scheduler."""
if self.is_running:
self.logger.warning("⚠️ Metrics scheduler is already running")
return
try:
# Update active users every 5 minutes
self.scheduler.add_job(
self._update_active_users_metric,
IntervalTrigger(minutes=5),
id='update_active_users',
name='Update Active Users Metric',
replace_existing=True
)
# Update system metrics every minute
self.scheduler.add_job(
self._update_system_metrics,
IntervalTrigger(minutes=1),
id='update_system_metrics',
name='Update System Metrics',
replace_existing=True
)
# Daily metrics reset at midnight
self.scheduler.add_job(
self._daily_metrics_reset,
CronTrigger(hour=0, minute=0),
id='daily_metrics_reset',
name='Daily Metrics Reset',
replace_existing=True
)
# Start scheduler
self.scheduler.start()
self.is_running = True
self.logger.info("✅ Metrics scheduler started successfully")
except Exception as e:
self.logger.error(f"❌ Failed to start metrics scheduler: {e}")
def stop_scheduler(self):
"""Stop the metrics scheduler."""
if not self.is_running:
self.logger.warning("⚠️ Metrics scheduler is not running")
return
try:
self.scheduler.shutdown()
self.is_running = False
self.logger.info("✅ Metrics scheduler stopped successfully")
except Exception as e:
self.logger.error(f"❌ Failed to stop metrics scheduler: {e}")
async def _update_active_users_metric(self):
"""Update active users metric from database."""
try:
if not self.bot_db:
await self.initialize()
if not self.bot_db:
self.logger.warning("⚠️ Cannot update active users: no database connection")
return
self.logger.debug("📊 Updating active users metric...")
# Count active users (last 24 hours)
import time
current_timestamp = int(time.time())
one_day_ago = current_timestamp - (24 * 60 * 60)
active_users_query = """
SELECT COUNT(DISTINCT user_id) as active_users
FROM our_users
WHERE date_changed > ?
"""
await self.bot_db.connect()
await self.bot_db.cursor.execute(active_users_query, (one_day_ago,))
result = await self.bot_db.cursor.fetchone()
active_users = result[0] if result else 0
await self.bot_db.close()
# Update metrics
metrics.set_active_users(active_users, "daily")
metrics.set_active_users(active_users, "total")
self.logger.debug(f"📊 Active users updated: {active_users}")
except Exception as e:
self.logger.error(f"❌ Failed to update active users metric: {e}")
# Set fallback value
metrics.set_active_users(0, "daily")
metrics.set_active_users(0, "total")
async def _update_system_metrics(self):
"""Update system-related metrics."""
try:
# You can add system metrics here (CPU, memory, etc.)
# For now, we'll just log that the job is running
self.logger.debug("📊 System metrics update job running...")
except Exception as e:
self.logger.error(f"❌ Failed to update system metrics: {e}")
async def _daily_metrics_reset(self):
"""Reset daily metrics at midnight."""
try:
self.logger.info("🔄 Daily metrics reset job running...")
# You can add daily metrics reset logic here
# For example, reset daily counters, update retention metrics, etc.
self.logger.info("✅ Daily metrics reset completed")
except Exception as e:
self.logger.error(f"❌ Failed to reset daily metrics: {e}")
async def force_update_active_users(self):
"""Force update of active users metric (for testing)."""
await self._update_active_users_metric()
def get_scheduler_status(self) -> dict:
"""Get current scheduler status."""
if not self.is_running:
return {"status": "stopped", "jobs": 0}
jobs = self.scheduler.get_jobs()
return {
"status": "running",
"jobs": len(jobs),
"job_details": [
{
"id": job.id,
"name": job.name,
"next_run": job.next_run_time.isoformat() if job.next_run_time else None
}
for job in jobs
]
}
# Global metrics scheduler instance
metrics_scheduler: Optional[MetricsScheduler] = None
def get_metrics_scheduler() -> MetricsScheduler:
"""Get global metrics scheduler instance."""
global metrics_scheduler
if metrics_scheduler is None:
metrics_scheduler = MetricsScheduler()
return metrics_scheduler
async def start_metrics_scheduler() -> MetricsScheduler:
"""Start metrics scheduler and return instance."""
global metrics_scheduler
if metrics_scheduler is None:
metrics_scheduler = MetricsScheduler()
await metrics_scheduler.initialize()
metrics_scheduler.start_scheduler()
return metrics_scheduler
def stop_metrics_scheduler():
"""Stop metrics scheduler if running."""
global metrics_scheduler
if metrics_scheduler:
metrics_scheduler.stop_scheduler()