Initial commit: Message Gateway project

- FastAPI приложение для отправки мониторинговых алертов в мессенджеры
- Поддержка Telegram и MAX/VK
- Интеграция с Grafana, Zabbix, AlertManager
- Автоматическое создание тикетов в Jira
- Управление группами мессенджеров через API
- Декораторы для авторизации и скрытия эндпоинтов
- Подробная документация в папке docs/

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
2025-11-12 20:25:11 +03:00
commit b90def35ed
72 changed files with 10609 additions and 0 deletions

316
app/core/metrics.py Normal file
View File

@@ -0,0 +1,316 @@
"""
Централизованное управление метриками Prometheus.
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import logging
from typing import Optional
from prometheus_client import Counter, CollectorRegistry, push_to_gateway
from functools import lru_cache
from app.core.config import settings
logger = logging.getLogger(__name__)
class MetricsManager:
"""Менеджер метрик Prometheus."""
def __init__(self):
"""Инициализация менеджера метрик."""
self.registry = CollectorRegistry()
self._init_metrics()
def _init_metrics(self) -> None:
"""Инициализация всех метрик."""
# API эндпоинты
self.api_endpoint_count = Counter(
'tg_monitoring_gateway_api_endpoint_total',
'Общее количество обращений к эндпоинтам API',
labelnames=['endpoint'],
registry=self.registry
)
# Сообщения по источникам
self.total_messages = Counter(
'tg_monitoring_gateway_total_messages',
'Всего сообщений получено',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
self.sent_messages = Counter(
'tg_monitoring_gateway_sent_messages',
'Сообщений успешно отправлено',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
self.reject_messages = Counter(
'tg_monitoring_gateway_reject_messages',
'Сообщений отклонено (стоп-слова)',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
self.error_messages = Counter(
'tg_monitoring_gateway_error_messages',
'Ошибок отправки сообщений',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
self.firing_messages = Counter(
'tg_monitoring_gateway_firing_messages',
'Горящих алертов',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
self.critical_messages = Counter(
'tg_monitoring_gateway_critical_messages',
'Критических алертов',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
self.resolved_messages = Counter(
'tg_monitoring_gateway_resolved_messages',
'Исправленных алертов',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
# Jira метрики
self.jira_tickets_created = Counter(
'tg_monitoring_gateway_jira_tickets_created',
'Jira тикетов создано',
labelnames=['source', 'k8s_cluster', 'chat', 'thread', 'project'],
registry=self.registry
)
self.jira_tickets_errors = Counter(
'tg_monitoring_gateway_jira_tickets_errors',
'Ошибок создания Jira тикетов',
labelnames=['source', 'k8s_cluster', 'chat', 'thread'],
registry=self.registry
)
def increment_api_endpoint(self, endpoint: str) -> None:
"""
Увеличить счетчик обращений к эндпоинту API.
Args:
endpoint: Имя эндпоинта.
"""
self.api_endpoint_count.labels(endpoint=endpoint).inc()
self._push_metrics()
def increment_total_message(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""
Увеличить счетчик полученных сообщений.
Args:
source: Источник сообщения (grafana, zabbix, alertmanager).
k8s_cluster: Имя Kubernetes кластера (опционально).
chat: Имя чата (опционально).
thread: ID треда (опционально).
"""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.total_messages.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def increment_sent_message(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик отправленных сообщений."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.sent_messages.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def increment_reject_message(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик отклоненных сообщений."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.reject_messages.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def increment_error_message(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик ошибок отправки."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.error_messages.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def increment_firing_message(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик горящих алертов."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.firing_messages.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def increment_critical_message(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик критических алертов."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.critical_messages.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def increment_resolved_message(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик исправленных алертов."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.resolved_messages.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def increment_jira_ticket_created(
self,
source: str,
project: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик созданных Jira тикетов."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.jira_tickets_created.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread,
project=project
).inc()
self._push_metrics()
def increment_jira_ticket_error(
self,
source: str,
k8s_cluster: Optional[str] = None,
chat: Optional[str] = None,
thread: Optional[int] = None
) -> None:
"""Увеличить счетчик ошибок создания Jira тикетов."""
k8s_cluster = k8s_cluster or ""
chat = chat or ""
thread = thread or 0
self.jira_tickets_errors.labels(
source=source,
k8s_cluster=k8s_cluster,
chat=chat,
thread=thread
).inc()
self._push_metrics()
def _push_metrics(self) -> None:
"""Отправить метрики в Pushgateway."""
from app.core.config import get_settings
settings = get_settings()
if not settings.pushgateway_url:
return
try:
push_to_gateway(
settings.pushgateway_url,
job=settings.pushgateway_job,
registry=self.registry
)
except Exception as e:
logger.error(f"Ошибка отправки метрик в Pushgateway: {e}")
# Глобальный экземпляр менеджера метрик
@lru_cache(maxsize=1)
def get_metrics_manager() -> MetricsManager:
"""Получить глобальный экземпляр менеджера метрик."""
return MetricsManager()
metrics = get_metrics_manager()