import os import aiohttp import logging from datetime import datetime from typing import Dict, List, Tuple try: from .metrics_collector import MetricsCollector except ImportError: from metrics_collector import MetricsCollector logger = logging.getLogger(__name__) class MessageSender: def __init__(self): # Получаем переменные окружения self.telegram_bot_token = os.getenv('TELEGRAM_MONITORING_BOT_TOKEN') self.group_for_logs = os.getenv('GROUP_MONITORING_FOR_LOGS') self.important_logs = os.getenv('IMPORTANT_MONITORING_LOGS') # Создаем экземпляр сборщика метрик self.metrics_collector = MetricsCollector() # Время последней отправки статуса self.last_status_time = None if not self.telegram_bot_token: logger.warning("TELEGRAM_MONITORING_BOT_TOKEN не установлен в переменных окружения") if not self.group_for_logs: logger.warning("GROUP_MONITORING_FOR_LOGS не установлен в переменных окружения") if not self.important_logs: logger.warning("IMPORTANT_MONITORING_LOGS не установлен в переменных окружения") async def send_telegram_message(self, chat_id: str, message: str) -> bool: """Отправка сообщения в Telegram через прямое обращение к API""" if not self.telegram_bot_token: logger.error("TELEGRAM_MONITORING_BOT_TOKEN не установлен") return False try: async with aiohttp.ClientSession() as session: url = f"https://api.telegram.org/bot{self.telegram_bot_token}/sendMessage" payload = { "chat_id": chat_id, "text": message, "parse_mode": "HTML" } async with session.post(url, json=payload) as response: if response.status == 200: logger.info(f"Сообщение успешно отправлено в чат {chat_id}") return True else: response_text = await response.text() logger.error(f"Ошибка отправки в Telegram: {response.status} - {response_text}") return False except Exception as e: logger.error(f"Ошибка при отправке сообщения в Telegram: {e}") return False def should_send_status(self) -> bool: """Проверка, нужно ли отправить статус (каждые 30 минут в 00 и 30 минут часа)""" now = datetime.now() # Проверяем, что сейчас 00 или 30 минут часа if now.minute in [0, 30]: # Проверяем, не отправляли ли мы уже статус в эту минуту if (self.last_status_time is None or self.last_status_time.hour != now.hour or self.last_status_time.minute != now.minute): self.last_status_time = now return True return False def should_send_startup_status(self) -> bool: """Проверка, нужно ли отправить статус при запуске""" return self.last_status_time is None def _get_disk_space_emoji(self, disk_percent: float) -> str: """Получение эмодзи для дискового пространства""" if disk_percent < 60: return "🟢" elif disk_percent < 90: return "⚠️" else: return "🚨" def get_status_message(self, system_info: Dict) -> str: """Формирование сообщения со статусом сервера""" try: voice_bot_status, voice_bot_uptime = self.metrics_collector.check_process_status('voice_bot') helper_bot_status, helper_bot_uptime = self.metrics_collector.check_process_status('helper_bot') # Получаем эмодзи для дискового пространства disk_emoji = self._get_disk_space_emoji(system_info['disk_percent']) message = f"""🖥 **Статус Сервера** | {system_info['current_time']} --------------------------------- **📊 Общая нагрузка:** CPU: {system_info['cpu_percent']}% | LA: {system_info['load_avg_1m']} / {system_info['cpu_count']} | IO Wait: {system_info['disk_percent']}% **💾 Память:** RAM: {system_info['ram_used']}/{system_info['ram_total']} GB ({system_info['ram_percent']}%) Swap: {system_info['swap_used']}/{system_info['swap_total']} GB ({system_info['swap_percent']}%) **🗂️ Дисковое пространство:** Диск (/): {system_info['disk_used']}/{system_info['disk_total']} GB ({system_info['disk_percent']}%) {disk_emoji} **💿 Диск I/O:** Read: {system_info['disk_read_speed']} | Write: {system_info['disk_write_speed']} Диск загружен: {system_info['disk_io_percent']}% **🤖 Процессы:** {voice_bot_status} voice-bot - {voice_bot_uptime} {helper_bot_status} helper-bot - {helper_bot_uptime} --------------------------------- ⏰ Uptime сервера: {system_info['system_uptime']}""" return message except Exception as e: logger.error(f"Ошибка при формировании статуса сервера: {e}") return f"Ошибка при получении статуса сервера: {e}" def get_alert_message(self, metric_name: str, current_value: float, details: str) -> str: """Формирование сообщения об алерте""" try: message = f"""🚨 **ALERT: Высокая нагрузка на сервере!** --------------------------------- **Показатель:** {metric_name} **Текущее значение:** {current_value}% ⚠️ **Пороговое значение:** 80% **Детали:** {details} **Сервер:** `{self.metrics_collector.os_type.upper()}` **Время:** `{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}` ---------------------------------""" return message except Exception as e: logger.error(f"Ошибка при формировании алерта: {e}") return f"Ошибка при формировании алерта: {e}" def get_recovery_message(self, metric_name: str, current_value: float, peak_value: float) -> str: """Формирование сообщения о восстановлении""" try: message = f"""✅ **RECOVERY: Нагрузка нормализовалась** --------------------------------- **Показатель:** {metric_name} **Текущее значение:** {current_value}% ✔️ **Было превышение:** До {peak_value}% **Сервер:** `{self.metrics_collector.os_type.upper()}` **Время:** `{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}` ---------------------------------""" return message except Exception as e: logger.error(f"Ошибка при формировании сообщения о восстановлении: {e}") return f"Ошибка при формировании сообщения о восстановлении: {e}" async def send_status_message(self) -> bool: """Отправка статуса сервера в группу логов""" if not self.group_for_logs: logger.warning("GROUP_MONITORING_FOR_LOGS не установлен, пропускаем отправку статуса") return False try: system_info = self.metrics_collector.get_system_info() if not system_info: logger.error("Не удалось получить информацию о системе") return False status_message = self.get_status_message(system_info) return await self.send_telegram_message(self.group_for_logs, status_message) except Exception as e: logger.error(f"Ошибка при отправке статуса: {e}") return False async def send_alert_message(self, metric_type: str, current_value: float, details: str) -> bool: """Отправка сообщения об алерте в важные логи""" if not self.important_logs: logger.warning("IMPORTANT_MONITORING_LOGS не установлен, пропускаем отправку алерта") return False try: metric_names = { 'cpu': 'Использование CPU', 'ram': 'Использование оперативной памяти', 'disk': 'Заполнение диска (/)' } metric_name = metric_names.get(metric_type, metric_type) alert_message = self.get_alert_message(metric_name, current_value, details) return await self.send_telegram_message(self.important_logs, alert_message) except Exception as e: logger.error(f"Ошибка при отправке алерта: {e}") return False async def send_recovery_message(self, metric_type: str, current_value: float, peak_value: float) -> bool: """Отправка сообщения о восстановлении в важные логи""" if not self.important_logs: logger.warning("IMPORTANT_MONITORING_LOGS не установлен, пропускаем отправку сообщения о восстановлении") return False try: metric_names = { 'cpu': 'Использование CPU', 'ram': 'Использование оперативной памяти', 'disk': 'Заполнение диска (/)' } metric_name = metric_names.get(metric_type, metric_type) recovery_message = self.get_recovery_message(metric_name, current_value, peak_value) return await self.send_telegram_message(self.important_logs, recovery_message) except Exception as e: logger.error(f"Ошибка при отправке сообщения о восстановлении: {e}") return False async def process_alerts_and_recoveries(self) -> None: """Обработка алертов и восстановлений""" try: system_info = self.metrics_collector.get_system_info() if not system_info: return # Проверка алертов alerts, recoveries = self.metrics_collector.check_alerts(system_info) # Отправка алертов for metric_type, value, details in alerts: await self.send_alert_message(metric_type, value, details) logger.warning(f"ALERT отправлен: {metric_type} - {value}% - {details}") # Отправка сообщений о восстановлении for metric_type, value in recoveries: # Находим пиковое значение для сообщения о восстановлении peak_value = self.metrics_collector.threshold await self.send_recovery_message(metric_type, value, peak_value) logger.info(f"RECOVERY отправлен: {metric_type} - {value}%") except Exception as e: logger.error(f"Ошибка при обработке алертов и восстановлений: {e}")