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:
@@ -25,6 +25,13 @@ from helper_bot.handlers.admin.utils import (
|
||||
)
|
||||
from logs.custom_logger import logger
|
||||
|
||||
# Local imports - metrics
|
||||
from helper_bot.utils.metrics import (
|
||||
metrics,
|
||||
track_time,
|
||||
track_errors
|
||||
)
|
||||
|
||||
# Создаем роутер с middleware для проверки доступа
|
||||
admin_router = Router()
|
||||
admin_router.message.middleware(AdminAccessMiddleware())
|
||||
@@ -38,6 +45,8 @@ admin_router.message.middleware(AdminAccessMiddleware())
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
Command('admin')
|
||||
)
|
||||
@track_time("admin_panel", "admin_handlers")
|
||||
@track_errors("admin_handlers", "admin_panel")
|
||||
async def admin_panel(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -62,6 +71,8 @@ async def admin_panel(
|
||||
StateFilter("AWAIT_BAN_TARGET", "AWAIT_BAN_DETAILS", "AWAIT_BAN_DURATION", "BAN_CONFIRMATION"),
|
||||
F.text == 'Отменить'
|
||||
)
|
||||
@track_time("cancel_ban_process", "admin_handlers")
|
||||
@track_errors("admin_handlers", "cancel_ban_process")
|
||||
async def cancel_ban_process(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -81,6 +92,8 @@ async def cancel_ban_process(
|
||||
StateFilter("ADMIN"),
|
||||
F.text == 'Бан (Список)'
|
||||
)
|
||||
@track_time("get_last_users", "admin_handlers")
|
||||
@track_errors("admin_handlers", "get_last_users")
|
||||
async def get_last_users(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -112,6 +125,8 @@ async def get_last_users(
|
||||
StateFilter("ADMIN"),
|
||||
F.text == 'Разбан (список)'
|
||||
)
|
||||
@track_time("get_banned_users", "admin_handlers")
|
||||
@track_errors("admin_handlers", "get_banned_users")
|
||||
async def get_banned_users(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -141,6 +156,8 @@ async def get_banned_users(
|
||||
StateFilter("ADMIN"),
|
||||
F.text.in_(['Бан по нику', 'Бан по ID'])
|
||||
)
|
||||
@track_time("start_ban_process", "admin_handlers")
|
||||
@track_errors("admin_handlers", "start_ban_process")
|
||||
async def start_ban_process(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -162,6 +179,8 @@ async def start_ban_process(
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
StateFilter("AWAIT_BAN_TARGET")
|
||||
)
|
||||
@track_time("process_ban_target", "admin_handlers")
|
||||
@track_errors("admin_handlers", "process_ban_target")
|
||||
async def process_ban_target(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -232,6 +251,8 @@ async def process_ban_target(
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
StateFilter("AWAIT_BAN_DETAILS")
|
||||
)
|
||||
@track_time("process_ban_reason", "admin_handlers")
|
||||
@track_errors("admin_handlers", "process_ban_reason")
|
||||
async def process_ban_reason(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -272,6 +293,8 @@ async def process_ban_reason(
|
||||
ChatTypeFilter(chat_type=["private"]),
|
||||
StateFilter("AWAIT_BAN_DURATION")
|
||||
)
|
||||
@track_time("process_ban_duration", "admin_handlers")
|
||||
@track_errors("admin_handlers", "process_ban_duration")
|
||||
async def process_ban_duration(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
@@ -315,6 +338,8 @@ async def process_ban_duration(
|
||||
StateFilter("BAN_CONFIRMATION"),
|
||||
F.text == 'Подтвердить'
|
||||
)
|
||||
@track_time("confirm_ban", "admin_handlers")
|
||||
@track_errors("admin_handlers", "confirm_ban")
|
||||
async def confirm_ban(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
|
||||
@@ -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 logs.custom_logger import logger
|
||||
|
||||
# Local imports - metrics
|
||||
from helper_bot.utils.metrics import (
|
||||
metrics,
|
||||
track_time,
|
||||
track_errors,
|
||||
db_query_time
|
||||
)
|
||||
|
||||
|
||||
class User:
|
||||
"""Модель пользователя"""
|
||||
@@ -29,6 +37,8 @@ class AdminService:
|
||||
def __init__(self, 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]:
|
||||
"""Получить список последних пользователей"""
|
||||
try:
|
||||
@@ -45,6 +55,8 @@ class AdminService:
|
||||
logger.error(f"Ошибка при получении списка последних пользователей: {e}")
|
||||
raise
|
||||
|
||||
@track_time("get_banned_users", "admin_service")
|
||||
@track_errors("admin_service", "get_banned_users")
|
||||
async def get_banned_users(self) -> List[BannedUser]:
|
||||
"""Получить список заблокированных пользователей"""
|
||||
try:
|
||||
@@ -68,6 +80,8 @@ class AdminService:
|
||||
logger.error(f"Ошибка при получении списка заблокированных пользователей: {e}")
|
||||
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]:
|
||||
"""Получить пользователя по username"""
|
||||
try:
|
||||
@@ -85,6 +99,8 @@ class AdminService:
|
||||
logger.error(f"Ошибка при поиске пользователя по username {username}: {e}")
|
||||
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]:
|
||||
"""Получить пользователя по ID"""
|
||||
try:
|
||||
@@ -101,6 +117,8 @@ class AdminService:
|
||||
logger.error(f"Ошибка при поиске пользователя по ID {user_id}: {e}")
|
||||
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:
|
||||
"""Заблокировать пользователя"""
|
||||
try:
|
||||
@@ -122,6 +140,8 @@ class AdminService:
|
||||
logger.error(f"Ошибка при блокировке пользователя {user_id}: {e}")
|
||||
raise
|
||||
|
||||
@track_time("unban_user", "admin_service")
|
||||
@track_errors("admin_service", "unban_user")
|
||||
async def unban_user(self, user_id: int) -> None:
|
||||
"""Разблокировать пользователя"""
|
||||
try:
|
||||
@@ -131,6 +151,8 @@ class AdminService:
|
||||
logger.error(f"Ошибка при разблокировке пользователя {user_id}: {e}")
|
||||
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:
|
||||
"""Валидация введенного ID пользователя"""
|
||||
try:
|
||||
@@ -141,6 +163,8 @@ class AdminService:
|
||||
except ValueError:
|
||||
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]:
|
||||
"""Получить данные заблокированных пользователей для отображения"""
|
||||
try:
|
||||
|
||||
@@ -24,10 +24,20 @@ from .constants import (
|
||||
)
|
||||
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.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(
|
||||
call: CallbackQuery,
|
||||
settings: MagicData("settings")
|
||||
@@ -59,6 +69,8 @@ async def post_for_group(
|
||||
|
||||
|
||||
@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(
|
||||
call: CallbackQuery,
|
||||
settings: MagicData("settings")
|
||||
@@ -89,6 +101,8 @@ async def decline_post_for_group(
|
||||
|
||||
|
||||
@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):
|
||||
ban_service = get_ban_service()
|
||||
# TODO: переделать на MagicData
|
||||
@@ -109,6 +123,8 @@ async def ban_user_from_post(call: CallbackQuery, **kwargs):
|
||||
|
||||
|
||||
@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):
|
||||
ban_service = get_ban_service()
|
||||
# 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))
|
||||
@track_time("process_unlock_user", "callback_handlers")
|
||||
@track_errors("callback_handlers", "process_unlock_user")
|
||||
async def process_unlock_user(call: CallbackQuery, **kwargs):
|
||||
ban_service = get_ban_service()
|
||||
# TODO: переделать на MagicData
|
||||
@@ -166,6 +184,8 @@ async def process_unlock_user(call: CallbackQuery, **kwargs):
|
||||
|
||||
|
||||
@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):
|
||||
await call.message.delete()
|
||||
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))
|
||||
@track_time("change_page", "callback_handlers")
|
||||
@track_errors("callback_handlers", "change_page")
|
||||
async def change_page(
|
||||
call: CallbackQuery,
|
||||
bot_db: MagicData("bot_db"),
|
||||
@@ -214,6 +236,8 @@ async def change_page(
|
||||
|
||||
|
||||
@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(
|
||||
call: CallbackQuery,
|
||||
bot_db: MagicData("bot_db"),
|
||||
@@ -257,6 +281,8 @@ async def save_voice_message(
|
||||
|
||||
|
||||
@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(
|
||||
call: CallbackQuery,
|
||||
bot_db: MagicData("bot_db"),
|
||||
|
||||
@@ -23,6 +23,14 @@ from .constants import (
|
||||
)
|
||||
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:
|
||||
def __init__(self, bot: Bot, db, settings: Dict[str, Any]):
|
||||
@@ -40,6 +48,8 @@ class PostPublishService:
|
||||
return self.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:
|
||||
"""Основной метод публикации поста"""
|
||||
# Проверяем, является ли сообщение частью медиагруппы
|
||||
@@ -64,6 +74,8 @@ class PostPublishService:
|
||||
else:
|
||||
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:
|
||||
"""Публикация текстового поста"""
|
||||
text_post = html.escape(str(call.message.text))
|
||||
@@ -73,6 +85,8 @@ class PostPublishService:
|
||||
await self._delete_post_and_notify_author(call, author_id)
|
||||
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:
|
||||
"""Публикация поста с фото"""
|
||||
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)
|
||||
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:
|
||||
"""Публикация поста с видео"""
|
||||
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)
|
||||
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:
|
||||
"""Публикация поста с кружком"""
|
||||
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)
|
||||
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:
|
||||
"""Публикация поста с аудио"""
|
||||
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)
|
||||
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:
|
||||
"""Публикация поста с войсом"""
|
||||
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)
|
||||
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:
|
||||
"""Публикация медиагруппы"""
|
||||
logger.info(f"Начинаю публикацию медиагруппы. Helper message ID: {call.message.message_id}")
|
||||
@@ -161,6 +185,8 @@ class PostPublishService:
|
||||
logger.error(f"Ошибка при публикации медиагруппы: {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:
|
||||
"""Отклонение поста"""
|
||||
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}")
|
||||
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:
|
||||
"""Отклонение одиночного поста"""
|
||||
logger.debug(f"Отклоняю одиночный пост. Message ID: {call.message.message_id}")
|
||||
@@ -200,6 +228,8 @@ class PostPublishService:
|
||||
raise
|
||||
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:
|
||||
"""Отклонение медиагруппы"""
|
||||
logger.debug(f"Отклоняю медиагруппу. Helper message ID: {call.message.message_id}")
|
||||
@@ -225,6 +255,8 @@ class PostPublishService:
|
||||
logger.error(f"Ошибка при отправке уведомления автору медиагруппы {author_id}: {e}")
|
||||
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:
|
||||
"""Получение ID автора по 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}")
|
||||
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:
|
||||
"""Получение ID автора для медиагруппы"""
|
||||
# Сначала пытаемся найти автора по helper_message_id
|
||||
@@ -259,6 +293,8 @@ class PostPublishService:
|
||||
raise PostNotFoundError(f"Автор не найден для медиагруппы {message_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:
|
||||
"""Удаление поста и уведомление автора"""
|
||||
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
|
||||
|
||||
@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:
|
||||
"""Удаление медиагруппы и уведомление автора"""
|
||||
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.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:
|
||||
"""Бан пользователя за спам"""
|
||||
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}")
|
||||
|
||||
@track_time("ban_user", "ban_service")
|
||||
@track_errors("ban_service", "ban_user")
|
||||
async def ban_user(self, user_id: str, user_name: str) -> str:
|
||||
"""Бан пользователя по ID"""
|
||||
user_name = await self.db.get_username(int(user_id))
|
||||
@@ -329,6 +371,8 @@ class BanService:
|
||||
|
||||
return user_name
|
||||
|
||||
@track_time("unlock_user", "ban_service")
|
||||
@track_errors("ban_service", "unlock_user")
|
||||
async def unlock_user(self, user_id: str) -> str:
|
||||
"""Разблокировка пользователя"""
|
||||
user_name = await self.db.get_username(int(user_id))
|
||||
|
||||
@@ -46,6 +46,8 @@ class GroupHandlers:
|
||||
)
|
||||
|
||||
@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):
|
||||
"""Handle admin reply to user through group chat"""
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ class AdminReplyService:
|
||||
def __init__(self, db: DatabaseProtocol) -> None:
|
||||
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:
|
||||
"""
|
||||
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}")
|
||||
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(
|
||||
self,
|
||||
chat_id: int,
|
||||
|
||||
@@ -29,7 +29,8 @@ from helper_bot.utils.helper_func import (
|
||||
from helper_bot.utils.metrics import (
|
||||
metrics,
|
||||
track_time,
|
||||
track_errors
|
||||
track_errors,
|
||||
db_query_time
|
||||
)
|
||||
|
||||
# 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"]))
|
||||
|
||||
@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):
|
||||
"""Handle emoji command"""
|
||||
await self.user_service.log_user_message(message)
|
||||
@@ -90,6 +93,8 @@ class PrivateHandlers:
|
||||
await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML')
|
||||
|
||||
@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):
|
||||
"""Handle restart command"""
|
||||
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')
|
||||
|
||||
@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):
|
||||
"""Handle start command and return to bot button with metrics tracking"""
|
||||
# User service operations with metrics
|
||||
@@ -116,6 +123,8 @@ class PrivateHandlers:
|
||||
await message.answer(hello_message, reply_markup=markup, parse_mode='HTML')
|
||||
|
||||
@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):
|
||||
"""Handle suggest post button"""
|
||||
# User service operations with metrics
|
||||
@@ -128,6 +137,8 @@ class PrivateHandlers:
|
||||
await message.answer(suggest_news, reply_markup=markup)
|
||||
|
||||
@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):
|
||||
"""Handle goodbye button"""
|
||||
# User service operations with metrics
|
||||
@@ -144,6 +155,8 @@ class PrivateHandlers:
|
||||
await state.set_state(FSM_STATES["START"])
|
||||
|
||||
@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):
|
||||
"""Handle post submission in suggest state"""
|
||||
# Post service operations with metrics
|
||||
@@ -158,6 +171,8 @@ class PrivateHandlers:
|
||||
await state.set_state(FSM_STATES["START"])
|
||||
|
||||
@error_handler
|
||||
@track_errors("private_handlers", "stickers")
|
||||
@track_time("stickers", "private_handlers")
|
||||
async def stickers(self, message: types.Message, state: FSMContext, **kwargs):
|
||||
"""Handle stickers request"""
|
||||
# User service operations with metrics
|
||||
@@ -171,6 +186,8 @@ class PrivateHandlers:
|
||||
await state.set_state(FSM_STATES["START"])
|
||||
|
||||
@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):
|
||||
"""Handle connect with admin button"""
|
||||
# User service operations with metrics
|
||||
@@ -181,6 +198,8 @@ class PrivateHandlers:
|
||||
await state.set_state(FSM_STATES["PRE_CHAT"])
|
||||
|
||||
@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):
|
||||
"""Handle messages in admin chat states"""
|
||||
# User service operations with metrics
|
||||
|
||||
@@ -74,7 +74,6 @@ class UserService:
|
||||
|
||||
@track_time("update_user_activity", "user_service")
|
||||
@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:
|
||||
"""Update user's last activity timestamp with metrics tracking"""
|
||||
await self.db.update_user_date(user_id)
|
||||
|
||||
@@ -14,6 +14,13 @@ from helper_bot.handlers.voice.constants import (
|
||||
)
|
||||
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:
|
||||
"""Модель голосового сообщения"""
|
||||
@@ -30,6 +37,8 @@ class VoiceBotService:
|
||||
self.bot_db = bot_db
|
||||
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]:
|
||||
"""Получить случайный приветственный стикер"""
|
||||
try:
|
||||
@@ -47,6 +56,8 @@ class VoiceBotService:
|
||||
await self._send_error_to_logs(f'Отправка приветственных стикеров лажает. Ошибка: {e}')
|
||||
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):
|
||||
"""Отправить приветственные сообщения"""
|
||||
try:
|
||||
@@ -141,6 +152,8 @@ class VoiceBotService:
|
||||
logger.error(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]]:
|
||||
"""Получить случайное аудио для прослушивания"""
|
||||
try:
|
||||
@@ -165,6 +178,8 @@ class VoiceBotService:
|
||||
logger.error(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:
|
||||
"""Пометить аудио как прослушанное"""
|
||||
try:
|
||||
@@ -173,6 +188,8 @@ class VoiceBotService:
|
||||
logger.error(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:
|
||||
"""Очистить прослушивания пользователя"""
|
||||
try:
|
||||
@@ -181,6 +198,8 @@ class VoiceBotService:
|
||||
logger.error(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:
|
||||
"""Получить количество оставшихся непрослушанных аудио"""
|
||||
try:
|
||||
@@ -190,11 +209,15 @@ class VoiceBotService:
|
||||
logger.error(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):
|
||||
"""Получить основную клавиатуру"""
|
||||
from helper_bot.keyboards.keyboards import 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:
|
||||
"""Отправить ошибку в логи"""
|
||||
try:
|
||||
@@ -215,6 +238,8 @@ class AudioFileService:
|
||||
def __init__(self, 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:
|
||||
"""Сгенерировать имя файла для аудио"""
|
||||
try:
|
||||
@@ -245,6 +270,8 @@ class AudioFileService:
|
||||
logger.error(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:
|
||||
"""Сохранить информацию об аудио файле в базу данных"""
|
||||
try:
|
||||
@@ -253,6 +280,8 @@ class AudioFileService:
|
||||
logger.error(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:
|
||||
"""Скачать и сохранить аудио файл"""
|
||||
try:
|
||||
|
||||
@@ -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 BUTTON_TEXTS
|
||||
|
||||
# Local imports - metrics
|
||||
from helper_bot.utils.metrics import (
|
||||
metrics,
|
||||
track_time,
|
||||
track_errors,
|
||||
db_query_time
|
||||
)
|
||||
|
||||
class VoiceHandlers:
|
||||
def __init__(self, db, settings):
|
||||
@@ -115,6 +122,8 @@ class VoiceHandlers:
|
||||
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")):
|
||||
"""Обработчик кнопки 'Голосовой бот' из основной клавиатуры"""
|
||||
try:
|
||||
@@ -135,6 +144,8 @@ class VoiceHandlers:
|
||||
# В случае ошибки вызываем start
|
||||
await self.start(message, state, bot_db, settings)
|
||||
|
||||
@track_time("restart_function", "voice_handlers")
|
||||
@track_errors("voice_handlers", "restart_function")
|
||||
async def restart_function(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -150,6 +161,8 @@ class VoiceHandlers:
|
||||
await message.answer(text='🎤 Записывайся или слушай!', reply_markup=markup)
|
||||
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(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -162,6 +175,8 @@ class VoiceHandlers:
|
||||
if user_emoji is not None:
|
||||
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(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -177,6 +192,8 @@ class VoiceHandlers:
|
||||
)
|
||||
await state.set_state(STATE_START)
|
||||
|
||||
@track_time("start", "voice_handlers")
|
||||
@track_errors("voice_handlers", "start")
|
||||
async def start(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -201,6 +218,8 @@ class VoiceHandlers:
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отметке получения приветствия: {e}")
|
||||
|
||||
@track_time("cancel_handler", "voice_handlers")
|
||||
@track_errors("voice_handlers", "cancel_handler")
|
||||
async def cancel_handler(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -215,6 +234,8 @@ class VoiceHandlers:
|
||||
await message.answer(text='Добро пожаловать в меню!', reply_markup=markup, parse_mode='HTML')
|
||||
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(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -239,6 +260,8 @@ class VoiceHandlers:
|
||||
await state.set_state(STATE_START)
|
||||
|
||||
|
||||
@track_time("standup_write", "voice_handlers")
|
||||
@track_errors("voice_handlers", "standup_write")
|
||||
async def standup_write(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -261,6 +284,8 @@ class VoiceHandlers:
|
||||
await state.set_state(STATE_STANDUP_WRITE)
|
||||
|
||||
|
||||
@track_time("suggest_voice", "voice_handlers")
|
||||
@track_errors("voice_handlers", "suggest_voice")
|
||||
async def suggest_voice(
|
||||
self,
|
||||
message: types.Message,
|
||||
@@ -299,6 +324,8 @@ class VoiceHandlers:
|
||||
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(
|
||||
self,
|
||||
message: types.Message,
|
||||
|
||||
@@ -81,12 +81,8 @@ async def start_bot(bdf):
|
||||
# Запускаем метрики сервер
|
||||
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("✅ Планировщик метрик запущен")
|
||||
logging.info("✅ Метрики будут обновляться в реальном времени через middleware")
|
||||
|
||||
# Запускаем бота с retry логикой
|
||||
await start_bot_with_retry(bot, dp)
|
||||
|
||||
@@ -162,7 +162,7 @@ class MetricsMiddleware(BaseMiddleware):
|
||||
await bot_db.cursor.execute(total_users_query)
|
||||
total_users_result = await bot_db.cursor.fetchone()
|
||||
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)
|
||||
daily_users_result = await bot_db.cursor.fetchone()
|
||||
daily_users = daily_users_result[0] if daily_users_result else 1
|
||||
@@ -328,8 +328,21 @@ class MetricsMiddleware(BaseMiddleware):
|
||||
|
||||
# FALLBACK: Record unknown callback
|
||||
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 {
|
||||
'command': f"callback_{parts[0][:20]}",
|
||||
'command': command,
|
||||
'user_type': "user" if callback.from_user else "unknown",
|
||||
'handler_type': "unknown_callback_handler"
|
||||
}
|
||||
|
||||
@@ -70,6 +70,14 @@ class BotMetrics:
|
||||
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
|
||||
self.messages_processed_total = Counter(
|
||||
'messages_processed_total',
|
||||
@@ -92,7 +100,14 @@ class BotMetrics:
|
||||
self.rate_limit_hits_total = Counter(
|
||||
'rate_limit_hits_total',
|
||||
'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
|
||||
)
|
||||
|
||||
@@ -121,8 +136,8 @@ class BotMetrics:
|
||||
status=status
|
||||
).observe(duration)
|
||||
|
||||
def set_active_users(self, count: int, user_type: str = "total"):
|
||||
"""Set the number of active users."""
|
||||
def set_active_users(self, count: int, user_type: str = "daily"):
|
||||
"""Set the number of active users for a specific type."""
|
||||
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"):
|
||||
@@ -275,6 +290,12 @@ def db_query_time(query_type: str = "unknown", table_name: str = "unknown", oper
|
||||
except Exception as e:
|
||||
duration = time.time() - start_time
|
||||
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(
|
||||
type(e).__name__,
|
||||
"database",
|
||||
@@ -293,6 +314,12 @@ def db_query_time(query_type: str = "unknown", table_name: str = "unknown", oper
|
||||
except Exception as e:
|
||||
duration = time.time() - start_time
|
||||
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(
|
||||
type(e).__name__,
|
||||
"database",
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user